From d4469ca7362629a4fe680f71a1448082ecc57800 Mon Sep 17 00:00:00 2001 From: Chris H Date: Sat, 22 Jun 2024 06:42:29 -0400 Subject: [PATCH] Add Spire Phantasm, Agent of Acquisitions, Cogwork Librarian, Leovold's Operative (#5437) * Next few Draft cards * Code review fixes * Add Spire Phantasm * Add Agent of Acquisitions, Cogwork Librarian, Leovold's Operative * Some small fixes * Getting closer * Now the UI is updating * Switch DraftPack to a delegate class * Fix agent of acquisitions when AI drafts it --- forge-core/src/main/java/forge/deck/Deck.java | 15 ++ .../java/forge/game/card/CardProperty.java | 3 + .../controllers/CEditorDraftingProcess.java | 11 +- .../src/test/java/forge/BoosterDraftTest.java | 9 +- .../cardsfolder/a/agent_of_acquisitions.txt | 7 + .../res/cardsfolder/c/cogwork_librarian.txt | 7 + .../res/cardsfolder/l/leovolds_operative.txt | 7 + .../res/cardsfolder/s/spire_phantasm.txt | 10 + .../editions/Conspiracy Take the Crown.txt | 1 + forge-gui/res/editions/Conspiracy.txt | 18 +- .../forge/gamemodes/limited/BoosterDraft.java | 41 +++- .../forge/gamemodes/limited/CardRanker.java | 17 +- .../forge/gamemodes/limited/DraftPack.java | 49 +++++ .../gamemodes/limited/IBoosterDraft.java | 4 +- .../gamemodes/limited/LimitedPlayer.java | 192 +++++++++++++++--- .../gamemodes/limited/LimitedPlayerAI.java | 51 ++++- 16 files changed, 384 insertions(+), 58 deletions(-) create mode 100644 forge-gui/res/cardsfolder/a/agent_of_acquisitions.txt create mode 100644 forge-gui/res/cardsfolder/c/cogwork_librarian.txt create mode 100644 forge-gui/res/cardsfolder/l/leovolds_operative.txt create mode 100644 forge-gui/res/cardsfolder/s/spire_phantasm.txt create mode 100644 forge-gui/src/main/java/forge/gamemodes/limited/DraftPack.java diff --git a/forge-core/src/main/java/forge/deck/Deck.java b/forge-core/src/main/java/forge/deck/Deck.java index 7c8fa7ed594..0f5e5e0fee4 100644 --- a/forge-core/src/main/java/forge/deck/Deck.java +++ b/forge-core/src/main/java/forge/deck/Deck.java @@ -174,6 +174,21 @@ public class Deck extends DeckBase implements Iterable kv : parts.entrySet()) { + CardPool pool = kv.getValue(); + for (Entry pc : pool) { + if (pc.getKey().getName().equalsIgnoreCase(name)) { + paperCard = pc.getKey(); + pool.remove(paperCard); + return paperCard; + } + } + } + return null; + } + // will return new if it was absent public CardPool getOrCreate(DeckSection deckSection) { CardPool p = get(deckSection); 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 f8423f91411..becfda819a0 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -2129,6 +2129,9 @@ public class CardProperty { List nameList = Lists.newArrayList(names.split(";")); return nameList.contains(card.getName()); + } else if (property.equals("NotedGuessPhantasm")) { + String names = sourceController.getDraftNotes().get("Spire Phantasm"); + return names != null && !names.isEmpty(); } else if (property.equals("NotedTypes")) { // Should Paliano Vanguard be hardcoded here or part of the property? String types = sourceController.getDraftNotes().get("Paliano Vanguard"); 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 30ddb32c78c..382104d339f 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 @@ -116,12 +116,21 @@ public class CEditorDraftingProcess extends ACEditorBase i // can only draft one at a time, regardless of the requested quantity PaperCard card = items.iterator().next().getKey(); + if (boosterDraft.getHumanPlayer().shouldSkipThisPick()) { + System.out.println(card + " not drafted because we're skipping this pick"); + showPackToDraft(); + return; + } + // Verify if card is in the activate pack? this.getDeckManager().addItem(card, 1); - // get next booster pack + // get next booster pack if we aren't picking again from this pack this.boosterDraft.setChoice(card); + showPackToDraft(); + } + protected void showPackToDraft() { boolean nextChoice = this.boosterDraft.hasNextChoice(); ItemPool pool = null; if (nextChoice) { diff --git a/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java b/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java index 0b35a78afc4..fcd0f5467c3 100644 --- a/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java +++ b/forge-gui-desktop/src/test/java/forge/BoosterDraftTest.java @@ -58,10 +58,15 @@ public class BoosterDraftTest implements IBoosterDraft { return result; } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + * + * @return + */ @Override - public void setChoice(final PaperCard c) { + public boolean setChoice(final PaperCard c) { System.out.println(c.getName()); + return false; } @Override diff --git a/forge-gui/res/cardsfolder/a/agent_of_acquisitions.txt b/forge-gui/res/cardsfolder/a/agent_of_acquisitions.txt new file mode 100644 index 00000000000..c288c3a14e7 --- /dev/null +++ b/forge-gui/res/cardsfolder/a/agent_of_acquisitions.txt @@ -0,0 +1,7 @@ +Name:Agent of Acquisitions +ManaCost:2 +Types:Artifact Creature Construct +PT:2/1 +Draft:Draft CARDNAME face up. +Draft:Instead of drafting a card from a booster pack, you may draft each card in that booster pack, one at a time. If you do, turn CARDNAME face down and you can’t draft cards for the rest of this draft round. (You may look at booster packs passed to you.) +Oracle:Draft Agent of Acquisitions face up.\nInstead of drafting a card from a booster pack, you may draft each card in that booster pack, one at a time. If you do, turn Agent of Acquisitions face down and you can’t draft cards for the rest of this draft round. (You may look at booster packs passed to you.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/c/cogwork_librarian.txt b/forge-gui/res/cardsfolder/c/cogwork_librarian.txt new file mode 100644 index 00000000000..4afd4ab6f88 --- /dev/null +++ b/forge-gui/res/cardsfolder/c/cogwork_librarian.txt @@ -0,0 +1,7 @@ +Name:Cogwork Librarian +ManaCost:4 +Types:Artifact Creature Construct +PT:3/3 +Draft:Draft CARDNAME face up. +Draft:As you draft a card, you may draft an additional card from that booster pack. If you do, put CARDNAME into that booster pack. +Oracle:Draft Cogwork Librarian face up.\nAs you draft a card, you may draft an additional card from that booster pack. If you do, put Cogwork Librarian into that booster pack. diff --git a/forge-gui/res/cardsfolder/l/leovolds_operative.txt b/forge-gui/res/cardsfolder/l/leovolds_operative.txt new file mode 100644 index 00000000000..bd6ef58acac --- /dev/null +++ b/forge-gui/res/cardsfolder/l/leovolds_operative.txt @@ -0,0 +1,7 @@ +Name:Leovold's Operative +ManaCost:2 G +Types:Creature Elf Rogue +PT:3/2 +Draft:Draft CARDNAME face up. +Draft:As you draft a card, you may draft an additional card from that booster pack. If you do, turn CARDNAME face down, then pass the next booster pack without drafting a card from it. (You may look at that booster pack.) +Oracle:Draft Leovold’s Operative face up.\n\nAs you draft a card, you may draft an additional card from that booster pack. If you do, turn Leovold’s Operative face down, then pass the next booster pack without drafting a card from it. (You may look at that booster pack.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/s/spire_phantasm.txt b/forge-gui/res/cardsfolder/s/spire_phantasm.txt new file mode 100644 index 00000000000..b22b9644c96 --- /dev/null +++ b/forge-gui/res/cardsfolder/s/spire_phantasm.txt @@ -0,0 +1,10 @@ +Name:Spire Phantasm +ManaCost:2 U U +Types:Creature Gargoyle Illusion +PT:3/2 +Draft:Reveal CARDNAME as you draft it. +Draft:The next time a player drafts a card from this booster pack, guess that card’s name. Then that player reveals the drafted card. +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self+NotedGuessPhantasm | Execute$ DrawACard | TriggerDescription$ When CARDNAME enters the battlefield, if you guessed correctly for a card named Spire Phantasm, draw a card. +SVar:DrawACard:DB$ Draw | Amount$ 1 +Oracle:Reveal Spire Phantasm as you draft it. The next time a player drafts a card from this booster pack, guess that card’s name. Then that player reveals the drafted card.\nFlying\nWhen Spire Phantasm enters the battlefield, if you guessed correctly for a card named Spire Phantasm, draw a card. \ No newline at end of file diff --git a/forge-gui/res/editions/Conspiracy Take the Crown.txt b/forge-gui/res/editions/Conspiracy Take the Crown.txt index 9479b4645d5..70f7fe7701c 100644 --- a/forge-gui/res/editions/Conspiracy Take the Crown.txt +++ b/forge-gui/res/editions/Conspiracy Take the Crown.txt @@ -248,6 +248,7 @@ Hold the Perimeter Hymn of the Wilds Illusionary Informant Incendiary Dissent +Leovold's Operative Natural Unity Noble Banneret Paliano Vanguard diff --git a/forge-gui/res/editions/Conspiracy.txt b/forge-gui/res/editions/Conspiracy.txt index e68f8526d6a..2c60fc063e6 100644 --- a/forge-gui/res/editions/Conspiracy.txt +++ b/forge-gui/res/editions/Conspiracy.txt @@ -221,31 +221,31 @@ ScryfallCode=CNS 210 R Reflecting Pool @Fred Fields [Draft Matters] -#1 Advantageous Proclamation|CNS +1 Advantageous Proclamation|CNS 1 Aether Searcher|CNS -#1 Agent of Acquisitions|CNS +1 Agent of Acquisitions|CNS #1 Backup Plan|CNS 1 Brago's Favor|CNS #1 Canal Dredger|CNS 1 Cogwork Grinder|CNS -#1 Cogwork Librarian|CNS +1 Cogwork Librarian|CNS 1 Cogwork Spy|CNS 1 Cogwork Tracker|CNS #1 Deal Broker|CNS -#1 Double Stroke|CNS +1 Double Stroke|CNS 1 Immediate Action|CNS -#1 Iterative Analysis|CNS +1 Iterative Analysis|CNS #1 Lore Seeker|CNS 1 Lurking Automaton|CNS 1 Muzzio's Preparations|CNS 1 Paliano, the High City|CNS -#1 Power Play|CNS +1 Power Play|CNS 1 Secret Summoning|CNS 1 Secrets of Paradise|CNS -#1 Sentinel Dispatch|CNS -#1 Unexpected Potential|CNS +1 Sentinel Dispatch|CNS +1 Unexpected Potential|CNS 1 Whispergear Sneak|CNS -#1 Worldknit|CNS +1 Worldknit|CNS [tokens] w_1_1_spirit_flying 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 cfa755c04d0..724c532d05e 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/BoosterDraft.java @@ -50,6 +50,7 @@ import java.util.*; */ public class BoosterDraft implements IBoosterDraft { + private static int nextId = 0; private static final int N_PLAYERS = 8; public static final String FILE_EXT = ".draft"; private final List players = new ArrayList<>(); @@ -371,9 +372,11 @@ public class BoosterDraft implements IBoosterDraft { } public void initializeBoosters() { + for (Supplier> boosterRound : this.product) { for (int i = 0; i < N_PLAYERS; i++) { - this.players.get(i).receiveUnopenedPack(boosterRound.get()); + DraftPack pack = new DraftPack(boosterRound.get(), nextId++); + this.players.get(i).receiveUnopenedPack(pack); } } startRound(); @@ -437,8 +440,10 @@ public class BoosterDraft implements IBoosterDraft { adjust = 0; } + // Do any players have a Canal Dredger? + for (int i = 0; i < N_PLAYERS; i++) { - List passingPack = this.players.get(i).passPack(); + DraftPack passingPack = this.players.get(i).passPack(); if (passingPack == null) continue; @@ -463,7 +468,17 @@ public class BoosterDraft implements IBoosterDraft { 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()); + + if (pl.shouldSkipThisPick()) { + continue; + } + + // Computer player has an empty pack or is passing the pack + Boolean passPack; + do { + // THe player holding onto the pack to draft an extra card... Do it now. + passPack = pl.draftCard(pl.chooseCard()); + } while (passPack != null && !passPack); } } @@ -473,6 +488,7 @@ public class BoosterDraft implements IBoosterDraft { @Override public boolean isRoundOver() { + // Really should check if all packs are empty, but this is a good enough approximation return packsInDraft == 0; } @@ -483,10 +499,12 @@ public class BoosterDraft implements IBoosterDraft { /** * {@inheritDoc} + * + * @return */ @Override - public void setChoice(final PaperCard c) { - final List thisBooster = this.localPlayer.nextChoice(); + public boolean setChoice(final PaperCard c) { + final DraftPack thisBooster = this.localPlayer.nextChoice(); if (!thisBooster.contains(c)) { throw new RuntimeException("BoosterDraft : setChoice() error - card not found - " + c @@ -495,11 +513,16 @@ public class BoosterDraft implements IBoosterDraft { recordDraftPick(thisBooster, c); - // TODO Agent of Acquisitions activation to loop the entire pack? - - this.localPlayer.draftCard(c); + boolean passPack = this.localPlayer.draftCard(c); + if (passPack) { + // Leovolds Operative and Cogwork Librarian get to draft an extra card.. How do we do that? + this.passPacks(); + } this.currentBoosterPick++; - this.passPacks(); + + // Return whether or not we passed, but that the UI always needs to refresh + // But returning might be useful for testing or other things? + return passPack; } private static String choosePackByPack(final List setz, int packs) { diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java b/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java index 2cf41228dd8..61f7baa244a 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java @@ -3,7 +3,6 @@ package forge.gamemodes.limited; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; - import forge.card.ColorSet; import forge.card.DeckHints; import forge.card.MagicColor; @@ -114,6 +113,22 @@ public class CardRanker { return cardScores; } + public static List getOrderedRawScores(List cards) { + List> cardScores = Lists.newArrayList(); + for(PaperCard card : cards) { + cardScores.add(Pair.of(getRawScore(card), card)); + } + + cardScores.sort(Collections.reverseOrder(new CardRankingComparator())); + + List rankedCards = new ArrayList<>(cardScores.size()); + for (Pair pair : cardScores) { + rankedCards.add(pair.getValue()); + } + + return rankedCards; + } + public static double getRawScore(PaperCard card) { Double rawScore; if (MagicColor.Constant.BASIC_LANDS.contains(card.getName())) { diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/DraftPack.java b/forge-gui/src/main/java/forge/gamemodes/limited/DraftPack.java new file mode 100644 index 00000000000..be55802268b --- /dev/null +++ b/forge-gui/src/main/java/forge/gamemodes/limited/DraftPack.java @@ -0,0 +1,49 @@ +package forge.gamemodes.limited; + +import com.google.common.collect.ForwardingList; +import forge.item.PaperCard; + +import java.util.AbstractMap; +import java.util.List; +import java.util.Map; + +public class DraftPack extends ForwardingList { + private final List cards; + private final int id; + private LimitedPlayer passedFrom; + private Map.Entry awaitingGuess; + + public DraftPack(List cards, int id) { + this.cards = cards; + this.id = id; + } + + public int getId() { + return id; + } + + public LimitedPlayer getPassedFrom() { + return passedFrom; + } + + public void setPassedFrom(LimitedPlayer passedFrom) { + this.passedFrom = passedFrom; + } + + public void setAwaitingGuess(LimitedPlayer player, PaperCard card) { + this.awaitingGuess = new AbstractMap.SimpleEntry<>(player, card); + } + + public Map.Entry getAwaitingGuess() { + return awaitingGuess; + } + + public void resetAwaitingGuess() { + this.awaitingGuess = null; + } + + @Override + protected List delegate() { + return cards; + } +} 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 c6dd6cac44d..2f1fd74edf3 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/IBoosterDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/IBoosterDraft.java @@ -34,7 +34,7 @@ public interface IBoosterDraft { int getRound(); CardPool nextChoice(); - void setChoice(PaperCard c); + boolean setChoice(PaperCard c); boolean hasNextChoice(); boolean isRoundOver(); Deck[] getDecks(); // size 7, all the computers decks @@ -49,4 +49,6 @@ public interface IBoosterDraft { IDraftLog getDraftLog(); LimitedPlayer getNeighbor(LimitedPlayer p, boolean left); LimitedPlayer getPlayer(int i); + + } 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 89da1b279c5..13797ef6082 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java @@ -21,15 +21,15 @@ public class LimitedPlayer { protected Deck deck; protected PaperCard lastPick; - protected Queue> packQueue; - protected Queue> unopenedPacks; + 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 AgentAcquisitionsCanDraftAll = 1; + private static final int AgentAcquisitionsIsDraftingAll = 1 << 1; + private static final int AgentAcquisitionsSkipDraftRound = 1 << 2; + private static final int CogworkLibrarianExtraDraft = 1 << 3; + private static final int CogworkLibrarianReturnLibrarian = 1 << 4; private static final int AnimusRemoveFromPool = 1 << 5; private static final int NobleBanneretActive = 1 << 6; private static final int PalianoVanguardActive = 1 << 7; @@ -37,6 +37,10 @@ public class LimitedPlayer { private static final int SearcherNoteNext = 1 << 9; private static final int WhispergearBoosterPeek = 1 << 10; private static final int IllusionaryInformantPeek = 1 << 11; + private static final int LeovoldsOperativeCanExtraDraft = 1 << 12; + private static final int LeovoldsOperativeExtraDraft = 1 << 13; + private static final int LeovoldsOperativeSkipNext = 1 << 14; + private static final int SpyNextCardDrafted = 1 << 15; private int playerFlags = 0; @@ -88,24 +92,25 @@ public class LimitedPlayer { return null; } - public boolean draftCard(PaperCard bestPick) { + public Boolean draftCard(PaperCard bestPick) { return draftCard(bestPick, DeckSection.Sideboard); } - public boolean draftCard(PaperCard bestPick, DeckSection section) { + public Boolean draftCard(PaperCard bestPick, DeckSection section) { if (bestPick == null) { - return false; + return null; } - List chooseFrom = packQueue.peek(); + DraftPack chooseFrom = packQueue.peek(); if (chooseFrom == null) { - return false; + return null; } boolean removedFromPool = false; boolean alreadyRevealed = false; + boolean passPack = true; chooseFrom.remove(bestPick); - lastPick = lastPick; + lastPick = bestPick; draftedThisRound++; @@ -134,9 +139,10 @@ public class LimitedPlayer { recordRemoveFromDraft(bestPick, choice); } - LimitedPlayer fromPlayer = receivedFrom(); + + LimitedPlayer fromPlayer = chooseFrom.getPassedFrom(); // If the previous player has an active Cogwork Spy, show them this card - if ((fromPlayer.playerFlags & SpyNextCardDrafted) == SpyNextCardDrafted) { + if (fromPlayer != null && (fromPlayer.playerFlags & SpyNextCardDrafted) == SpyNextCardDrafted) { if (fromPlayer instanceof LimitedPlayerAI) { // I'm honestly not sure what the AI would do by learning this information // But just log that a reveal "happened" @@ -169,9 +175,73 @@ public class LimitedPlayer { } } + if ((playerFlags & AgentAcquisitionsCanDraftAll) == AgentAcquisitionsCanDraftAll) { + if (handleAgentOfAcquisitions(chooseFrom, bestPick)) { + addLog(name() + " drafted the rest of the pack with Agent of Acquisitions"); + playerFlags &= ~AgentAcquisitionsCanDraftAll; + playerFlags |= AgentAcquisitionsIsDraftingAll; + } + } + + if ((playerFlags & AgentAcquisitionsIsDraftingAll) == AgentAcquisitionsIsDraftingAll) { + if (chooseFrom.isEmpty()) { + playerFlags &= ~AgentAcquisitionsIsDraftingAll; + playerFlags |= AgentAcquisitionsSkipDraftRound; + } else { + passPack = false; + } + } + + if ((playerFlags & LeovoldsOperativeExtraDraft) == LeovoldsOperativeExtraDraft) { + if (handleLeovoldsOperative(chooseFrom, bestPick)) { + addLog(name() + " skipped their next pick with Leovold's Operative."); + playerFlags &= ~LeovoldsOperativeExtraDraft; + playerFlags |= LeovoldsOperativeSkipNext; + passPack = false; + } + } + + if ((playerFlags & LeovoldsOperativeCanExtraDraft) == LeovoldsOperativeCanExtraDraft) { + if (handleLeovoldsOperative(chooseFrom, bestPick)) { + addLog(name() + " picking again with Leovold's Operative."); + playerFlags &= ~LeovoldsOperativeCanExtraDraft; + playerFlags |= LeovoldsOperativeExtraDraft; + passPack = false; + } + } + + if ((playerFlags & CogworkLibrarianReturnLibrarian) == CogworkLibrarianReturnLibrarian) { + addLog(name() + " returned Cogwork Librarian to the pack."); + + PaperCard librarian = deck.removeCardName("Cogwork Librarian"); + // TODO The librarian needs to be removed from the UI + + // We shouldn't get here unless we've drafted librarian so we should be able to find one in Deck + // If somehow we don't wellll.. we should remove the bitflag anyway + playerFlags &= ~CogworkLibrarianReturnLibrarian; + if (librarian != null) { + chooseFrom.add(librarian); + } else { + System.out.println("This shouldn't happen. We drafted a libarian but didn't remove it properly."); + } + } + + if ((playerFlags & CogworkLibrarianExtraDraft) == CogworkLibrarianExtraDraft) { + if (handleCogworkLibrarian(chooseFrom, bestPick)) { + addLog(name() + " drafted an extra card with Cogwork Librarian."); + playerFlags &= ~CogworkLibrarianExtraDraft; + playerFlags |= CogworkLibrarianReturnLibrarian; + passPack = false; + } + } + + if (chooseFrom.getAwaitingGuess() != null) { + comparePhantasmGuess(chooseFrom, bestPick); + } + if (removedFromPool) { // Can we hide this from UI? - return true; + return passPack; } CardPool pool = deck.getOrCreate(section); @@ -182,7 +252,7 @@ public class LimitedPlayer { Iterable draftActions = bestPick.getRules().getMainPart().getDraftActions(); if (draftActions == null || !draftActions.iterator().hasNext()) { - return true; + return passPack; } // Draft Actions @@ -223,17 +293,17 @@ public class LimitedPlayer { } else { if (Iterables.contains(draftActions, "You may look at the next card drafted from this booster pack.")) { - // Cogwork Spy playerFlags |= SpyNextCardDrafted; - } - - if (Iterables.contains(draftActions, "Note the player who passed CARDNAME to you.")) { + } else if (fromPlayer != null && Iterables.contains(draftActions, "Note the player who passed CARDNAME to you.")) { // Note who passed it to you. // If you receive last card from Canal Dredger, we need to figure out who last had the pack? List note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList()); note.add(String.valueOf(fromPlayer.order)); + addLog(name() + " revealed " + bestPick.getName() + " and noted " + fromPlayer.name() + " passed it."); } else if (Iterables.contains(draftActions, "Reveal the next card you draft and note its name.")) { playerFlags |= SearcherNoteNext; + } else if (Iterables.contains(draftActions, "The next time a player drafts a card from this booster pack, guess that card’s name. Then that player reveals the drafted card.")) { + chooseFrom.setAwaitingGuess(this, handleSpirePhantasm(chooseFrom)); } addLog(name() + " revealed " + bestPick.getName() + " as " + name() + " drafted it."); @@ -261,6 +331,12 @@ public class LimitedPlayer { // Do we need to ask to use the Sneak immediately? } else if (Iterables.contains(draftActions, "During the draft, you may turn CARDNAME face down. If you do, look at the next card drafted by a player of your choice.")) { playerFlags |= IllusionaryInformantPeek; + } else if (Iterables.contains(draftActions, "As you draft a card, you may draft an additional card from that booster pack. If you do, put CARDNAME into that booster pack.")) { + playerFlags |= CogworkLibrarianExtraDraft; + } else if (Iterables.contains(draftActions, "As you draft a card, you may draft an additional card from that booster pack. If you do, turn CARDNAME face down, then pass the next booster pack without drafting a card from it. (You may look at that booster pack.)")) { + playerFlags |= LeovoldsOperativeExtraDraft; + } else if (Iterables.contains(draftActions, "Instead of drafting a card from a booster pack, you may draft each card in that booster pack, one at a time. If you do, turn CARDNAME face down and you can’t draft cards for the rest of this draft round. (You may look at booster packs passed to you.)")) { + playerFlags |= AgentAcquisitionsCanDraftAll; } } @@ -277,32 +353,44 @@ public class LimitedPlayer { // Mobile doesnt have a draft log yet } - public List nextChoice() { + public DraftPack nextChoice() { return packQueue.peek(); } - public LimitedPlayer receivedFrom() { - return draft.getNeighbor(this, draft.getRound() % 2 == 0); - } - public void newPack() { currentPack = order; draftedThisRound = 0; packQueue.add(unopenedPacks.poll()); + playerFlags &= ~AgentAcquisitionsSkipDraftRound; } public void adjustPackNumber(int adjust, int numPacks) { + // I shouldn't need this since DraftPack has this info currentPack = (currentPack + adjust + numPacks) % numPacks; } - public List passPack() { - return packQueue.poll(); + public DraftPack passPack() { + DraftPack pack = packQueue.poll(); + if (pack != null) { + pack.setPassedFrom(this); + } + return pack; } - public void receiveUnopenedPack(List pack) { + public boolean shouldSkipThisPick() { + boolean skipping = (playerFlags & AgentAcquisitionsSkipDraftRound) == AgentAcquisitionsSkipDraftRound || (playerFlags & LeovoldsOperativeSkipNext) == LeovoldsOperativeSkipNext; + + if (skipping && (playerFlags & LeovoldsOperativeSkipNext) == LeovoldsOperativeSkipNext) { + playerFlags &= ~LeovoldsOperativeSkipNext; + } + + return skipping; + } + + public void receiveUnopenedPack(DraftPack pack) { unopenedPacks.add(pack); } - public void receiveOpenedPack(List pack) { + public void receiveOpenedPack(DraftPack pack) { packQueue.add(pack); } @@ -504,7 +592,7 @@ public class LimitedPlayer { return true; } - protected List peekAtBoosterPack(int round, int playerNumber) { + protected DraftPack peekAtBoosterPack(int round, int playerNumber) { if (draft.getRound() > round) { // There aren't any unopened packs from earlier rounds return null; @@ -541,11 +629,51 @@ public class LimitedPlayer { return true; } + public PaperCard handleSpirePhantasm(DraftPack chooseFrom) { + if (chooseFrom.isEmpty()) { + return null; + } + + return SGuiChoose.one("Guess the next card drafted from this pack", chooseFrom); + } + + public boolean handleLeovoldsOperative(DraftPack pack, PaperCard drafted) { + if (Objects.equals(SGuiChoose.one("Draft an extra pick with Leovold's Operative?", Lists.newArrayList("Yes", "No")), "No")) { + return false; + } + + playerFlags |= LeovoldsOperativeExtraDraft; + return true; + } + + public boolean handleCogworkLibrarian(DraftPack pack, PaperCard drafted) { + return !Objects.equals(SGuiChoose.one("Draft an extra pick with Cogwork Librarian?", Lists.newArrayList("Yes", "No")), "No"); + } + + public boolean handleAgentOfAcquisitions(DraftPack pack, PaperCard drafted) { + return !Objects.equals(SGuiChoose.one("Draft the rest of the pack with Agent of Acquisitions?", Lists.newArrayList("Yes", "No")), "No"); + } + + public void comparePhantasmGuess(DraftPack pack, PaperCard drafted) { + LimitedPlayer guesser = pack.getAwaitingGuess().getKey(); + PaperCard guess = pack.getAwaitingGuess().getValue(); + + addLog(name() + " reveals " + drafted.getName() + " from " + guesser.name() + "'s guess of " + guess.getName() + " with Spire Phantasm."); + if (guess.equals(drafted)) { + addLog(guesser.name() + " correctly guessed " + guess.getName() + " with Spire Phantasm."); + guesser.getDraftNotes().computeIfAbsent("Spire Phantasm", k -> Lists.newArrayList()).add(guess.getName()); + } else { + addLog(guesser.name() + " incorrectly guessed " + guess.getName() + " with Spire Phantasm."); + } + + pack.resetAwaitingGuess(); + } + + /* public void addSingleBoosterPack(boolean random) { // TODO Lore Seeker // 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 e486105c00f..ab5bc6e83c1 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java @@ -12,6 +12,9 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import static forge.gamemodes.limited.CardRanker.getOrderedRawScores; +import static forge.gamemodes.limited.CardRanker.rankCardsInPack; + public class LimitedPlayerAI extends LimitedPlayer { protected DeckColors deckCols; @@ -26,7 +29,7 @@ public class LimitedPlayerAI extends LimitedPlayer { return null; } - List chooseFrom = packQueue.peek(); + DraftPack chooseFrom = packQueue.peek(); if (chooseFrom.isEmpty()) { return null; } @@ -38,11 +41,10 @@ public class LimitedPlayerAI extends LimitedPlayer { // TODO Archdemon of Paliano random draft while active - final ColorSet chosenColors = deckCols.getChosenColors(); final boolean canAddMoreColors = deckCols.canChoseMoreColors(); - List rankedCards = CardRanker.rankCardsInPack(chooseFrom, pool.toFlatList(), chosenColors, canAddMoreColors); + List rankedCards = rankCardsInPack(chooseFrom, pool.toFlatList(), chosenColors, canAddMoreColors); PaperCard bestPick = rankedCards.get(0); if (canAddMoreColors) { @@ -177,4 +179,47 @@ public class LimitedPlayerAI extends LimitedPlayer { //peekAt.getLastPick(); return true; } + + @Override + public PaperCard handleSpirePhantasm(DraftPack chooseFrom) { + if (chooseFrom.isEmpty()) { + return null; + } + + // Choose the card with the highest rank left + return getOrderedRawScores(chooseFrom).get(0); + } + + @Override + public boolean handleLeovoldsOperative(DraftPack pack, PaperCard drafted) { + // Whats the score of the thing I just drafted? + // Whats the next card I would draft? + if (currentPack == 3) { + return true; + } + + return draftedThisRound < 3; + } + + @Override + public boolean handleAgentOfAcquisitions(DraftPack pack, PaperCard drafted) { + // Whats the score of the thing I just drafted? + // Whats the total score of the rest of the pack? + // How many of these cards would actually make my deck? + if (currentPack == 3) { + return true; + } + + return draftedThisRound > 2 && draftedThisRound < 6; + } + + @Override + public boolean handleCogworkLibrarian(DraftPack pack, PaperCard drafted) { + if (currentPack == 3) { + return true; + } + + return draftedThisRound < 3; + } + }