mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-14 17:58:01 +00:00
Implement Dungeon mechanism and related spoiled cards
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -598,6 +598,7 @@ public final class CardRulesPredicates {
|
||||
public static final Predicate<CardRules> IS_SCHEME = CardRulesPredicates.coreType(true, CardType.CoreType.Scheme);
|
||||
public static final Predicate<CardRules> IS_VANGUARD = CardRulesPredicates.coreType(true, CardType.CoreType.Vanguard);
|
||||
public static final Predicate<CardRules> IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy);
|
||||
public static final Predicate<CardRules> IS_DUNGEON = CardRulesPredicates.coreType(true, CardType.CoreType.Dungeon);
|
||||
public static final Predicate<CardRules> IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land);
|
||||
public static final Predicate<CardRules> CAN_BE_BRAWL_COMMANDER = Predicates.and(Presets.IS_LEGENDARY,
|
||||
Predicates.or(Presets.IS_CREATURE, Presets.IS_PLANESWALKER));
|
||||
|
||||
@@ -57,6 +57,7 @@ public final class CardType implements Comparable<CardType>, 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<CardType>, 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<CardType>, 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;
|
||||
}
|
||||
|
||||
@@ -44,5 +44,6 @@ public interface CardTypeView extends Iterable<String>, Serializable {
|
||||
boolean isPhenomenon();
|
||||
boolean isEmblem();
|
||||
boolean isTribal();
|
||||
boolean isDungeon();
|
||||
CardTypeView getTypeWithChanges(Iterable<CardChangedType> changedCardTypes);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<AbilityKey, Object> runParams = AbilityKey.mapFromCard(dungeon);
|
||||
runParams.put(AbilityKey.Player, player);
|
||||
game.getTriggerHandler().runTrigger(TriggerType.DungeonCompleted, runParams, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ public enum AbilityKey {
|
||||
ReplacementResult("ReplacementResult"),
|
||||
ReplacementResultMap("ReplacementResultMap"),
|
||||
Result("Result"),
|
||||
RoomName("RoomName"),
|
||||
Scheme("Scheme"),
|
||||
Source("Source"),
|
||||
Sources("Sources"),
|
||||
|
||||
@@ -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<Card> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
@@ -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<PaperCard> dungeonCards = StaticData.instance().getVariantCards().getAllCards(
|
||||
Predicates.compose(CardRulesPredicates.Presets.IS_DUNGEON, PaperCard.FN_GET_RULES));
|
||||
List<ICardFace> 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<SpellAbility> 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<AbilityKey, Object> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -302,6 +302,7 @@ public class Card extends GameEntity implements Comparable<Card>, 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<Card>, 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<Card>, 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 ");
|
||||
|
||||
@@ -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<String> abs = Arrays.asList(keyword.substring("Dungeon:".length()).split(","));
|
||||
final Map<String, SpellAbility> 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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -194,6 +194,7 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
private boolean tappedLandForManaThisTurn = false;
|
||||
private int attackersDeclaredThisTurn = 0;
|
||||
private PlayerCollection attackedOpponentsThisTurn = new PlayerCollection();
|
||||
private List<Card> completedDungeons = new ArrayList<>();
|
||||
|
||||
private final Map<ZoneType, PlayerZone> zones = Maps.newEnumMap(ZoneType.class);
|
||||
private final Map<Long, Integer> adjustLandPlays = Maps.newHashMap();
|
||||
@@ -1947,6 +1948,13 @@ public class Player extends GameEntity implements Comparable<Player> {
|
||||
attackersDeclaredThisTurn = 0;
|
||||
}
|
||||
|
||||
public final List<Card> 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.");
|
||||
|
||||
@@ -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<String, String> params, final Card host, final boolean intrinsic) {
|
||||
super(params, host, intrinsic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean performTest(final Map<AbilityKey, Object> runParams) {
|
||||
if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Player))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setTriggeringObjects(final SpellAbility sa, Map<AbilityKey, Object> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<String, String> params, final Card host, final boolean intrinsic) {
|
||||
super(params, host, intrinsic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean performTest(final Map<AbilityKey, Object> 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<AbilityKey, Object> 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 "";
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
9
forge-gui/res/cardsfolder/upcoming/cloister_gargoyle.txt
Normal file
9
forge-gui/res/cardsfolder/upcoming/cloister_gargoyle.txt
Normal file
@@ -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.
|
||||
8
forge-gui/res/cardsfolder/upcoming/dungeon_crawler.txt
Normal file
8
forge-gui/res/cardsfolder/upcoming/dungeon_crawler.txt
Normal file
@@ -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.
|
||||
@@ -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.
|
||||
15
forge-gui/res/cardsfolder/upcoming/ellywick_tumblestrum.txt
Normal file
15
forge-gui/res/cardsfolder/upcoming/ellywick_tumblestrum.txt
Normal file
@@ -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."
|
||||
7
forge-gui/res/cardsfolder/upcoming/gloom_stalker.txt
Normal file
7
forge-gui/res/cardsfolder/upcoming/gloom_stalker.txt
Normal file
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
7
forge-gui/res/cardsfolder/upcoming/shortcut_seeker.txt
Normal file
7
forge-gui/res/cardsfolder/upcoming/shortcut_seeker.txt
Normal file
@@ -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.)
|
||||
15
forge-gui/res/cardsfolder/upcoming/tomb_of_annihilation.txt
Normal file
15
forge-gui/res/cardsfolder/upcoming/tomb_of_annihilation.txt
Normal file
@@ -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.
|
||||
@@ -324,7 +324,8 @@ Dihada
|
||||
Domri
|
||||
Dovin
|
||||
Duck
|
||||
Dungeon-Master
|
||||
Dungeon Master
|
||||
Ellywick
|
||||
Elspeth
|
||||
Estrid
|
||||
Freyalise
|
||||
|
||||
6
forge-gui/res/tokenscripts/b_1_1_skeleton.txt
Normal file
6
forge-gui/res/tokenscripts/b_1_1_skeleton.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Name:Skeleton
|
||||
ManaCost:no cost
|
||||
Types:Creature Skeleton
|
||||
Colors:black
|
||||
PT:1/1
|
||||
Oracle:
|
||||
@@ -0,0 +1,7 @@
|
||||
Name:The Atropal
|
||||
ManaCost:no cost
|
||||
Types:Legendary Creature God Horror
|
||||
Colors:black
|
||||
PT:4/4
|
||||
K:Deathtouch
|
||||
Oracle:Deathtouch
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user