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 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 clsEf) { this(clsEf, true); } ApiType(Class 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) {