diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java
index ae17f988dc7..957abd27d41 100644
--- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java
+++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java
@@ -171,6 +171,7 @@ public enum SpellApiToAi {
.put(ApiType.UnattachAll, UnattachAllAi.class)
.put(ApiType.Untap, UntapAi.class)
.put(ApiType.UntapAll, UntapAllAi.class)
+ .put(ApiType.Venture, AlwaysPlayAi.class)
.put(ApiType.Vote, VoteAi.class)
.put(ApiType.WinsGame, GameWinAi.class)
@@ -191,6 +192,6 @@ public enum SpellApiToAi {
result = ReflectionUtil.makeDefaultInstanceOf(clz);
apiToInstance.put(api, result);
}
- return result;
+ return result;
}
}
diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java
index 0aac2fd8695..b6989348d16 100644
--- a/forge-core/src/main/java/forge/card/CardRules.java
+++ b/forge-core/src/main/java/forge/card/CardRules.java
@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
@@ -34,7 +34,7 @@ import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
/**
* A collection of methods containing full
* meta and gameplay properties of a card.
- *
+ *
* @author Forge
* @version $Id: CardRules.java 9708 2011-08-09 19:34:12Z jendave $
*/
@@ -116,7 +116,7 @@ public final class CardRules implements ICardCharacteristics {
public boolean isVariant() {
CardType t = getType();
- return t.isVanguard() || t.isScheme() || t.isPlane() || t.isPhenomenon() || t.isConspiracy();
+ return t.isVanguard() || t.isScheme() || t.isPlane() || t.isPhenomenon() || t.isConspiracy() || t.isDungeon();
}
public CardSplitType getSplitType() {
@@ -334,7 +334,7 @@ public final class CardRules implements ICardCharacteristics {
/**
* Gets the card.
- *
+ *
* @return the card
*/
public final CardRules getCard() {
@@ -370,7 +370,7 @@ public final class CardRules implements ICardCharacteristics {
/**
* Parses the line.
- *
+ *
* @param line
* the line
*/
@@ -528,7 +528,7 @@ public final class CardRules implements ICardCharacteristics {
/*
* (non-Javadoc)
- *
+ *
* @see java.util.Iterator#hasNext()
*/
@Override
@@ -538,7 +538,7 @@ public final class CardRules implements ICardCharacteristics {
/*
* (non-Javadoc)
- *
+ *
* @see java.util.Iterator#next()
*/
@Override
@@ -554,7 +554,7 @@ public final class CardRules implements ICardCharacteristics {
/*
* (non-Javadoc)
- *
+ *
* @see java.util.Iterator#remove()
*/
@Override
diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java
index e32e87b7070..560bfbe2e9c 100644
--- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java
+++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java
@@ -598,6 +598,7 @@ public final class CardRulesPredicates {
public static final Predicate IS_SCHEME = CardRulesPredicates.coreType(true, CardType.CoreType.Scheme);
public static final Predicate IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard);
public static final Predicate IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
+ public static final Predicate IS_DUNGEON = CardRulesPredicates.coreType(true, CardType.CoreType.Dungeon);
public static final Predicate IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
diff --git a/forge-core/src/main/java/forge/card/CardType.java b/forge-core/src/main/java/forge/card/CardType.java
index c2172c1058f..3941b990124 100644
--- a/forge-core/src/main/java/forge/card/CardType.java
+++ b/forge-core/src/main/java/forge/card/CardType.java
@@ -57,6 +57,7 @@ public final class CardType implements Comparable, CardTypeView {
Artifact(true, "artifacts"),
Conspiracy(false, "conspiracies"),
Creature(true, "creatures"),
+ Dungeon(false, "dungeons"),
Emblem(false, "emblems"),
Enchantment(true, "enchantments"),
Instant(false, "instants"),
@@ -446,6 +447,11 @@ public final class CardType implements Comparable, CardTypeView {
return coreTypes.contains(CoreType.Tribal);
}
+ @Override
+ public boolean isDungeon() {
+ return coreTypes.contains(CoreType.Dungeon);
+ }
+
@Override
public String toString() {
if (calculatedType == null) {
@@ -686,13 +692,11 @@ public final class CardType implements Comparable, CardTypeView {
}
private static boolean isMultiwordType(final String type) {
- final String[] multiWordTypes = { "Serra's Realm", "Bolas's Meditation Realm" };
- // no need to loop for only 2 exceptions!
- if (multiWordTypes[0].startsWith(type) && !multiWordTypes[0].equals(type)) {
- return true;
- }
- if (multiWordTypes[1].startsWith(type) && !multiWordTypes[1].equals(type)) {
- return true;
+ final String[] multiWordTypes = { "Serra's Realm", "Bolas's Meditation Realm", "Dungeon Master" };
+ for (int i = 0; i < multiWordTypes.length; ++i) {
+ if (multiWordTypes[i].startsWith(type) && !multiWordTypes[i].equals(type)) {
+ return true;
+ }
}
return false;
}
diff --git a/forge-core/src/main/java/forge/card/CardTypeView.java b/forge-core/src/main/java/forge/card/CardTypeView.java
index e9b65aeac81..cfecf57a752 100644
--- a/forge-core/src/main/java/forge/card/CardTypeView.java
+++ b/forge-core/src/main/java/forge/card/CardTypeView.java
@@ -44,5 +44,6 @@ public interface CardTypeView extends Iterable, Serializable {
boolean isPhenomenon();
boolean isEmblem();
boolean isTribal();
+ boolean isDungeon();
CardTypeView getTypeWithChanges(Iterable changedCardTypes);
}
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index 6cd1d3aef23..a5fa42d8772 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -1192,6 +1192,8 @@ public class GameAction {
for (final Card c : cards) {
// If a token is in a zone other than the battlefield, it ceases to exist.
checkAgain |= stateBasedAction704_5d(c);
+ // Dungeon Card won't affect other cards, so don't need to set checkAgain
+ stateBasedAction_Dungeon(c);
}
}
}
@@ -1382,6 +1384,15 @@ public class GameAction {
return checkAgain;
}
+ private void stateBasedAction_Dungeon(Card c) {
+ if (!c.getType().isDungeon() || !c.isInLastRoom()) {
+ return;
+ }
+ if (!game.getStack().hasSourceOnStack(c, null)) {
+ completeDungeon(c.getController(), c);
+ }
+ }
+
private boolean stateBasedAction704_attach(Card c, CardZoneTable table) {
boolean checkAgain = false;
@@ -1706,7 +1717,7 @@ public class GameAction {
game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false);
// in case the destroyed card has such a trigger
game.getTriggerHandler().registerActiveLTBTrigger(c);
-
+
final Card sacrificed = sacrificeDestroy(c, sa, table, params);
return sacrificed != null;
}
@@ -2187,4 +2198,14 @@ public class GameAction {
counterTable.triggerCountersPutAll(game);
counterTable.clear();
}
+
+ public void completeDungeon(Player player, Card dungeon) {
+ player.addCompletedDungeon(dungeon);
+ ceaseToExist(dungeon, true);
+
+ // Run RoomEntered trigger
+ final Map runParams = AbilityKey.mapFromCard(dungeon);
+ runParams.put(AbilityKey.Player, player);
+ game.getTriggerHandler().runTrigger(TriggerType.DungeonCompleted, runParams, false);
+ }
}
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java
index 2c5d4f86b48..a3186c03c6c 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java
@@ -107,6 +107,7 @@ public enum AbilityKey {
ReplacementResult("ReplacementResult"),
ReplacementResultMap("ReplacementResultMap"),
Result("Result"),
+ RoomName("RoomName"),
Scheme("Scheme"),
Source("Source"),
Sources("Sources"),
diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
index 87e8d638540..b0b6aaf65ae 100644
--- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
+++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java
@@ -3359,6 +3359,29 @@ public class AbilityUtils {
return doXMath(opps == null ? 0 : opps.size(), m, source, ctb);
}
+ if (value.equals("DungeonsCompleted")) {
+ return doXMath(player.getCompletedDungeons().size(), m, source, ctb);
+ }
+ if (value.equals("DifferentlyNamedDungeonsCompleted")) {
+ int amount = 0;
+ List dungeons = player.getCompletedDungeons();
+ for (int i = 0; i < dungeons.size(); ++i) {
+ Card d1 = dungeons.get(i);
+ boolean hasSameName = false;
+ for (int j = i - 1; j >= 0; --j) {
+ Card d2 = dungeons.get(j);
+ if (d1.getName().equals(d2.getName())) {
+ hasSameName = true;
+ break;
+ }
+ }
+ if (!hasSameName) {
+ ++amount;
+ }
+ }
+ return doXMath(amount, m, source, ctb);
+ }
+
return doXMath(0, m, source, ctb);
}
diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java
index c528e15ce83..7bdbb06db4b 100644
--- a/forge-game/src/main/java/forge/game/ability/ApiType.java
+++ b/forge-game/src/main/java/forge/game/ability/ApiType.java
@@ -7,7 +7,7 @@ import java.util.Map;
import forge.game.ability.effects.*;
import forge.util.ReflectionUtil;
-/**
+/**
* TODO: Write javadoc for this type.
*
*/
@@ -172,6 +172,7 @@ public enum ApiType {
UnattachAll (UnattachAllEffect.class),
Untap (UntapEffect.class),
UntapAll (UntapAllEffect.class),
+ Venture (VentureEffect.class),
Vote (VoteEffect.class),
WinsGame (GameWinEffect.class),
@@ -187,7 +188,7 @@ public enum ApiType {
private final Class extends SpellAbilityEffect> clsEffect;
private static final Map allValues = new HashMap<>();
-
+
static {
for(ApiType t : ApiType.values()) {
allValues.put(t.name().toLowerCase(), t);
@@ -197,7 +198,7 @@ public enum ApiType {
ApiType(Class extends SpellAbilityEffect> clsEf) { this(clsEf, true); }
ApiType(Class extends SpellAbilityEffect> clsEf, final boolean isStateLess) {
clsEffect = clsEf;
- instanceEffect = isStateLess ? ReflectionUtil.makeDefaultInstanceOf(clsEf) : null;
+ instanceEffect = isStateLess ? ReflectionUtil.makeDefaultInstanceOf(clsEf) : null;
}
public static ApiType smartValueOf(String value) {
diff --git a/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java b/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java
new file mode 100644
index 00000000000..96af917b2db
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java
@@ -0,0 +1,125 @@
+package forge.game.ability.effects;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.base.Predicates;
+
+import forge.StaticData;
+import forge.card.CardRulesPredicates;
+import forge.card.ICardFace;
+import forge.game.Game;
+import forge.game.ability.AbilityKey;
+import forge.game.ability.SpellAbilityEffect;
+import forge.game.card.Card;
+import forge.game.card.CardCollectionView;
+import forge.game.card.CounterType;
+import forge.game.event.GameEventCardCounters;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+import forge.game.trigger.Trigger;
+import forge.game.trigger.TriggerType;
+import forge.game.trigger.WrappedAbility;
+import forge.game.zone.ZoneType;
+import forge.item.PaperCard;
+import forge.util.Localizer;
+
+public class VentureEffect extends SpellAbilityEffect {
+
+ private Card getDungeonCard(SpellAbility sa, Player player) {
+ final Game game = player.getGame();
+
+ CardCollectionView commandCards = player.getCardsIn(ZoneType.Command);
+ for (Card card : commandCards) {
+ if (card.getType().isDungeon()) {
+ if (!card.isInLastRoom()) {
+ return card;
+ }
+ // If the current dungeon is already in last room, complete it first.
+ game.getAction().completeDungeon(player, card);
+ break;
+ }
+ }
+
+ // Create a new dugeon card chosen by player in command zone.
+ List dungeonCards = StaticData.instance().getVariantCards().getAllCards(
+ Predicates.compose(CardRulesPredicates.Presets.IS_DUNGEON, PaperCard.FN_GET_RULES));
+ List faces = new ArrayList<>();
+ for (PaperCard pc : dungeonCards) {
+ faces.add(pc.getRules().getMainPart());
+ }
+ String message = Localizer.getInstance().getMessage("lblChooseACardName");
+ String chosen = player.getController().chooseCardName(sa, faces, message);
+ Card dungeon = Card.fromPaperCard(StaticData.instance().getVariantCards().getUniqueByName(chosen), player);
+
+ game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
+ game.getAction().moveTo(ZoneType.Command, dungeon, sa);
+ game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone);
+
+ return dungeon;
+ }
+
+ private String chooseNextRoom(SpellAbility sa, Player player, Card dungeon, String room) {
+ String nextRoomParam = "";
+ for (final Trigger t : dungeon.getTriggers()) {
+ SpellAbility roomSA = t.getOverridingAbility();
+ if (roomSA.getParam("RoomName").equals(room)) {
+ nextRoomParam = roomSA.getParam("NextRoomName");
+ break;
+ }
+ }
+ String [] nextRoomNames = nextRoomParam.split(",");
+ if (nextRoomNames.length > 1) {
+ List candidates = new ArrayList<>();
+ for (String nextRoomName : nextRoomNames) {
+ for (final Trigger t : dungeon.getTriggers()) {
+ SpellAbility roomSA = t.getOverridingAbility();
+ if (roomSA.getParam("RoomName").equals(nextRoomName)) {
+ candidates.add(new WrappedAbility(t, roomSA, player));
+ break;
+ }
+ }
+ }
+ final String title = Localizer.getInstance().getMessage("lblChooseAbilityForObject", dungeon.toString());
+ SpellAbility chosen = player.getController().chooseSingleSpellForEffect(candidates, sa, title, null);
+ return chosen.getParam("RoomName");
+ } else {
+ return nextRoomNames[0];
+ }
+ }
+
+ private void ventureIntoDungeon(SpellAbility sa, Player player) {
+ final Game game = player.getGame();
+ Card dungeon = getDungeonCard(sa, player);
+ String room = dungeon.getCurrentRoom();
+ String nextRoom = null;
+
+ // Determine next room to venture into
+ if (room == null || room.isEmpty()) {
+ SpellAbility roomSA = dungeon.getTriggers().get(0).getOverridingAbility();
+ nextRoom = roomSA.getParam("RoomName");
+ } else {
+ nextRoom = chooseNextRoom(sa, player, dungeon, room);
+ }
+
+ dungeon.setCurrentRoom(nextRoom);
+ // TODO: Currently play the Add Counter sound, but maybe add soundeffect for marker?
+ game.fireEvent(new GameEventCardCounters(dungeon, CounterType.getType("LEVEL"), 0, 1));
+
+ // Run RoomEntered trigger
+ final Map runParams = AbilityKey.mapFromCard(dungeon);
+ runParams.put(AbilityKey.RoomName, nextRoom);
+ game.getTriggerHandler().runTrigger(TriggerType.RoomEntered, runParams, false);
+ }
+
+ @Override
+ public void resolve(SpellAbility sa) {
+ for (final Player p : getTargetPlayers(sa)) {
+ if (!sa.usesTargeting() || p.canBeTargetedBy(sa)) {
+ ventureIntoDungeon(sa, p);
+ }
+ }
+ }
+
+}
diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java
index afebbeeeed1..292662352dd 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -302,6 +302,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
private EvenOdd chosenEvenOdd = null;
private Direction chosenDirection = null;
private String chosenMode = "";
+ private String currentRoom = null;
private Card exiledWith = null;
private Player exiledBy = null;
@@ -1782,6 +1783,23 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
view.updateChosenMode(this);
}
+ public String getCurrentRoom() {
+ return currentRoom;
+ }
+ public void setCurrentRoom(String room) {
+ currentRoom = room;
+ view.updateCurrentRoom(this);
+ }
+ public boolean isInLastRoom() {
+ for (final Trigger t : getTriggers()) {
+ SpellAbility sa = t.getOverridingAbility();
+ if (sa.getParam("RoomName").equals(currentRoom) && !sa.hasParam("NextRoom")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public boolean hasChosenName() {
return chosenName != null;
}
@@ -2112,7 +2130,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
|| keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Adapt")
|| keyword.startsWith("Transfigure") || keyword.startsWith("Aura swap")
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")
- || keyword.startsWith("Encore") || keyword.startsWith("Mutate")) {
+ || keyword.startsWith("Encore") || keyword.startsWith("Mutate") || keyword.startsWith("Dungeon")) {
// keyword parsing takes care of adding a proper description
} else if (keyword.startsWith("CantBeBlockedByAmount")) {
sbLong.append(getName()).append(" can't be blocked ");
@@ -4666,7 +4684,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars {
getGame().getTriggerHandler().registerActiveTrigger(this, false);
getGame().getTriggerHandler().runTrigger(TriggerType.PhaseIn, runParams, false);
}
-
+
game.updateLastStateForCard(this);
return true;
diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
index 060db0f2008..569f9821f62 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
@@ -19,6 +19,7 @@ package forge.game.card;
import java.util.Arrays;
import java.util.EnumSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -1817,6 +1818,41 @@ public class CardFactoryUtil {
inst.addTrigger(parsedUpkeepTrig);
inst.addTrigger(parsedSacTrigger);
+ } else if (keyword.startsWith("Dungeon")) {
+ final List abs = Arrays.asList(keyword.substring("Dungeon:".length()).split(","));
+ final Map saMap = new LinkedHashMap<>();
+
+ for(String ab : abs) {
+ saMap.put(ab, AbilityFactory.getAbility(card, ab));
+ }
+ for (SpellAbility sa : saMap.values()) {
+ String roomName = sa.getParam("RoomName");
+ StringBuilder trigStr = new StringBuilder("Mode$ RoomEntered | TriggerZones$ Command");
+ trigStr.append(" | ValidCard$ Card.Self | ValidRoom$ ").append(roomName);
+ trigStr.append(" | TriggerDescription$ ").append(roomName).append(" — ").append(sa.getDescription());
+ if (sa.hasParam("NextRoom")) {
+ boolean first = true;
+ StringBuilder nextRoomParam = new StringBuilder();
+ trigStr.append(" (→ ");
+ for (String nextRoomSVar : sa.getParam("NextRoom").split(",")) {
+ if (!first) {
+ trigStr.append(" or ");
+ nextRoomParam.append(",");
+ }
+ String nextRoomName = saMap.get(nextRoomSVar).getParam("RoomName");
+ trigStr.append(nextRoomName);
+ nextRoomParam.append(nextRoomName);
+ first = false;
+ }
+ trigStr.append(")");
+ sa.putParam("NextRoomName", nextRoomParam.toString());
+ }
+
+ // Need to set intrinsic to false here, else the first room won't get triggered
+ final Trigger t = TriggerHandler.parseTrigger(trigStr.toString(), card, false);
+ t.setOverridingAbility(sa);
+ inst.addTrigger(t);
+ }
} else if (keyword.startsWith("Ward")) {
final String[] k = keyword.split(":");
final Cost cost = new Cost(k[1], false);
diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java
index 8c143e87aac..c6c09df0127 100644
--- a/forge-game/src/main/java/forge/game/card/CardView.java
+++ b/forge-game/src/main/java/forge/game/card/CardView.java
@@ -389,6 +389,13 @@ public class CardView extends GameEntityView {
set(TrackableProperty.ChosenMode, c.getChosenMode());
}
+ public String getCurrentRoom() {
+ return get(TrackableProperty.CurrentRoom);
+ }
+ void updateCurrentRoom(Card c) {
+ set(TrackableProperty.CurrentRoom, c.getCurrentRoom());
+ }
+
private String getRemembered() {
return get(TrackableProperty.Remembered);
}
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 d28579534ac..8a6c6618ace 100644
--- a/forge-game/src/main/java/forge/game/player/Player.java
+++ b/forge-game/src/main/java/forge/game/player/Player.java
@@ -194,6 +194,7 @@ public class Player extends GameEntity implements Comparable {
private boolean tappedLandForManaThisTurn = false;
private int attackersDeclaredThisTurn = 0;
private PlayerCollection attackedOpponentsThisTurn = new PlayerCollection();
+ private List completedDungeons = new ArrayList<>();
private final Map zones = Maps.newEnumMap(ZoneType.class);
private final Map adjustLandPlays = Maps.newHashMap();
@@ -1947,6 +1948,13 @@ public class Player extends GameEntity implements Comparable {
attackersDeclaredThisTurn = 0;
}
+ public final List getCompletedDungeons() {
+ return completedDungeons;
+ }
+ public void addCompletedDungeon(Card dungeon) {
+ completedDungeons.add(dungeon);
+ }
+
public final void altWinBySpellEffect(final String sourceName) {
if (cantWin()) {
System.out.println("Tried to win, but currently can't.");
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerCompletedDungeon.java b/forge-game/src/main/java/forge/game/trigger/TriggerCompletedDungeon.java
new file mode 100644
index 00000000000..8312a3d6c8e
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerCompletedDungeon.java
@@ -0,0 +1,35 @@
+package forge.game.trigger;
+
+import java.util.Map;
+
+import forge.game.ability.AbilityKey;
+import forge.game.card.Card;
+import forge.game.spellability.SpellAbility;
+import forge.util.Localizer;
+
+public class TriggerCompletedDungeon extends Trigger {
+
+ public TriggerCompletedDungeon(final Map params, final Card host, final boolean intrinsic) {
+ super(params, host, intrinsic);
+ }
+
+ @Override
+ public final boolean performTest(final Map runParams) {
+ if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Player))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public final void setTriggeringObjects(final SpellAbility sa, Map runParams) {
+ sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player);
+ }
+
+ public String getImportantStackObjects(SpellAbility sa) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Localizer.getInstance().getMessage("lblPlayer")).append(": ").append(sa.getTriggeringObject(AbilityKey.Player));
+ return sb.toString();
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerEnteredRoom.java b/forge-game/src/main/java/forge/game/trigger/TriggerEnteredRoom.java
new file mode 100644
index 00000000000..1f7e1a8e414
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerEnteredRoom.java
@@ -0,0 +1,42 @@
+package forge.game.trigger;
+
+import java.util.Map;
+
+import forge.game.ability.AbilityKey;
+import forge.game.card.Card;
+import forge.game.spellability.SpellAbility;
+
+public class TriggerEnteredRoom extends Trigger {
+
+ public TriggerEnteredRoom(final Map params, final Card host, final boolean intrinsic) {
+ super(params, host, intrinsic);
+ }
+
+ @Override
+ public final boolean performTest(final Map runParams) {
+ if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Card))) {
+ return false;
+ }
+
+ if (!matchesValidParam("ValidRoom", runParams.get(AbilityKey.RoomName))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public final void setTriggeringObjects(final SpellAbility sa, Map runParams) {
+ sa.setTriggeringObjectsFrom(runParams, AbilityKey.RoomName);
+ }
+
+ public String getImportantStackObjects(SpellAbility sa) {
+ Object roomName = sa.getTriggeringObject(AbilityKey.RoomName);
+ if (roomName != null) {
+ StringBuilder sb = new StringBuilder("Room: ");
+ sb.append(roomName);
+ return sb.toString();
+ }
+ return "";
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerType.java b/forge-game/src/main/java/forge/game/trigger/TriggerType.java
index 606da2ee827..ad50db3d606 100644
--- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java
@@ -7,7 +7,7 @@ import java.util.Map;
import forge.game.card.Card;
-/**
+/**
* TODO: Write javadoc for this type.
*
*/
@@ -58,6 +58,7 @@ public enum TriggerType {
Discarded(TriggerDiscarded.class),
DiscardedAll(TriggerDiscardedAll.class),
Drawn(TriggerDrawn.class),
+ DungeonCompleted(TriggerCompletedDungeon.class),
Evolved(TriggerEvolved.class),
ExcessDamage(TriggerExcessDamage.class),
Exerted(TriggerExerted.class),
@@ -88,6 +89,7 @@ public enum TriggerType {
Regenerated(TriggerRegenerated.class),
Revealed(TriggerRevealed.class),
RolledDie(TriggerRolledDie.class),
+ RoomEntered(TriggerEnteredRoom.class),
Sacrificed(TriggerSacrificed.class),
Scry(TriggerScry.class),
SearchedLibrary(TriggerSearchedLibrary.class),
diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java
index ab5db9e2836..666fee7ffd2 100644
--- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java
+++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java
@@ -63,6 +63,7 @@ public enum TrackableProperty {
ChosenDirection(TrackableTypes.EnumType(Direction.class)),
ChosenEvenOdd(TrackableTypes.EnumType(EvenOdd.class)),
ChosenMode(TrackableTypes.StringType),
+ CurrentRoom(TrackableTypes.StringType),
Remembered(TrackableTypes.StringType),
NamedCard(TrackableTypes.StringType),
NamedCard2(TrackableTypes.StringType),
diff --git a/forge-gui/res/cardsfolder/upcoming/cloister_gargoyle.txt b/forge-gui/res/cardsfolder/upcoming/cloister_gargoyle.txt
new file mode 100644
index 00000000000..d142b7f3afa
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/cloister_gargoyle.txt
@@ -0,0 +1,9 @@
+Name:Cloister Gargoyle
+ManaCost:2 W
+Types:Artifact Creature Gargoyle
+PT:0/4
+T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBVenture | TriggerDescription$ When CARDNAME enters the battlefield, venture into the dungeon. (Enter the first room or advance to the next room.)
+SVar:DBVenture:DB$ Venture | Defined$ You
+S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 3 | AddKeyword$ Flying | CheckSVar$ X | SVarCompare$ GE1 | Description$ As long as you've completed a dungeon, CARDNAME gets +3/+0 and has flying.
+SVar:X:PlayerCountPropertyYou$DungeonsCompleted
+Oracle:When Cloister Gargoyle enters the battlefield, venture into the dungeon. (Enter the first room or advance to the next room.)\nAs long as you've completed a dungeon, Cloister Gargoyle gets +3/+0 and has flying.
diff --git a/forge-gui/res/cardsfolder/upcoming/dungeon_crawler.txt b/forge-gui/res/cardsfolder/upcoming/dungeon_crawler.txt
new file mode 100644
index 00000000000..954174d6c2a
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/dungeon_crawler.txt
@@ -0,0 +1,8 @@
+Name:Dungeon Crawler
+ManaCost:B
+Types:Creature Zombie
+PT:2/1
+K:CARDNAME enters the battlefield tapped.
+T:Mode$ DungeonCompleted | ValidPlayer$ You | TriggerZones$ Graveyard | OptionalDecider$ You | Execute$ DBReturn | TriggerDescription$ Whenever you complete a dungeon, you may return CARDNAME from your graveyard to your hand.
+SVar:DBReturn:DB$ChangeZone | Origin$ Graveyard | Destination$ Hand | Defined$ Self
+Oracle:Dungeon Crawler enters the battlefield tapped.\nWhenever you complete a dungeon, you may return Dungeon Crawler from your graveyard to your hand.
diff --git a/forge-gui/res/cardsfolder/upcoming/dungeon_of_the_mad_mage.txt b/forge-gui/res/cardsfolder/upcoming/dungeon_of_the_mad_mage.txt
new file mode 100644
index 00000000000..7954e01b7e6
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/dungeon_of_the_mad_mage.txt
@@ -0,0 +1,19 @@
+Name:Dungeon of the Mad Mage
+ManaCost:no cost
+Types:Dungeon
+K:Dungeon:DBPortal,DBDungeon,DBBazaar,DBCaverns,DBLost,DBRunestone,DBGraveyard,DBMines,DBLair
+SVar:DBPortal:DB$ GainLife | Defined$ You | LifeAmount$ 1 | RoomName$ Yawning Portal | SpellDescription$ You gain 1 life. | NextRoom$ DBDungeon
+SVar:DBDungeon:DB$ Scry | ScryNum$ 1 | RoomName$ Dungeon Level | SpellDescription$ Scry 1. | NextRoom$ DBBazaar,DBCaverns
+SVar:DBBazaar:DB$ Token | TokenScript$ c_a_treasure_sac | TokenOwner$ You | RoomName$ Goblin Bazaar | SpellDescription$ Create a Treasure token. | NextRoom$ DBLost
+SVar:DBCaverns:DB$ Pump | ValidTgts$ Creature | KW$ HIDDEN CARDNAME can't attack. | Duration$ UntilYourNextTurn | IsCurse$ True | RoomName$ Twisted Caverns | SpellDescription$ Target creature can't attack until your next turn. | NextRoom$ DBLost
+SVar:DBLost:DB$ Scry | ScryNum$ 2 | RoomName$ Lost Level | SpellDescription$ Scry 2. | NextRoom$ DBRunestone,DBGraveyard
+SVar:DBRunestone:DB$ Dig | Defined$ You | DigNum$ 2 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | RoomName$ Runestone Caverns | SpellDescription$ Exile the top two cards of your library. You may play them. | NextRoom$ DBMines
+SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | RememberObjects$ RememberedCard | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup
+SVar:STPlay:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Exile | Affected$ Card.IsRemembered | MayPlay$ True | Description$ You may play them.
+SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+SVar:DBGraveyard:DB$ Token | TokenScript$ b_1_1_skeleton | TokenOwner$ You | TokenAmount$ 2 | RoomName$ Muiral's Graveyard | SpellDescription$ Create two 1/1 black Skeleton creature tokens. | NextRoom$ DBMines
+SVar:DBMines:DB$ Scry | ScryNum$ 3 | RoomName$ Deep Mines | SpellDescription$ Scry 3. | NextRoom$ DBLair
+SVar:DBLair:DB$ Draw | Defined$ You | NumCards$ 3 | RememberDrawn$ True | SubAbility$ DBReveal | RoomName$ Mad Wizard's Lair | SpellDescription$ Draw three cards and reveal them. You may cast one of them without paying its mana cost.
+SVar:DBReveal:DB$ Reveal | Defined$ You | RevealDefined$ Remembered | SubAbility$ DBPlay
+SVar:DBPlay:DB$ Play | Defined$ Remembered.nonLand | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup
+Oracle:(1) Yawning Portal — You gain 1 life. (→ 2)\n(2) Dungeon Level — Scry 1. (→ 3a or 3b)\n(3a) Goblin Bazaar — Create a Treasure token. (→ 4)\n(3b) Twisted Caverns — Target creature can't attack until your next turn. (→ 4)\n(4) Lost Level — Scry 2. (→ 5a or 5b)\n(5a) Runestone Caverns — Exile the top two cards of your library. You may play them. (→ 6)\n(5b) Muiral's Graveyard — Create two 1/1 black Skeleton creature tokens. (→ 6)\n(6) Deep Mines — Scry 3. (→ 7)\n(7) Mad Wizard's Lair — Draw three cards and reveal them. You may cast one of them without paying its mana cost.
diff --git a/forge-gui/res/cardsfolder/upcoming/ellywick_tumblestrum.txt b/forge-gui/res/cardsfolder/upcoming/ellywick_tumblestrum.txt
new file mode 100644
index 00000000000..30de1b8596b
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/ellywick_tumblestrum.txt
@@ -0,0 +1,15 @@
+Name:Ellywick Tumblestrum
+ManaCost:2 G G
+Types:Legendary Planeswalker Ellywick
+Loyalty:4
+A:AB$ Venture | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | Defined$ You | SpellDescription$ Venture into the dungeon. (Enter the first room or advance to the next room.)
+A:AB$ Dig | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | DigNum$ 6 | ChangeNum$ 1 | Optional$ True | ChangeValid$ Creature | ForceRevealToController$ True | RememberChanged$ True | RestRandomOrder$ True | SubAbility$ DBGainLife | SpellDescription$ Look at the top six cards of your library. You may reveal a creature card from among them and put it into your hand. If it's legendary, you gain 3 life. Put the rest on the bottom of your library in a random order.
+SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 3 | ConditionCheckSVar$ IsLegendary | ConditionSVarCompare$ GE1 | SubAbility$ DBCleanup
+SVar:IsLegendary:Count$ValidHand Creature.Legendary+IsRemembered
+SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+A:AB$ Effect | Cost$ SubCounter<7/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem - Ellywick Tumblestrum | Image$ emblem_ellywick_tumblestrum | StaticAbilities$ STOverrun | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Creatures you control have trample and haste and get +2/+2 for each differently named dungeon you've completed."
+SVar:STOverrun:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.YouCtrl | AffectedZone$ Battlefield | AddPower$ X | AddToughness$ X | AddKeyword$ Trample & Haste | Description$ Creatures you control have trample and haste and get +2/+2 for each differently named dungeon you've completed.
+SVar:X:PlayerCountPropertyYou$DifferentlyNamedDungeonsCompleted/Twice
+Oracle:[+1]: Venture into the dungeon. (Enter the first room or advance to the next room.)
+[−2]: Look at the top six cards of your library. You may reveal a creature card from among them and put it into your hand. If it's legendary, you gain 3 life. Put the rest on the bottom of your library in a random order.
+[−7]: You get an emblem with "Creatures you control have trample and haste and get +2/+2 for each differently named dungeon you've completed."
diff --git a/forge-gui/res/cardsfolder/upcoming/gloom_stalker.txt b/forge-gui/res/cardsfolder/upcoming/gloom_stalker.txt
new file mode 100644
index 00000000000..c033e830b2a
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/gloom_stalker.txt
@@ -0,0 +1,7 @@
+Name:Gloom Stalker
+ManaCost:2 W
+Types:Creature Dwarf Ranger
+PT:2/3
+S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Double Strike | CheckSVar$ X | SVarCompare$ GE1 | Description$ As long as you've completed a dungeon, CARDNAME has double strike.
+SVar:X:PlayerCountPropertyYou$DungeonsCompleted
+Oracle:As long as you've completed a dungeon, Gloom Stalker has double strike.
diff --git a/forge-gui/res/cardsfolder/upcoming/lost_mine_of_phandelver.txt b/forge-gui/res/cardsfolder/upcoming/lost_mine_of_phandelver.txt
new file mode 100644
index 00000000000..145e9122755
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/lost_mine_of_phandelver.txt
@@ -0,0 +1,13 @@
+Name:Lost Mine of Phandelver
+ManaCost:no cost
+Types:Dungeon
+K:Dungeon:DBEntrance,DBGoblinLair,DBMineTunnels,DBStoreroom,DBDarkPool,DBFungiCavern,DBTempleDumathoin
+SVar:DBEntrance:DB$ Scry | ScryNum$ 1 | RoomName$ Cave Entrance | SpellDescription$ Scry 1. | NextRoom$ DBGoblinLair,DBMineTunnels
+SVar:DBGoblinLair:DB$ Token | TokenScript$ r_1_1_goblin | TokenOwner$ You | RoomName$ Goblin Lair | SpellDescription$ Create a 1/1 red Goblin creature token. | NextRoom$ DBStoreroom,DBDarkPool
+SVar:DBMineTunnels:DB$ Token | TokenScript$ c_a_treasure_sac | TokenOwner$ You | RoomName$ Mine Tunnels | SpellDescription$ Create a Treasure token. | NextRoom$ DBDarkPool,DBFungiCavern
+SVar:DBStoreroom:DB$ PutCounter | ValidTgts$ Creature | CounterType$ P1P1 | CounterType$ P1P1 | CounterNum$ 1 | RoomName$ Storeroom | SpellDescription$ Put a +1/+1 counter on target creature. | NextRoom$ DBTempleDumathoin
+SVar:DBDarkPool:DB$ LoseLife | Defined$ Player.Opponent | LifeAmount$ 1 | SubAbility$ DBGainLife | RoomName$ Dark Pool | SpellDescription$ Each opponent loses 1 life and you gain 1 life. | NextRoom$ DBTempleDumathoin
+SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1
+SVar:DBFungiCavern:DB$ Pump | ValidTgts$ Creature | NumAtt$ -4 | Duration$ UntilYourNextTurn | IsCurse$ True | RoomName$ Fungi Cavern | SpellDescription$ Target creature gets -4/-0 until your next turn. | NextRoom$ DBTempleDumathoin
+SVar:DBTempleDumathoin:DB$ Draw | Defined$ You | NumCards$ 1 | RoomName$ Temple of Dumathoin | SpellDescription$ Draw a card.
+Oracle:(1) Cave Entrance — Scry 1. (→ 2a or 2b)\n(2a) Goblin Lair — Create a 1/1 red Goblin creature token. (→ 3a or 3b)\n(2b) Mine Tunnels — Create a Treasure token. (→ 3b or 3c)\n(3a) Storeroom — Put a +1/+1 counter on target creature. (→ 4)\n(3b) Dark Pool — Each opponent loses 1 life and you gain 1 life. (→ 4)\n(3c) Fungi Cavern — Target creature gets -4/-0 until your next turn. (→ 4)\n(4) Temple of Dumathoin — Draw a card.
diff --git a/forge-gui/res/cardsfolder/upcoming/nadaar_selfless_paladin.txt b/forge-gui/res/cardsfolder/upcoming/nadaar_selfless_paladin.txt
new file mode 100644
index 00000000000..19647258fd0
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/nadaar_selfless_paladin.txt
@@ -0,0 +1,11 @@
+Name:Nadaar, Selfless Paladin
+ManaCost:2 W
+Types:Legendary Creature Dragon Knight
+PT:3/3
+K:Vigilance
+T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBVenture | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, venture into the dungeon. (Enter the first room or advance to the next room.)
+T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ DBVenture | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, venture into the dungeon. (Enter the first room or advance to the next room.)
+SVar:DBVenture:DB$ Venture | Defined$ You
+S:Mode$ Continuous | Affected$ Creature.YouCtrl+Other | AddPower$ 1 | AddToughness$ 1 | CheckSVar$ X | SVarCompare$ GE1 | Description$ Other creatures you control get +1/+1 as long as you've completed a dungeon.
+SVar:X:PlayerCountPropertyYou$DungeonsCompleted
+Oracle:Vigilance\nWhenever Nadaar, Selfless Paladin enters the battlefield or attacks, venture into the dungeon. (Enter the first room or advance to the next room.)\nOther creatures you control get +1/+1 as long as you've completed a dungeon.
diff --git a/forge-gui/res/cardsfolder/upcoming/shortcut_seeker.txt b/forge-gui/res/cardsfolder/upcoming/shortcut_seeker.txt
new file mode 100644
index 00000000000..3eda6789187
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/shortcut_seeker.txt
@@ -0,0 +1,7 @@
+Name:Shortcut Seeker
+ManaCost:3 U
+Types:Creature Human Rogue
+PT:2/5
+T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ DBVenture | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, venture into the dungeon. (Enter the first room or advance to the next room.)
+SVar:DBVenture:DB$ Venture | Defined$ You
+Oracle:Whenever Shortcut Seeker deals combat damage to a player, venture into the dungeon. (Enter the first room or advance to the next room.)
diff --git a/forge-gui/res/cardsfolder/upcoming/tomb_of_annihilation.txt b/forge-gui/res/cardsfolder/upcoming/tomb_of_annihilation.txt
new file mode 100644
index 00000000000..3185bbc1abe
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/tomb_of_annihilation.txt
@@ -0,0 +1,15 @@
+Name:Tomb of Annihilation
+ManaCost:no cost
+Types:Dungeon
+K:Dungeon:DBEntry,DBVeilsOfFear,DBOubliette,DBSandfallCell,DBCradleDeathGod
+SVar:DBEntry:DB$ LoseLife | Defined$ Player | LifeAmount$ 1 | RoomName$ Trapped Entry | SpellDescription$ Each player loses 1 life. | NextRoom$ DBVeilsOfFear,DBOubliette
+SVar:DBVeilsOfFear:DB$ RepeatEach | RepeatSubAbility$ DBLoseLife1 | RepeatPlayers$ Player | RoomName$ Veils of Fear | SpellDescription$ Each player loses 2 life unless they discard a card. | NextRoom$ DBSandfallCell
+SVar:DBLoseLife1:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ 2 | UnlessCost$ Discard<1/Card> | UnlessPayer$ Remembered
+SVar:DBOubliette:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | SubAbility$ DBSacArtifact | RoomName$ Oubliette | SpellDescription$ Discard a card and sacrifice an artifact, a creature, and a land. | NextRoom$ DBCradleDeathGod
+SVar:DBSacArtifact:DB$ Sacrifice | Defined$ You | SacValid$ Artifact | SubAbility$ DBSacCreature
+SVar:DBSacCreature:DB$ Sacrifice | Defined$ You | SacValid$ Creature | SubAbility$ DBSacLand
+SVar:DBSacLand:DB$ Sacrifice | Defined$ You | SacValid$ Land
+SVar:DBSandfallCell:DB$ RepeatEach | RepeatSubAbility$ DBLoseLife2 | RepeatPlayers$ Player | RoomName$ Sandfall Cell | SpellDescription$ Each player loses 2 life unless they sacrifice an artifact, a creature, or a land. | NextRoom$ DBCradleDeathGod
+SVar:DBLoseLife2:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ 2 | UnlessCost$ Sac<1/Artifact;Creature;Land/an artifact, a creature, or a land> | UnlessPayer$ Remembered
+SVar:DBCradleDeathGod:DB$ Token | TokenScript$ b_4_4_the_atropal_deathtouch | TokenOwner$ You | RoomName$ Cradle of the Death God | SpellDescription$ Create The Atropal, a legendary 4/4 black God Horror creature token with deathtouch.
+Oracle:(1) Trapped Entry — Each player loses 1 life. (→ 2a or 2b)\n(2a) Veils of Fear — Each player loses 2 life unless they discard a card. (→ 3)\n(2b) Oubliette — Discard a card and sacrifice an artifact, a creature, and a land. (→ 4)\n(3) Sandfall Cell — Each player loses 2 life unless they sacrifice an artifact, a creature, or a land. (→ 4)\n(4) Cradle of the Death God — Create The Atropal, a legendary 4/4 black God Horror creature token with deathtouch.
diff --git a/forge-gui/res/lists/TypeLists.txt b/forge-gui/res/lists/TypeLists.txt
index 4956e4953e5..7129eee7b5c 100644
--- a/forge-gui/res/lists/TypeLists.txt
+++ b/forge-gui/res/lists/TypeLists.txt
@@ -324,7 +324,8 @@ Dihada
Domri
Dovin
Duck
-Dungeon-Master
+Dungeon Master
+Ellywick
Elspeth
Estrid
Freyalise
diff --git a/forge-gui/res/tokenscripts/b_1_1_skeleton.txt b/forge-gui/res/tokenscripts/b_1_1_skeleton.txt
new file mode 100644
index 00000000000..fccdbf113f7
--- /dev/null
+++ b/forge-gui/res/tokenscripts/b_1_1_skeleton.txt
@@ -0,0 +1,6 @@
+Name:Skeleton
+ManaCost:no cost
+Types:Creature Skeleton
+Colors:black
+PT:1/1
+Oracle:
diff --git a/forge-gui/res/tokenscripts/b_4_4_the_atropal_deathtouch.txt b/forge-gui/res/tokenscripts/b_4_4_the_atropal_deathtouch.txt
new file mode 100644
index 00000000000..acb205645ec
--- /dev/null
+++ b/forge-gui/res/tokenscripts/b_4_4_the_atropal_deathtouch.txt
@@ -0,0 +1,7 @@
+Name:The Atropal
+ManaCost:no cost
+Types:Legendary Creature God Horror
+Colors:black
+PT:4/4
+K:Deathtouch
+Oracle:Deathtouch
diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java
index 1d3df5bad43..3e4742c1e85 100644
--- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java
+++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java
@@ -126,7 +126,7 @@ public class CardDetailUtil {
public static String getCurrentColors(final CardStateView c) {
ColorSet curColors = c.getColors();
- String strCurColors = "";
+ String strCurColors = "";
if (curColors.hasWhite()) { strCurColors += "{W}"; }
if (curColors.hasBlue()) { strCurColors += "{U}"; }
@@ -140,7 +140,7 @@ public class CardDetailUtil {
return strCurColors;
}
-
+
public static DetailColors getRarityColor(final CardRarity rarity) {
switch (rarity) {
case Uncommon:
@@ -251,7 +251,7 @@ public class CardDetailUtil {
origIdent = origCard != null ? getCurrentColors(origCard.isFaceDown() ? CardView.get(origCard).getState(false) : CardView.get(origCard).getCurrentState()) : "";
} catch(Exception ex) {
System.err.println("Unexpected behavior: card " + card.getName() + "[" + card.getId() + "] tripped an exception when trying to process current card colors.");
- }
+ }
isChanged = !curColors.equals(origIdent);
}
@@ -477,6 +477,15 @@ public class CardDetailUtil {
area.append(")");
}
+ // dungeon room
+ if (card.getCurrentRoom() != null && !card.getCurrentRoom().isEmpty()) {
+ if (area.length() != 0) {
+ area.append("\n");
+ }
+ area.append("(In room: ");
+ area.append(card.getCurrentRoom()).append(")");
+ }
+
// a card has something attached to it
if (card.hasCardAttachments()) {
if (area.length() != 0) {