diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java
index 514af661290..90e792fe8a2 100644
--- a/forge-core/src/main/java/forge/deck/DeckFormat.java
+++ b/forge-core/src/main/java/forge/deck/DeckFormat.java
@@ -336,7 +336,10 @@ public enum DeckFormat {
return TextUtil.concatWithSpace("contains a Custom Card:", cp.getKey(), "\nPlease Enable Custom Cards in Forge Preferences to use this deck.");
// Might cause issues since it ignores "Special" Cards
if (simpleCard == null) {
- return TextUtil.concatWithSpace("contains the nonexisting card", cp.getKey());
+ simpleCard = StaticData.instance().getVariantCards().getCard(cp.getKey());
+ if (simpleCard == null) {
+ return TextUtil.concatWithSpace("contains the nonexisting card", cp.getKey());
+ }
}
if (canHaveAnyNumberOf(simpleCard)) {
diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java
index d7707dc482e..baea4e6b655 100644
--- a/forge-game/src/main/java/forge/game/card/CardProperty.java
+++ b/forge-game/src/main/java/forge/game/card/CardProperty.java
@@ -9,7 +9,10 @@ import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaCost;
import forge.card.mana.ManaCostShard;
-import forge.game.*;
+import forge.game.CardTraitBase;
+import forge.game.EvenOdd;
+import forge.game.Game;
+import forge.game.GameEntity;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.card.CardPredicates.Presets;
@@ -2094,6 +2097,16 @@ public class CardProperty {
}
}
return false;
+ } else if (property.equals("NotedColor")) {
+ String colors = sourceController.getDraftNotes().get(spellAbility.getHostCard().getName());
+ if (colors == null) {
+ return false;
+ }
+ return (colors.contains("white") && card.getColor().hasWhite()) ||
+ (colors.contains("blue") && card.getColor().hasBlue()) ||
+ (colors.contains("black") && card.getColor().hasBlack()) ||
+ (colors.contains("red") && card.getColor().hasRed()) ||
+ (colors.contains("green") && card.getColor().hasGreen());
} else if (property.startsWith("Triggered")) {
if (spellAbility instanceof SpellAbility) {
final String key = property.substring(9);
diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
index 58a1f6b5586..f53dbdc2d27 100644
--- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
+++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java
@@ -17,13 +17,7 @@
*/
package forge.game.spellability;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.lang3.StringUtils;
-
import com.google.common.collect.Lists;
-
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
@@ -48,9 +42,13 @@ import forge.game.replacement.ReplacementType;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
-import forge.game.zone.ZoneType;
import forge.game.zone.Zone;
+import forge.game.zone.ZoneType;
import forge.util.TextUtil;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.List;
+import java.util.Map;
/**
*
@@ -524,6 +522,9 @@ public class AbilityManaPart implements java.io.Serializable {
return getComboColors(sa);
}
String produced = this.getOrigProduced();
+ if (produced.contains("NotedColor")) {
+ produced = produced.replace("NotedColor", sa.getActivatingPlayer().getDraftNotes().get(""));
+ }
if (produced.contains("Chosen")) {
produced = produced.replace("Chosen", this.getChosenColor(sa));
}
@@ -663,6 +664,23 @@ public class AbilityManaPart implements java.io.Serializable {
if (origProduced.contains("Chosen")) {
origProduced = origProduced.replace("Chosen", getChosenColor(sa));
}
+ if (origProduced.contains("NotedColors")) {
+ String colors = sa.getActivatingPlayer().getDraftNotes().get(sa.getHostCard().getName());
+ if (colors == null) {
+ return "";
+ }
+ // Colors here is an comma separated color list, potentially with duplicates
+ // We need to remove duplicates and convert to single letters
+ StringBuilder sb = new StringBuilder();
+ for (String color : colors.split(",")) {
+ String shortColor = MagicColor.toShortString(color);
+ if (sb.indexOf(shortColor) == -1) {
+ sb.append(shortColor).append(" ");
+ }
+ }
+ origProduced = origProduced.replace("NotedColors", sb.toString().trim());
+ }
+
if (!origProduced.contains("ColorIdentity")) {
return TextUtil.fastReplace(origProduced, "Combo ", "");
}
diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorDraftingProcess.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorDraftingProcess.java
index 79cf6bac656..578f61ca59b 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorDraftingProcess.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorDraftingProcess.java
@@ -17,9 +17,6 @@
*/
package forge.screens.deckeditor.controllers;
-import java.util.HashSet;
-import java.util.Map.Entry;
-
import forge.Singletons;
import forge.deck.Deck;
import forge.deck.DeckGroup;
@@ -43,6 +40,9 @@ import forge.toolbox.FOptionPane;
import forge.util.ItemPool;
import forge.util.Localizer;
+import java.util.HashSet;
+import java.util.Map.Entry;
+
/**
* Updates the deck editor UI as necessary draft selection mode.
*
@@ -132,6 +132,9 @@ public class CEditorDraftingProcess extends ACEditorBase i
this.showChoices(pool);
}
else {
+ // TODO Deal Broker
+ // Offer trades before saving
+
this.saveDraft();
}
}
diff --git a/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java b/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java
index a719931e293..80137916497 100644
--- a/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java
+++ b/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java
@@ -1,19 +1,18 @@
package forge;
-import java.util.List;
-
-import forge.gamemodes.limited.IDraftLog;
-import forge.gamemodes.limited.LimitedPlayer;
-import org.testng.annotations.Test;
-
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.game.card.Card;
import forge.gamemodes.limited.IBoosterDraft;
+import forge.gamemodes.limited.IDraftLog;
+import forge.gamemodes.limited.LimitedPlayer;
import forge.item.PaperCard;
import forge.item.SealedProduct;
import forge.item.generation.BoosterGenerator;
import forge.model.FModel;
+import org.testng.annotations.Test;
+
+import java.util.List;
/**
*
@@ -91,4 +90,9 @@ public class BoosterDraftTest implements IBoosterDraft {
public IDraftLog getDraftLog() {
return null;
}
+
+ @Override
+ public LimitedPlayer getNeighbor(LimitedPlayer p, boolean left) {
+ return null;
+ }
}
diff --git a/forge-gui/res/cardsfolder/a/animus_of_predation.txt b/forge-gui/res/cardsfolder/a/animus_of_predation.txt
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/forge-gui/res/cardsfolder/p/paliano_the_high_city.txt b/forge-gui/res/cardsfolder/p/paliano_the_high_city.txt
new file mode 100644
index 00000000000..e5b5c8e3852
--- /dev/null
+++ b/forge-gui/res/cardsfolder/p/paliano_the_high_city.txt
@@ -0,0 +1,8 @@
+Name:Paliano, the High City
+Types:Legendary Land
+Draft:Reveal CARDNAME as you draft it.
+Draft:As you draft CARDNAME, the player to your right chooses a color, you choose another color, then the player to your left chooses a third color.
+A:AB$ Mana | Cost$ T | Produced$ Combo NotedColors | Condition$ Add one mana of any color chosen as you drafted cards named CARDNAME.
+Oracle:Reveal CARDNAME as you draft it. As you draft CARDNAME, the player to your right chooses a color, you choose another color, then the player to your left chooses a third color.\n{T}: Add one mana of any color chosen as you drafted cards named Paliano, the High City.
+
+
diff --git a/forge-gui/res/cardsfolder/r/regicide.txt b/forge-gui/res/cardsfolder/r/regicide.txt
new file mode 100644
index 00000000000..6085e8f5fac
--- /dev/null
+++ b/forge-gui/res/cardsfolder/r/regicide.txt
@@ -0,0 +1,7 @@
+Name:Regicide
+ManaCost:B
+Types:Instant
+Draft:Reveal CARDNAME as you draft it.
+Draft:As you draft CARDNAME, the player to your right chooses a color, you choose another color, then the player to your left chooses a third color.
+A:SP$ Destroy | ValidTgts$ Creature.NotedColor | SpellDescription$ Destroy target creature that’s one or more of the colors chosen as you drafted cards named CARDNAME.
+Oracle:Reveal Regicide as you draft it. The player to your right chooses a color, you choose another color, then the player to your left chooses a third color.\nDestroy target creature that’s one or more of the colors chosen as you drafted cards named Regicide.
\ No newline at end of file
diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java
index d36b55b2713..7bf8c7309e1 100644
--- a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java
+++ b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java
@@ -17,22 +17,9 @@
*/
package forge.gamemodes.limited;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Stack;
-import java.util.TreeMap;
-
-import org.apache.commons.lang3.ArrayUtils;
-
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
-
import forge.StaticData;
import forge.card.CardEdition;
import forge.deck.CardPool;
@@ -53,6 +40,10 @@ import forge.util.ItemPool;
import forge.util.Localizer;
import forge.util.TextUtil;
import forge.util.storage.IStorage;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.io.File;
+import java.util.*;
/**
* Booster Draft Format.
@@ -294,6 +285,11 @@ public class BoosterDraft implements IBoosterDraft {
return draftLog;
}
+ @Override
+ public LimitedPlayer getNeighbor(LimitedPlayer player, boolean left) {
+ return players.get((player.order + (left ? -1 : 1) + N_PLAYERS) % N_PLAYERS);
+ }
+
private void setupCustomDraft(final CustomLimited draft) {
final ItemPool dPool = draft.getCardPool();
if (dPool == null) {
@@ -451,6 +447,7 @@ public class BoosterDraft implements IBoosterDraft {
// Loop through players 1-7 to draft their current pack
for (int i = 1; i < N_PLAYERS; i++) {
LimitedPlayer pl = this.players.get(i);
+ // TODO Agent of Acquisitions activation to loop the entire pack?
pl.draftCard(pl.chooseCard());
}
}
@@ -483,6 +480,8 @@ public class BoosterDraft implements IBoosterDraft {
recordDraftPick(thisBooster, c);
+ // TODO Agent of Acquisitions activation to loop the entire pack?
+
this.localPlayer.draftCard(c);
this.currentBoosterPick++;
this.passPacks();
diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/DeckColors.java b/forge-gui/src/main/java/forge/gamemodes/limited/DeckColors.java
index d831b1ee7e9..8fd53913072 100644
--- a/forge-gui/src/main/java/forge/gamemodes/limited/DeckColors.java
+++ b/forge-gui/src/main/java/forge/gamemodes/limited/DeckColors.java
@@ -17,12 +17,12 @@
*/
package forge.gamemodes.limited;
-import java.util.List;
-
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.item.IPaperCard;
+import java.util.List;
+
public class DeckColors {
protected ColorSet chosen;
@@ -30,6 +30,13 @@ public class DeckColors {
public int MAX_COLORS = 2;
+ DeckColors() {}
+
+ DeckColors(int max_col) {
+ // If we want to draft decks that are more than 2 colors, we can change the MAX_COLORS value here.
+ MAX_COLORS = max_col;
+ }
+
public ColorSet getChosenColors() {
if (null == chosen) {
chosen = ColorSet.fromMask(colorMask);
diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/IBoosterDraft.java b/forge-gui/src/main/java/forge/gamemodes/limited/IBoosterDraft.java
index 43b1a0ee70a..ec7c64cd851 100644
--- a/forge-gui/src/main/java/forge/gamemodes/limited/IBoosterDraft.java
+++ b/forge-gui/src/main/java/forge/gamemodes/limited/IBoosterDraft.java
@@ -46,4 +46,5 @@ public interface IBoosterDraft {
void setLogEntry(IDraftLog draftingProcess);
IDraftLog getDraftLog();
+ LimitedPlayer getNeighbor(LimitedPlayer p, boolean left);
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java
index 0c0d7cbd627..e89f85ee849 100644
--- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java
+++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java
@@ -2,9 +2,11 @@ package forge.gamemodes.limited;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import forge.card.MagicColor;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
+import forge.gui.util.SGuiChoose;
import forge.item.PaperCard;
import forge.util.TextUtil;
@@ -20,22 +22,23 @@ public class LimitedPlayer {
protected Queue> packQueue;
protected Queue> unopenedPacks;
+ protected List removedFromCardPool = new ArrayList<>();
private static final int CantDraftThisRound = 1;
private static final int SpyNextCardDrafted = 1 << 1;
private static final int ReceiveLastCard = 1 << 2;
private static final int CanRemoveAfterDraft = 1 << 3;
private static final int CanTradeAfterDraft = 1 << 4;
+ private static final int AnimusRemoveFromPool = 1 << 5;
private static final int MAXFLAGS = CantDraftThisRound | ReceiveLastCard | CanRemoveAfterDraft | SpyNextCardDrafted
- | CanTradeAfterDraft;
+ | CanTradeAfterDraft | AnimusRemoveFromPool;
- private final int playerFlags = 0;
+ private int playerFlags = 0;
private final List faceUp = Lists.newArrayList();
private final List revealed = Lists.newArrayList();
private final Map> noted = new HashMap<>();
- //private Map powers = new HashMap<>();
IBoosterDraft draft = null;
@@ -61,7 +64,9 @@ public class LimitedPlayer {
}
public PaperCard chooseCard() {
- // A basic LimitedPlayer chooses cards via the UI instead of this function
+ // A non-AI LimitedPlayer chooses cards via the UI instead of this function
+ // TODO Archdemon of Paliano random draft while active
+
return null;
}
@@ -80,8 +85,15 @@ public class LimitedPlayer {
chooseFrom.remove(bestPick);
- CardPool pool = deck.getOrCreate(section);
- pool.add(bestPick);
+ if ((playerFlags & AnimusRemoveFromPool) == AnimusRemoveFromPool &&
+ removeWithAnimus(bestPick)) {
+ removedFromCardPool.add(bestPick);
+ return true;
+ } else {
+ CardPool pool = deck.getOrCreate(section);
+ pool.add(bestPick);
+ }
+
draftedThisRound++;
if (bestPick.getRules().getMainPart().getDraftActions() == null) {
@@ -92,22 +104,73 @@ public class LimitedPlayer {
Iterable draftActions = bestPick.getRules().getMainPart().getDraftActions();
if (Iterables.contains(draftActions, "Reveal CARDNAME as you draft it.")) {
revealed.add(bestPick);
+ showRevealedCard(bestPick);
if (Iterables.contains(draftActions, "Note how many cards you've drafted this draft round, including CARDNAME.")) {
List note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList());
note.add(String.valueOf(draftedThisRound));
addLog(name() + " revealed " + bestPick.getName() + " and noted " + draftedThisRound + " cards drafted this round.");
- } else {
+ } else if (Iterables.contains(draftActions, "As you draft CARDNAME, the player to your right chooses a color, you choose another color, then the player to your left chooses a third color.")) {
+ List chosenColors = new ArrayList();
+
+ LimitedPlayer leftPlayer = draft.getNeighbor(this, true);
+ LimitedPlayer rightPlayer = draft.getNeighbor(this, false);
+ List availableColors = new ArrayList(MagicColor.Constant.ONLY_COLORS);
+
+ String c = rightPlayer.chooseColor(availableColors, this, bestPick.getName());
+ chosenColors.add(c);
+ availableColors.remove(c);
+
+ c = this.chooseColor(availableColors, this, bestPick.getName());
+ chosenColors.add(c);
+ availableColors.remove(c);
+
+ c = leftPlayer.chooseColor(availableColors, this, bestPick.getName());
+ chosenColors.add(c);
+ availableColors.remove(c);
+
+ List note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList());
+ note.add(String.join(",", chosenColors));
+
+ addLog(name() + " revealed " + bestPick.getName() + " and noted " + chosenColors + " chosen.");
+ }
+ else {
addLog(name() + " revealed " + bestPick.getName() + " as they drafted it.");
}
}
+ if (Iterables.contains(draftActions, "Draft CARDNAME face up.")) {
+ faceUp.add(bestPick);
+ addLog(name() + " drafted " + bestPick.getName() + " face up.");
+ showRevealedCard(bestPick);
- // Colors
- // TODO Note Paliano, the High City
- // TODO Note Regicide
- // TODO Note Paliano Vanguard
- // TODO Note Aether Searcher (for the next card)
+ // TODO Noble Banneret
+ // TODO Paliano Vanguard
+ // As you draft a VALID, you may Note its [name/type/], and turn this face down
+
+
+ // TODO Animus of Predation
+ if (Iterables.contains(draftActions, "As you draft a card, you may remove it from the draft face up. (It isn’t in your card pool.)")) {
+ playerFlags |= AnimusRemoveFromPool;
+ }
+ // As you draft a VALID, you may remove it face up. (It's no longer in your draft pool)
+ // TODO We need a deck section that's not your sideboard but is your cardpool?
+ // Keyword absorption: If creature is absorbed, it gains all the abilities of the creature it absorbed. This includes
+ // flying, first strike, double strike, deathtouch, haste, hexproof, indestructible, lifelink, menace, reach, and vigilance.
+ }
+
+ // Note who passed it to you. (Either player before you in draft passing order except if you receive the last card
+ // TODO Cogwork Tracker
+
+
+ // Note next card on this card
+ // TODO Aether Searcher (for the next card)
+
+ // Peek at next card from this pack
+ // TODO Cogwork Spy
+
+ // TODO Lore Seeker
+ // This adds a pack and MIGHT screw up all of our assumptions about pack passing. Do this last probably
return true;
}
@@ -141,6 +204,14 @@ public class LimitedPlayer {
packQueue.add(pack);
}
+ protected String chooseColor(List colors, LimitedPlayer player, String title) {
+ return SGuiChoose.one(player.name() + " drafted " + title + ": Choose a color", colors);
+ }
+
+ protected boolean removeWithAnimus(PaperCard bestPick) {
+ return SGuiChoose.one("Remove this " + bestPick + " from the draft for ANnimus of Predation?", Lists.newArrayList("Yes", "No")).equals("Yes");
+ }
+
public String name() {
if (this instanceof LimitedPlayerAI) {
return "Player[" + order + "]";
@@ -149,41 +220,15 @@ public class LimitedPlayer {
return "You";
}
+ public void showRevealedCard(PaperCard pick) {
+ // TODO Show the revealed card in the CardDetailPanel
+
+ }
+
/*
public void addSingleBoosterPack(boolean random) {
// TODO Lore Seeker
- // Generate booster pack then, "receive" that pack
- }
-
- public boolean activatePower(DraftPower power) {
- if (!powers.containsKey(power)) {
- return false;
- }
-
- int i = (int)powers.get(power);
- if (i == 1) {
- powers.remove(power);
- } else {
- powers.put(power, i-1);
- }
-
- power.activate(this);
-
-
- return true;
- }
-
- public boolean noteObject(String cardName, Object notedObj) {
- // Returns boolean based on creation of new mapped param
- boolean alreadyContained = noted.containsKey(cardName);
-
- if (alreadyContained) {
- noted.get(cardName).add(notedObj);
- } else {
- noted.put(cardName, Lists.newArrayList(notedObj));
- }
-
- return !alreadyContained;
+ // Generate booster pack then, insert it "before" the pack we're currently drafting from
}
*/
}
diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java
index a0ad94c8fed..278f58bce55 100644
--- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java
+++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java
@@ -1,7 +1,5 @@
package forge.gamemodes.limited;
-import java.util.List;
-
import forge.card.ColorSet;
import forge.deck.CardPool;
import forge.deck.Deck;
@@ -9,6 +7,9 @@ import forge.deck.DeckSection;
import forge.item.PaperCard;
import forge.localinstance.properties.ForgePreferences;
+import java.util.Collections;
+import java.util.List;
+
public class LimitedPlayerAI extends LimitedPlayer {
protected DeckColors deckCols;
@@ -33,6 +34,9 @@ public class LimitedPlayerAI extends LimitedPlayer {
System.out.println("Player[" + order + "] pack: " + chooseFrom.toString());
}
+ // TODO Archdemon of Paliano random draft while active
+
+
final ColorSet chosenColors = deckCols.getChosenColors();
final boolean canAddMoreColors = deckCols.canChoseMoreColors();
@@ -54,4 +58,25 @@ public class LimitedPlayerAI extends LimitedPlayer {
CardPool section = deck.getOrCreate(DeckSection.Sideboard);
return new BoosterDeckBuilder(section.toFlatList(), deckCols).buildDeck(landSetCode);
}
+
+ @Override
+ protected String chooseColor(List colors, LimitedPlayer player, String title) {
+ if (player.equals(this)) {
+ // For Paliano, choose one of my colors
+ // For Regicie, random is fine?
+ } else {
+ // For Paliano, if player has revealed anything, try to avoid that color
+ // For Regicide, don't choose one of my colors
+ }
+ Collections.shuffle(colors);
+ return colors.get(0);
+ }
+
+ @Override
+ protected boolean removeWithAnimus(PaperCard bestPick) {
+ // TODO Animus of Predation logic
+ // Feel free to remove any cards that we won't play and can give us a bonus
+ // We should verify we don't already have the keyword bonus that card would grant
+ return false;
+ }
}