From edb85a18c437b08f1447930b62678cf38584548e Mon Sep 17 00:00:00 2001 From: Chris H Date: Mon, 27 May 2024 21:32:32 -0400 Subject: [PATCH] Add Noble Banneret --- .../java/forge/game/card/CardProperty.java | 6 + .../res/cardsfolder/n/noble_banneret.txt | 8 + .../editions/Conspiracy Take the Crown.txt | 1 + .../gamemodes/limited/LimitedPlayer.java | 193 ++++++++++++------ .../gamemodes/limited/LimitedPlayerAI.java | 12 ++ 5 files changed, 160 insertions(+), 60 deletions(-) create mode 100644 forge-gui/res/cardsfolder/n/noble_banneret.txt 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 baea4e6b655..f79ef70be24 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -2107,6 +2107,12 @@ public class CardProperty { (colors.contains("black") && card.getColor().hasBlack()) || (colors.contains("red") && card.getColor().hasRed()) || (colors.contains("green") && card.getColor().hasGreen()); + } else if (property.equals("NotedName")) { + String names = sourceController.getDraftNotes().get(spellAbility.getHostCard().getName()); + if (names == null || names.isEmpty()) { + return false; + } + return names.contains(card.getName()); } else if (property.startsWith("Triggered")) { if (spellAbility instanceof SpellAbility) { final String key = property.substring(9); diff --git a/forge-gui/res/cardsfolder/n/noble_banneret.txt b/forge-gui/res/cardsfolder/n/noble_banneret.txt new file mode 100644 index 00000000000..d56b0496172 --- /dev/null +++ b/forge-gui/res/cardsfolder/n/noble_banneret.txt @@ -0,0 +1,8 @@ +Name:Noble Banneret +ManaCost:2 W W +Types:Creature Human Knight +PT:3/3 +Draft:Draft CARDNAME face up. +Draft:As you draft a creature card, you may reveal it, note its name, then turn CARDNAME face down. +S:Mode$ Continuous | Affected$ Card.Self,Creature.NotedName+YouCtrl | AddPower$ 1 | AddToughness$ 1 | AddKeyword$ Lifelink | IsPresent$ Creature.NotedName+YouCtrl | PresentCompare$ GE1 | Description$ As long as you control one or more creatures with a name you noted for cards named Noble Banneret, CARDNAME and those creatures get +1/+1 and have lifelink. +Oracle:Draft Noble Banneret face up.\nAs you draft a creature card, you may reveal it, note its name, then turn Noble Banneret face down.\nAs long as you control one or more creatures with a name you noted for cards named Noble Banneret, Noble Banneret and those creatures get +1/+1 and have lifelink. diff --git a/forge-gui/res/editions/Conspiracy Take the Crown.txt b/forge-gui/res/editions/Conspiracy Take the Crown.txt index a704e3f565b..55e42a265f4 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 Incendiary Dissent Natural Unity +Noble Banneret Pyretic Hunter Sovereign's Realm Summoner's Bond 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 af265dac7c5..7b23d166a1a 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java @@ -23,15 +23,16 @@ public class LimitedPlayer { protected Queue> unopenedPacks; protected List removedFromCardPool = new ArrayList<>(); - private static final int CantDraftThisRound = 1; + 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 NobleBanneretActive = 1 << 6; private static final int MAXFLAGS = CantDraftThisRound | ReceiveLastCard | CanRemoveAfterDraft | SpyNextCardDrafted - | CanTradeAfterDraft | AnimusRemoveFromPool; + | CanTradeAfterDraft | AnimusRemoveFromPool | NobleBanneretActive; private int playerFlags = 0; @@ -90,68 +91,20 @@ public class LimitedPlayer { return false; } + boolean alreadyRevealed = false; + chooseFrom.remove(bestPick); draftedThisRound++; - if ((playerFlags & AnimusRemoveFromPool) == AnimusRemoveFromPool && - removeWithAnimus(bestPick)) { - removedFromCardPool.add(bestPick); - addLog(name() + " removed " + bestPick.getName() + " from the draft for Animus of Predation."); - - List keywords = new ArrayList(); - if (bestPick.getRules().getType().isCreature()) { - for (String keyword : bestPick.getRules().getMainPart().getKeywords()) { - switch (keyword) { - case "Flying": - keywords.add("Flying"); - break; - case "First strike": - keywords.add("First Strike"); - break; - case "Double strike": - keywords.add("Double Strike"); - break; - case "Deathtouch": - keywords.add("Deathtouch"); - break; - case "Haste": - keywords.add("Haste"); - break; - case "Hexproof": - keywords.add("Hexproof"); - break; - case "Indestructible": - keywords.add("Indestructible"); - break; - case "Lifelink": - keywords.add("Lifelink"); - break; - case "Menace": - keywords.add("Menace"); - break; - case "Reach": - keywords.add("Reach"); - break; - case "Vigilance": - keywords.add("Vigilance"); - break; - } - } - - if (!keywords.isEmpty()) { - List note = noted.computeIfAbsent("Animus of Predation", k -> Lists.newArrayList()); - note.add(String.join(",", keywords)); - addLog(name() + " added " + String.join(",", keywords) + " for Animus of Predation."); - } - } - - return true; - } else { + if (!handleAnimusOfPredation(bestPick)) { CardPool pool = deck.getOrCreate(section); pool.add(bestPick); } + alreadyRevealed |= handleNobleBanneret(bestPick); + + if (bestPick.getRules().getMainPart().getDraftActions() == null) { return true; } @@ -159,8 +112,10 @@ public class LimitedPlayer { // Draft Actions Iterable draftActions = bestPick.getRules().getMainPart().getDraftActions(); if (Iterables.contains(draftActions, "Reveal CARDNAME as you draft it.")) { - revealed.add(bestPick); - showRevealedCard(bestPick); + if (!alreadyRevealed) { + 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()); @@ -198,15 +153,18 @@ public class LimitedPlayer { if (Iterables.contains(draftActions, "Draft CARDNAME face up.")) { faceUp.add(bestPick); addLog(name() + " drafted " + bestPick.getName() + " face up."); - showRevealedCard(bestPick); + if (!alreadyRevealed) { + showRevealedCard(bestPick); + } - // TODO Noble Banneret // TODO Paliano Vanguard // As you draft a VALID, you may Note its [name/type/], and turn this face down 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.)")) { // Animus of Predation playerFlags |= AnimusRemoveFromPool; + } else if (Iterables.contains(draftActions, "As you draft a creature card, you may reveal it, note its name, then turn CARDNAME face down.")) { + playerFlags |= NobleBanneretActive; } // 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? @@ -267,6 +225,10 @@ public class LimitedPlayer { return SGuiChoose.one("Remove this " + bestPick + " from the draft for ANnimus of Predation?", Lists.newArrayList("Yes", "No")).equals("Yes"); } + protected boolean revealWithBanneret(PaperCard bestPick) { + return SGuiChoose.one("Reveal this " + bestPick + " for Noble Banneret?", Lists.newArrayList("Yes", "No")).equals("Yes"); + } + public String name() { if (this instanceof LimitedPlayerAI) { return "Player[" + order + "]"; @@ -280,6 +242,117 @@ public class LimitedPlayer { } + public boolean handleAnimusOfPredation(PaperCard bestPick) { + if ((playerFlags & AnimusRemoveFromPool) != AnimusRemoveFromPool) { + return false; + } + + if (!removeWithAnimus(bestPick)) { + return false; + } + + removedFromCardPool.add(bestPick); + addLog(name() + " removed " + bestPick.getName() + " from the draft for Animus of Predation."); + + List keywords = new ArrayList(); + if (bestPick.getRules().getType().isCreature()) { + for (String keyword : bestPick.getRules().getMainPart().getKeywords()) { + switch (keyword) { + case "Flying": + keywords.add("Flying"); + break; + case "First strike": + keywords.add("First Strike"); + break; + case "Double strike": + keywords.add("Double Strike"); + break; + case "Deathtouch": + keywords.add("Deathtouch"); + break; + case "Haste": + keywords.add("Haste"); + break; + case "Hexproof": + keywords.add("Hexproof"); + break; + case "Indestructible": + keywords.add("Indestructible"); + break; + case "Lifelink": + keywords.add("Lifelink"); + break; + case "Menace": + keywords.add("Menace"); + break; + case "Reach": + keywords.add("Reach"); + break; + case "Vigilance": + keywords.add("Vigilance"); + break; + } + } + + if (!keywords.isEmpty()) { + List note = noted.computeIfAbsent("Animus of Predation", k -> Lists.newArrayList()); + note.add(String.join(",", keywords)); + addLog(name() + " added " + String.join(",", keywords) + " for Animus of Predation."); + } + } + + return true; + } + + public boolean handleNobleBanneret(PaperCard bestPick) { + boolean alreadyRevealed = false; + if ((playerFlags & NobleBanneretActive) != NobleBanneretActive) { + return false; + } + + if (!bestPick.getRules().getType().isCreature()) { + return false; + } + + boolean remaining = false; + PaperCard found = null; + + for(PaperCard c : faceUp) { + if (c.getName().equals("Noble Banneret")) { + if (found == null) { + found = c; + } else { + remaining = true; + break; + } + } + } + + if (found == null) { + playerFlags &= ~NobleBanneretActive; + return false; + } + + if (!revealWithBanneret(bestPick)) { + return false; + } + + // As you draft a creature card, you may reveal it, note its name, then turn CARDNAME face down. + List note = noted.computeIfAbsent(found.getName(), k -> Lists.newArrayList()); + revealed.add(bestPick); + note.add(bestPick.getName()); + addLog(name() + " revealed " + bestPick.getName() + " and noted its name for Noble Banneret."); + addLog(name() + " has flipped Noble Banneret face down."); + alreadyRevealed = true; + + faceUp.remove(found); + + if (!remaining) { + playerFlags &= ~NobleBanneretActive; + } + return alreadyRevealed; + } + /* public void addSingleBoosterPack(boolean random) { // TODO Lore Seeker 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 b654ccc13e9..777826332ad 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayerAI.java @@ -79,4 +79,16 @@ public class LimitedPlayerAI extends LimitedPlayer { // We should verify we don't already have the keyword bonus that card would grant return false; } + + @Override + protected boolean revealWithBanneret(PaperCard bestPick) { + // Just choose the first creature that we haven't noted yet. + // This is a very simple heuristic, but it's good enough for now. + if (!bestPick.getRules().getType().isCreature()) { + return false; + } + + List nobleBanneret = getDraftNotes().getOrDefault("Noble Banneret", null); + return nobleBanneret == null || !nobleBanneret.contains(bestPick.getName()); + } }