From 799fd4cc51cc2298ade620fb25b44a4900ebcee8 Mon Sep 17 00:00:00 2001 From: Chris H Date: Mon, 20 May 2024 20:23:37 -0400 Subject: [PATCH] More draft matters --- .../src/main/java/forge/deck/DeckFormat.java | 5 +- .../java/forge/game/card/CardProperty.java | 15 +- .../game/spellability/AbilityManaPart.java | 32 ++++- .../controllers/CEditorDraftingProcess.java | 9 +- .../src/test/java/forge/BoosterDraftTest.java | 16 ++- .../res/cardsfolder/a/animus_of_predation.txt | 0 .../cardsfolder/p/paliano_the_high_city.txt | 8 ++ forge-gui/res/cardsfolder/r/regicide.txt | 7 + .../forge/gamemodes/limited/BoosterDraft.java | 25 ++-- .../forge/gamemodes/limited/DeckColors.java | 11 +- .../gamemodes/limited/IBoosterDraft.java | 1 + .../gamemodes/limited/LimitedPlayer.java | 133 ++++++++++++------ .../gamemodes/limited/LimitedPlayerAI.java | 29 +++- 13 files changed, 212 insertions(+), 79 deletions(-) create mode 100644 forge-gui/res/cardsfolder/a/animus_of_predation.txt create mode 100644 forge-gui/res/cardsfolder/p/paliano_the_high_city.txt create mode 100644 forge-gui/res/cardsfolder/r/regicide.txt 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; + } }