Draft matters with a vengeance (#5400)

* Next few Draft cards

* Whispergear Sneak

* Whispergear Sneak

* Code review fixes

* Fix index mapping

* Don't try to log if Draft doesnt have one
This commit is contained in:
Chris H
2024-06-12 02:18:12 -04:00
committed by GitHub
parent 5c02a580c3
commit 50746d9a72
17 changed files with 319 additions and 53 deletions

View File

@@ -50,7 +50,6 @@ import forge.util.Aggregates;
import forge.util.MyRandom; import forge.util.MyRandom;
import forge.util.Visitor; import forge.util.Visitor;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -327,7 +326,9 @@ public class Game {
int plId = 0; int plId = 0;
for (RegisteredPlayer psc : players0) { for (RegisteredPlayer psc : players0) {
IGameEntitiesFactory factory = (IGameEntitiesFactory)psc.getPlayer(); IGameEntitiesFactory factory = (IGameEntitiesFactory)psc.getPlayer();
Player pl = factory.createIngamePlayer(this, plId++); // If the Registered Player already has a pre-assigned ID, use that. Otherwise, assign a new one.
Integer id = psc.getId();
Player pl = factory.createIngamePlayer(this, id == null ? plId++ : id);
allPlayers.add(pl); allPlayers.add(pl);
ingamePlayers.add(pl); ingamePlayers.add(pl);

View File

@@ -2120,6 +2120,14 @@ public class CardProperty {
} }
List<String> nameList = Lists.newArrayList(names.split(";")); List<String> nameList = Lists.newArrayList(names.split(";"));
return nameList.contains(card.getName());
} else if (property.equals("NotedNameAetherSearcher")) {
String names = sourceController.getDraftNotes().get("Aether Searcher");
if (names == null || names.isEmpty()) {
return false;
}
List<String> nameList = Lists.newArrayList(names.split(";"));
return nameList.contains(card.getName()); return nameList.contains(card.getName());
} else if (property.equals("NotedTypes")) { } else if (property.equals("NotedTypes")) {
// Should Paliano Vanguard be hardcoded here or part of the property? // Should Paliano Vanguard be hardcoded here or part of the property?

View File

@@ -2,7 +2,6 @@ package forge.game.player;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.game.CardTraitBase; import forge.game.CardTraitBase;
import forge.game.Game; import forge.game.Game;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
@@ -15,6 +14,7 @@ import forge.util.Expressions;
import forge.util.TextUtil; import forge.util.TextUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@@ -264,6 +264,10 @@ public class PlayerProperty {
if (source.getChosenPlayer() == null || !source.getChosenPlayer().equals(player)) { if (source.getChosenPlayer() == null || !source.getChosenPlayer().equals(player)) {
return false; return false;
} }
} else if (property.equals("NotedDefender")) {
String tracker = player.getDraftNotes().getOrDefault("Cogwork Tracker", "");
return Iterables.contains(Arrays.asList(tracker.split(",")), String.valueOf(player));
} else if (property.startsWith("life")) { } else if (property.startsWith("life")) {
int life = player.getLife(); int life = player.getLife();
int amount = AbilityUtils.calculateAmount(source, property.substring(6), spellAbility); int amount = AbilityUtils.calculateAmount(source, property.substring(6), spellAbility);

View File

@@ -1,13 +1,7 @@
package forge.game.player; package forge.game.player;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import forge.LobbyPlayer; import forge.LobbyPlayer;
import forge.deck.CardPool; import forge.deck.CardPool;
import forge.deck.Deck; import forge.deck.Deck;
@@ -16,6 +10,11 @@ import forge.game.GameType;
import forge.item.IPaperCard; import forge.item.IPaperCard;
import forge.item.PaperCard; import forge.item.PaperCard;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class RegisteredPlayer { public class RegisteredPlayer {
private final Deck originalDeck; // never return or modify this instance (it's a reference to game resources) private final Deck originalDeck; // never return or modify this instance (it's a reference to game resources)
private Deck currentDeck; private Deck currentDeck;
@@ -37,6 +36,7 @@ public class RegisteredPlayer {
private List<PaperCard> vanguardAvatars = null; private List<PaperCard> vanguardAvatars = null;
private PaperCard planeswalker = null; private PaperCard planeswalker = null;
private int teamNumber = -1; // members of teams with negative id will play FFA. private int teamNumber = -1; // members of teams with negative id will play FFA.
private Integer id = null;
private boolean randomFoil = false; private boolean randomFoil = false;
private boolean enableETBCountersEffect = false; private boolean enableETBCountersEffect = false;
@@ -45,6 +45,14 @@ public class RegisteredPlayer {
restoreDeck(); restoreDeck();
} }
public final Integer getId() {
return id;
}
public final void setId(Integer id0) {
id = id0;
}
public final Deck getDeck() { public final Deck getDeck() {
return currentDeck; return currentDeck;
} }

View File

@@ -1,17 +1,7 @@
package forge.screens.home.sanctioned; package forge.screens.home.sanctioned;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.SwingUtilities;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.Singletons; import forge.Singletons;
import forge.deck.Deck; import forge.deck.Deck;
import forge.deck.DeckGroup; import forge.deck.DeckGroup;
@@ -38,6 +28,14 @@ import forge.screens.deckeditor.views.VStatistics;
import forge.toolbox.FOptionPane; import forge.toolbox.FOptionPane;
import forge.util.Localizer; import forge.util.Localizer;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/** /**
* Controls the draft submenu in the home UI. * Controls the draft submenu in the home UI.
* *
@@ -142,6 +140,12 @@ public enum CSubmenuDraft implements ICDoc {
FModel.getGauntletMini().resetGauntletDraft(); FModel.getGauntletMini().resetGauntletDraft();
String duelType = (String)VSubmenuDraft.SINGLETON_INSTANCE.getCbOpponent().getSelectedItem(); String duelType = (String)VSubmenuDraft.SINGLETON_INSTANCE.getCbOpponent().getSelectedItem();
if (duelType == null) {
FOptionPane.showErrorDialog("Please select duel types for the draft match.", "Missing opponent items");
return;
}
final DeckGroup opponentDecks = FModel.getDecks().getDraft().get(humanDeck.getName()); final DeckGroup opponentDecks = FModel.getDecks().getDraft().get(humanDeck.getName());
if (gauntlet) { if (gauntlet) {
if ("Gauntlet".equals(duelType)) { if ("Gauntlet".equals(duelType)) {
@@ -161,7 +165,7 @@ public enum CSubmenuDraft implements ICDoc {
} }
}); });
List<Deck> aiDecks = Lists.newArrayList(); Map<Integer, Deck> aiMap = Maps.newHashMap();
if (VSubmenuDraft.SINGLETON_INSTANCE.isSingleSelected()) { if (VSubmenuDraft.SINGLETON_INSTANCE.isSingleSelected()) {
// Restore Zero Indexing // Restore Zero Indexing
final int aiIndex = Integer.parseInt(duelType)-1; final int aiIndex = Integer.parseInt(duelType)-1;
@@ -169,28 +173,43 @@ public enum CSubmenuDraft implements ICDoc {
if (aiDeck == null) { if (aiDeck == null) {
throw new IllegalStateException("Draft: Computer deck is null!"); throw new IllegalStateException("Draft: Computer deck is null!");
} }
aiDecks.add(aiDeck);
aiMap.put(aiIndex, aiDeck);
} else { } else {
final int numOpponents = Integer.parseInt(duelType); final int numOpponents = Integer.parseInt(duelType);
List<Deck> randomOpponents = Lists.newArrayList(opponentDecks.getAiDecks()); int maxDecks = opponentDecks.getAiDecks().size();
Collections.shuffle(randomOpponents); if (numOpponents > maxDecks) {
aiDecks = randomOpponents.subList(0, numOpponents); throw new IllegalStateException("Draft: Not enough decks for the number of opponents!");
for(Deck d : aiDecks) { }
if (d == null) {
List<Integer> aiIndices = Lists.newArrayList();
for(int i = 0; i < maxDecks; i++) {
aiIndices.add(i);
}
Collections.shuffle(aiIndices);
aiIndices = aiIndices.subList(0, numOpponents);
for(int i : aiIndices) {
final Deck aiDeck = opponentDecks.getAiDecks().get(i);
if (aiDeck == null) {
throw new IllegalStateException("Draft: Computer deck is null!"); throw new IllegalStateException("Draft: Computer deck is null!");
} }
aiMap.put(i + 1, aiDeck);
} }
} }
final List<RegisteredPlayer> starter = new ArrayList<>(); final List<RegisteredPlayer> starter = new ArrayList<>();
// Human is 0
final RegisteredPlayer human = new RegisteredPlayer(humanDeck.getDeck()).setPlayer(GamePlayerUtil.getGuiPlayer()); final RegisteredPlayer human = new RegisteredPlayer(humanDeck.getDeck()).setPlayer(GamePlayerUtil.getGuiPlayer());
starter.add(human); starter.add(human);
for(Deck aiDeck : aiDecks) { human.setId(0);
starter.add(new RegisteredPlayer(aiDeck).setPlayer(GamePlayerUtil.createAiPlayer())); for(Map.Entry<Integer, Deck> aiDeck : aiMap.entrySet()) {
} RegisteredPlayer aiPlayer = new RegisteredPlayer(aiDeck.getValue()).setPlayer(GamePlayerUtil.createAiPlayer());
for (final RegisteredPlayer pl : starter) { aiPlayer.setId(aiDeck.getKey());
pl.assignConspiracies(); starter.add(aiPlayer);
aiPlayer.assignConspiracies();
} }
final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch();

View File

@@ -26,6 +26,7 @@ import java.util.List;
public class BoosterDraftTest implements IBoosterDraft { public class BoosterDraftTest implements IBoosterDraft {
private int n = 3; private int n = 3;
private int round = 1;
@Override @Override
@Test(timeOut = 1000) @Test(timeOut = 1000)
@@ -43,6 +44,11 @@ public class BoosterDraftTest implements IBoosterDraft {
return null; return null;
} }
@Override
public int getRound() {
return round;
}
@Override @Override
public CardPool nextChoice() { public CardPool nextChoice() {
this.n--; this.n--;
@@ -95,4 +101,9 @@ public class BoosterDraftTest implements IBoosterDraft {
public LimitedPlayer getNeighbor(LimitedPlayer p, boolean left) { public LimitedPlayer getNeighbor(LimitedPlayer p, boolean left) {
return null; return null;
} }
@Override
public LimitedPlayer getPlayer(int i) {
return null;
}
} }

View File

@@ -0,0 +1,17 @@
Name:Aether Searcher
ManaCost:7
PT:6/4
Types:Artifact Creature Construct
Draft:Reveal CARDNAME as you draft it.
Draft:Reveal the next card you draft and note its name.
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearchHand | TriggerDescription$ When CARDNAME enters the battlefield, you may search your hand and/or library for a card with a name noted as you drafted cards named Aether Searcher. You may cast it without paying its mana cost. If you searched your library this way, shuffle.
SVar:TrigSearchHand:DB$ ChangeZone | Origin$ Hand | Destination$ Hand | ChangeType$ Card.NotedNameAetherSearcher | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ TrigBranch
# Branch to casting the found spell
SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ EQ1 | TrueSubAbility$ CastFromHand | FalseSubAbility$ SearchLibrary
SVar:CastFromHand:DB$ Play | ValidZone$ Hand | Valid$ Card.IsRemembered | Controller$ You | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup
# Or search the library
SVar:SearchLibrary:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Card.NotedNameAetherSearcher | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ CastFromLibrary
SVar:CastFromLibrary:DB$ Play | ValidZone$ Library | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | Valid$ Card.IsRemembered | Controller$ You | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Remembered$Amount
Oracle:Reveal Aether Searcher as you draft it. Reveal the next card you draft and note its name.\nWhen Aether Searcher enters the battlefield, you may search your hand and/or library for a card with a name noted as you drafted cards named Aether Searcher. You may cast it without paying its mana cost. If you searched your library this way, shuffle.

View File

@@ -0,0 +1,8 @@
Name:Cogwork Spy
ManaCost:3
Types:Artifact Creature Bird Construct
PT:2/1
K:Flying
Draft:Reveal CARDNAME as you draft it.
Draft:You may look at the next card drafted from this booster pack.
Oracle:Reveal Cogwork Spy as you draft it. You may look at the next card drafted from this booster pack.\nFlying

View File

@@ -0,0 +1,9 @@
Name:Cogwork Tracker
ManaCost:4
Types:Artifact Creature Hound Construct
PT:4/4
Draft:Reveal CARDNAME as you draft it.
Draft:Note the player who passed CARDNAME to you.
S:Mode$ MustAttack | ValidCreature$ Card.Self | Description$ CARDNAME attacks each combat if able.
S:Mode$ MustAttack | ValidPlayer$ Opponent.NotedDefender | ValidCreature$ Card.Self | Description$ CARDNAME attacks a player you noted for cards named Cogwork Tracker each combat if able.
Oracle:Reveal Cogwork Tracker as you draft it and note the player who passed it to you.\nCogwork Tracker attacks each combat if able.\nCogwork Tracker attacks a player you noted for cards named Cogwork Tracker each combat if able.

View File

@@ -0,0 +1,8 @@
Name:Illusionary Informant
ManaCost:1 U
Types:Creature Bird Illusion
PT:1/3
Draft:Draft CARDNAME face up.
Draft:During the draft, you may turn CARDNAME face down. If you do, look at the next card drafted by a player of your choice.
K:Flying
Oracle:Draft Illusionary Informant face up.\nDuring the draft, you may turn Illusionary Informant face down. If you do, look at the next card drafted by a player of your choice.\nFlying

View File

@@ -0,0 +1,7 @@
Name:Whispergear Sneak
ManaCost:1
Types:Artifact Creature Construct
PT:1/1
Draft:Draft CARDNAME face up.
Draft:During the draft, you may turn CARDNAME face down. If you do, look at any unopened booster pack in the draft or any booster pack not being looked at by another player.
Oracle:Draft Whispergear Sneak face up.\nDuring the draft, you may turn Whispergear Sneak face down. If you do, look at any unopened booster pack in the draft or any booster pack not being looked at by another player.

View File

@@ -246,6 +246,7 @@ Garbage Fire
Hired Heist Hired Heist
Hold the Perimeter Hold the Perimeter
Hymn of the Wilds Hymn of the Wilds
Illusionary Informant
Incendiary Dissent Incendiary Dissent
Natural Unity Natural Unity
Noble Banneret Noble Banneret

View File

@@ -222,15 +222,15 @@ ScryfallCode=CNS
[Draft Matters] [Draft Matters]
#1 Advantageous Proclamation|CNS #1 Advantageous Proclamation|CNS
#1 AEther Searcher|CNS 1 AEther Searcher|CNS
#1 Agent of Acquisitions|CNS #1 Agent of Acquisitions|CNS
#1 Backup Plan|CNS #1 Backup Plan|CNS
1 Brago's Favor|CNS 1 Brago's Favor|CNS
#1 Canal Dredger|CNS #1 Canal Dredger|CNS
1 Cogwork Grinder|CNS 1 Cogwork Grinder|CNS
#1 Cogwork Librarian|CNS #1 Cogwork Librarian|CNS
#1 Cogwork Spy|CNS 1 Cogwork Spy|CNS
#1 Cogwork Tracker|CNS 1 Cogwork Tracker|CNS
#1 Deal Broker|CNS #1 Deal Broker|CNS
#1 Double Stroke|CNS #1 Double Stroke|CNS
1 Immediate Action|CNS 1 Immediate Action|CNS
@@ -244,7 +244,7 @@ ScryfallCode=CNS
1 Secrets of Paradise|CNS 1 Secrets of Paradise|CNS
#1 Sentinel Dispatch|CNS #1 Sentinel Dispatch|CNS
#1 Unexpected Potential|CNS #1 Unexpected Potential|CNS
#1 Whispergear Sneak|CNS 1 Whispergear Sneak|CNS
#1 Worldknit|CNS #1 Worldknit|CNS
[tokens] [tokens]

View File

@@ -285,9 +285,14 @@ public class BoosterDraft implements IBoosterDraft {
return draftLog; return draftLog;
} }
@Override
public int getRound() {
return nextBoosterGroup;
}
@Override @Override
public LimitedPlayer getNeighbor(LimitedPlayer player, boolean left) { public LimitedPlayer getNeighbor(LimitedPlayer player, boolean left) {
return players.get((player.order + (left ? -1 : 1) + N_PLAYERS) % N_PLAYERS); return players.get((player.order + (left ? 1 : -1) + N_PLAYERS) % N_PLAYERS);
} }
private void setupCustomDraft(final CustomLimited draft) { private void setupCustomDraft(final CustomLimited draft) {
@@ -412,6 +417,15 @@ public class BoosterDraft implements IBoosterDraft {
return this.localPlayer; return this.localPlayer;
} }
@Override
public LimitedPlayer getPlayer(int i) {
if (i == 0) {
return this.localPlayer;
}
return this.players.get(i - 1);
}
public void passPacks() { public void passPacks() {
// Alternate direction of pack passing // Alternate direction of pack passing
int adjust = this.nextBoosterGroup % 2 == 1 ? 1 : -1; int adjust = this.nextBoosterGroup % 2 == 1 ? 1 : -1;
@@ -419,6 +433,7 @@ public class BoosterDraft implements IBoosterDraft {
adjust = 0; adjust = 0;
} else if (currentBoosterPick % 2 == 1 && "Always".equals(this.doublePickDuringDraft)) { } else if (currentBoosterPick % 2 == 1 && "Always".equals(this.doublePickDuringDraft)) {
// This may not work with Conspiracy cards that mess with the draft // This may not work with Conspiracy cards that mess with the draft
// But it probably doesn't matter since Conspiracy doesn't have double pick?
adjust = 0; adjust = 0;
} }

View File

@@ -32,6 +32,7 @@ import forge.item.PaperCard;
*/ */
public interface IBoosterDraft { public interface IBoosterDraft {
int getRound();
CardPool nextChoice(); CardPool nextChoice();
void setChoice(PaperCard c); void setChoice(PaperCard c);
boolean hasNextChoice(); boolean hasNextChoice();
@@ -47,4 +48,5 @@ public interface IBoosterDraft {
void setLogEntry(IDraftLog draftingProcess); void setLogEntry(IDraftLog draftingProcess);
IDraftLog getDraftLog(); IDraftLog getDraftLog();
LimitedPlayer getNeighbor(LimitedPlayer p, boolean left); LimitedPlayer getNeighbor(LimitedPlayer p, boolean left);
LimitedPlayer getPlayer(int i);
} }

View File

@@ -19,6 +19,7 @@ public class LimitedPlayer {
protected int currentPack; protected int currentPack;
protected int draftedThisRound; protected int draftedThisRound;
protected Deck deck; protected Deck deck;
protected PaperCard lastPick;
protected Queue<List<PaperCard>> packQueue; protected Queue<List<PaperCard>> packQueue;
protected Queue<List<PaperCard>> unopenedPacks; protected Queue<List<PaperCard>> unopenedPacks;
@@ -33,17 +34,16 @@ public class LimitedPlayer {
private static final int NobleBanneretActive = 1 << 6; private static final int NobleBanneretActive = 1 << 6;
private static final int PalianoVanguardActive = 1 << 7; private static final int PalianoVanguardActive = 1 << 7;
private static final int GrinderRemoveFromPool = 1 << 8; private static final int GrinderRemoveFromPool = 1 << 8;
private static final int SearcherNoteNext = 1 << 9;
private static final int MAXFLAGS = CantDraftThisRound | ReceiveLastCard | CanRemoveAfterDraft | SpyNextCardDrafted private static final int WhispergearBoosterPeek = 1 << 10;
| CanTradeAfterDraft | AnimusRemoveFromPool | NobleBanneretActive | PalianoVanguardActive private static final int IllusionaryInformantPeek = 1 << 11;
| GrinderRemoveFromPool;
private int playerFlags = 0; private int playerFlags = 0;
private final List<PaperCard> faceUp = Lists.newArrayList(); private final List<PaperCard> faceUp = Lists.newArrayList();
private final List<PaperCard> revealed = Lists.newArrayList(); private final List<PaperCard> revealed = Lists.newArrayList();
private final Map<String, List<String>> noted = new HashMap<>(); private final Map<String, List<String>> noted = new HashMap<>();
private final HashSet<String> semicolonDelimiter = Sets.newHashSet("Noble Banneret", "Cogwork Grinder"); private final HashSet<String> semicolonDelimiter = Sets.newHashSet("Noble Banneret", "Cogwork Grinder", "Aether Searcher");
IBoosterDraft draft; IBoosterDraft draft;
@@ -69,6 +69,10 @@ public class LimitedPlayer {
return serialized; return serialized;
} }
public PaperCard getLastPick() {
return lastPick;
}
public Deck getDeck() { public Deck getDeck() {
return deck; return deck;
} }
@@ -101,6 +105,7 @@ public class LimitedPlayer {
boolean alreadyRevealed = false; boolean alreadyRevealed = false;
chooseFrom.remove(bestPick); chooseFrom.remove(bestPick);
lastPick = lastPick;
draftedThisRound++; draftedThisRound++;
@@ -129,6 +134,41 @@ public class LimitedPlayer {
recordRemoveFromDraft(bestPick, choice); recordRemoveFromDraft(bestPick, choice);
} }
LimitedPlayer fromPlayer = receivedFrom();
// If the previous player has an active Cogwork Spy, show them this card
if ((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"
addLog(this.name() + " revealed a card to " + fromPlayer.name() + " via Cogwork Spy.");
} else {
addLog(this.name() + " revealed " + bestPick.getName() + " to you with Cogwork Spy.");
}
fromPlayer.playerFlags &= ~SpyNextCardDrafted;
}
if ((playerFlags & SearcherNoteNext) == SearcherNoteNext) {
addLog(name() + " revealed " + bestPick.getName() + " for Aether Searcher.");
playerFlags &= ~SearcherNoteNext;
List<String> note = noted.computeIfAbsent("Aether Searcher", k -> Lists.newArrayList());
note.add(String.valueOf(bestPick.getName()));
}
if ((playerFlags & WhispergearBoosterPeek) == WhispergearBoosterPeek) {
if (handleWhispergearSneak()) {
addLog(name() + " peeked at a booster pack with Whispergear Sneak and turned it face down.");
playerFlags &= ~WhispergearBoosterPeek;
}
}
if ((playerFlags & IllusionaryInformantPeek) == IllusionaryInformantPeek) {
if (handleIllusionaryInformant()) {
addLog(name() + " peeked at " + fromPlayer.name() + "'s next pick with Illusionary Informant and turned it face down.");
playerFlags &= ~IllusionaryInformantPeek;
}
}
if (removedFromPool) { if (removedFromPool) {
// Can we hide this from UI? // Can we hide this from UI?
return true; return true;
@@ -182,7 +222,21 @@ public class LimitedPlayer {
addLog(name() + " revealed " + bestPick.getName() + " and noted " + String.join(",", chosenColors) + " chosen colors."); addLog(name() + " revealed " + bestPick.getName() + " and noted " + String.join(",", chosenColors) + " chosen colors.");
} }
else { else {
addLog(name() + " revealed " + bestPick.getName() + " as they drafted it."); 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.")) {
// 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<String> note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList());
note.add(String.valueOf(fromPlayer.order));
} else if (Iterables.contains(draftActions, "Reveal the next card you draft and note its name.")) {
playerFlags |= SearcherNoteNext;
}
addLog(name() + " revealed " + bestPick.getName() + " as " + name() + " drafted it.");
} }
} }
if (Iterables.contains(draftActions, "Draft CARDNAME face up.")) { if (Iterables.contains(draftActions, "Draft CARDNAME face up.")) {
@@ -202,18 +256,14 @@ public class LimitedPlayer {
playerFlags |= NobleBanneretActive; playerFlags |= NobleBanneretActive;
} else if (Iterables.contains(draftActions, "As you draft a creature card, you may reveal it, note its creature types, then turn CARDNAME face down.")) { } else if (Iterables.contains(draftActions, "As you draft a creature card, you may reveal it, note its creature types, then turn CARDNAME face down.")) {
playerFlags |= PalianoVanguardActive; playerFlags |= PalianoVanguardActive;
} else if (Iterables.contains(draftActions, "During the draft, you may turn CARDNAME face down. If you do, look at any unopened booster pack in the draft or any booster pack not being looked at by another player.")) {
playerFlags |= WhispergearBoosterPeek;
// 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;
} }
} }
// 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 // TODO Lore Seeker
// This adds a pack and MIGHT screw up all of our assumptions about pack passing. Do this last probably // This adds a pack and MIGHT screw up all of our assumptions about pack passing. Do this last probably
@@ -221,13 +271,20 @@ public class LimitedPlayer {
} }
public void addLog(String message) { public void addLog(String message) {
this.draft.getDraftLog().addLogEntry(message); if (this.draft.getDraftLog() != null) {
this.draft.getDraftLog().addLogEntry(message);
}
// Mobile doesnt have a draft log yet
} }
public List<PaperCard> nextChoice() { public List<PaperCard> nextChoice() {
return packQueue.peek(); return packQueue.peek();
} }
public LimitedPlayer receivedFrom() {
return draft.getNeighbor(this, draft.getRound() % 2 == 0);
}
public void newPack() { public void newPack() {
currentPack = order; currentPack = order;
draftedThisRound = 0; draftedThisRound = 0;
@@ -430,10 +487,65 @@ public class LimitedPlayer {
return alreadyRevealed; return alreadyRevealed;
} }
public boolean handleWhispergearSneak() {
if (Objects.equals(SGuiChoose.oneOrNone("Peek at a booster pack with Whispergear Sneak?", Lists.newArrayList("Yes", "No")), "No")) {
return false;
}
int round = 3;
if (draft.getRound() != 3) {
round = SGuiChoose.getInteger("Which round would you like to peek at?", draft.getRound(), 3);
}
int playerId = SGuiChoose.getInteger("Which player would you like to peek at?", 0, draft.getOpposingPlayers().length);
SGuiChoose.reveal("Peeked booster", peekAtBoosterPack(round, playerId));
// This reveal popup doesn't update the card detail panel in draft
// How do we get to do that?
return true;
}
protected List<PaperCard> peekAtBoosterPack(int round, int playerNumber) {
if (draft.getRound() > round) {
// There aren't any unopened packs from earlier rounds
return null;
}
int relativeRound = round - draft.getRound();
LimitedPlayer player;
if (playerNumber == 0) {
player = this.draft.getHumanPlayer();
} else {
player = this.draft.getOpposingPlayers()[playerNumber - 1];
}
if (relativeRound == 0) {
// I want to see a pack from the current round
return player.packQueue.peek();
} else {
return player.unopenedPacks.peek();
}
}
public boolean handleIllusionaryInformant() {
Integer player = SGuiChoose.getInteger("Peek at another player's last pick?", 0, draft.getOpposingPlayers().length);
if (Objects.equals(player, null)) {
return false;
}
LimitedPlayer peekAt = draft.getPlayer(player);
if (peekAt == null) {
return false;
}
SGuiChoose.reveal("Player " + player + " lastPicked: ", Lists.newArrayList(peekAt.getLastPick()));
return true;
}
/* /*
public void addSingleBoosterPack(boolean random) { public void addSingleBoosterPack(boolean random) {
// TODO Lore Seeker // TODO Lore Seeker
// Generate booster pack then, insert it "before" the pack we're currently drafting from // Generate booster pack then, insert it "before" the pack we're currently drafting from
} }
*/ */
} }

View File

@@ -6,6 +6,7 @@ import forge.deck.Deck;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences;
import forge.util.MyRandom;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -141,4 +142,39 @@ public class LimitedPlayerAI extends LimitedPlayer {
return types.containsAll(notedTypes); return types.containsAll(notedTypes);
} }
@Override
public boolean handleWhispergearSneak() {
// Always choose the next pack I will open
// What do I do with this information? Great question. I have no idea.
List<PaperCard> cards;
if (draft.getRound() == 3) {
// Take a peek at the pack you are about to get if its the last round
cards = peekAtBoosterPack(this.order, 1);
} else {
cards = peekAtBoosterPack(this.order, draft.getRound() + 1);
}
return true;
}
@Override
public boolean handleIllusionaryInformant() {
// Always choose the next pack I will open
// What do I do with this information? Great question. I have no idea.
int player;
do {
player = MyRandom.getRandom().nextInt(draft.getOpposingPlayers().length + 1);
} while(player == this.order);
LimitedPlayer peekAt = draft.getPlayer(player);
if (peekAt == null) {
return false;
}
// Not really sure what the AI does with this information. But its' known now.
//peekAt.getLastPick();
return true;
}
} }