Add Animus of Predation

This commit is contained in:
Chris H
2024-05-26 14:19:51 -04:00
parent 26ec757736
commit 66bd484f27
8 changed files with 136 additions and 55 deletions

View File

@@ -69,11 +69,7 @@ import forge.util.collect.FCollectionView;
import io.sentry.Breadcrumb; import io.sentry.Breadcrumb;
import io.sentry.Sentry; import io.sentry.Sentry;
import java.util.Collections; import java.util.*;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* <p> * <p>
@@ -1882,6 +1878,31 @@ public class AiController {
return options.get(0); return options.get(0);
} }
case ChooseNumber: case ChooseNumber:
if (sa.getHostCard().getName().equals("Emissary's Ploy")) {
// Count the amount of creatures in each CMC of 1,2,3 and choose that number
// If you have multiple ploys, technically AI should choose different numbers
// But thats not what happens currently
List<Integer> counter = Lists.newArrayList(0,0,0);
int max = 0;
int slot = 0;
for (Card c : relatedPlayer.getZone(ZoneType.Library).getCards()) {
if (!c.isCreature()) {
continue;
}
if (c.getCMC() > 0 && c.getCMC() < 4) {
counter.set(c.getCMC() - 1, counter.get(c.getCMC() - 1) + 1);
}
}
for(int i = 0; i < counter.size(); i++) {
if (counter.get(i) >= max) {
max = counter.get(i);
slot = i;
}
}
return slot;
}
return Aggregates.random(options); return Aggregates.random(options);
default: default:
return options.get(0); return options.get(0);

View File

@@ -19,24 +19,22 @@ package forge.game;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.*; import com.google.common.collect.*;
import forge.GameCommand; import forge.GameCommand;
import forge.StaticData; import forge.StaticData;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.card.MagicColor;
import forge.card.CardType.Supertype; import forge.card.CardType.Supertype;
import forge.card.MagicColor;
import forge.deck.DeckSection; import forge.deck.DeckSection;
import forge.game.ability.*; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.*; import forge.game.card.*;
import forge.game.event.*; import forge.game.event.*;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
import forge.game.mulligan.MulliganService; import forge.game.mulligan.MulliganService;
import forge.game.player.GameLossReason; import forge.game.player.*;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.player.PlayerCollection;
import forge.game.player.PlayerPredicates;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementResult; import forge.game.replacement.ReplacementResult;
import forge.game.replacement.ReplacementType; import forge.game.replacement.ReplacementType;
@@ -56,7 +54,6 @@ import forge.item.PaperCard;
import forge.util.*; import forge.util.*;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import java.util.*; import java.util.*;
@@ -2197,7 +2194,8 @@ public class GameAction {
for (Card c : ploys) { for (Card c : ploys) {
if (!cmc.isEmpty()) { if (!cmc.isEmpty()) {
chosen = takesAction.getController().chooseNumber(c.getSpellPermanent(), "Emissary's Ploy", cmc, c.getOwner()); SpellAbility sa = new SpellAbility.EmptySa(ApiType.ChooseNumber, c, takesAction);
chosen = takesAction.getController().chooseNumber(sa, "Emissary's Ploy", cmc, c.getOwner());
cmc.remove((Object)chosen); cmc.remove((Object)chosen);
} }

View File

@@ -17,42 +17,21 @@
*/ */
package forge.game.staticability; package forge.game.staticability;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
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 com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.GameCommand; import forge.GameCommand;
import forge.card.CardStateName; import forge.card.*;
import forge.card.CardType;
import forge.card.ColorSet;
import forge.card.MagicColor;
import forge.card.RemoveType;
import forge.game.Game; import forge.game.Game;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.StaticEffect; import forge.game.StaticEffect;
import forge.game.StaticEffects; import forge.game.StaticEffects;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.*;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardState;
import forge.game.card.CardUtil;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface; import forge.game.keyword.KeywordInterface;
@@ -64,6 +43,9 @@ import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger; import forge.game.trigger.Trigger;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.TextUtil; import forge.util.TextUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
/** /**
* The Class StaticAbility_Continuous. * The Class StaticAbility_Continuous.
@@ -330,6 +312,11 @@ public final class StaticAbilityContinuous {
String[] restrictions = params.containsKey("SharedRestrictions") ? params.get("SharedRestrictions").split(",") : new String[] {"Card"}; String[] restrictions = params.containsKey("SharedRestrictions") ? params.get("SharedRestrictions").split(",") : new String[] {"Card"};
addKeywords = CardFactoryUtil.sharedKeywords(addKeywords, restrictions, zones, hostCard, stAb); addKeywords = CardFactoryUtil.sharedKeywords(addKeywords, restrictions, zones, hostCard, stAb);
} }
if (params.containsKey("FromDraftNotes")) {
addKeywords = Lists.newArrayList(hostCard.getController().getDraftNotes().getOrDefault(hostCard.getName(), "").split(","));
}
} else if (params.containsKey("ShareRememberedKeywords")) { } else if (params.containsKey("ShareRememberedKeywords")) {
List<String> kwToShare = Lists.newArrayList(); List<String> kwToShare = Lists.newArrayList();
for (final Object o : hostCard.getRemembered()) { for (final Object o : hostCard.getRemembered()) {

View File

@@ -186,7 +186,9 @@ public class CEditorDraftingProcess extends ACEditorBase<PaperCard, DeckGroup> i
deck.getOrCreate(DeckSection.Sideboard).addAll(this.getDeckManager().getPool()); deck.getOrCreate(DeckSection.Sideboard).addAll(this.getDeckManager().getPool());
return deck; return deck;
} // getPlayersDeck() // Why don't we just do?
// return player.getDeck()
}
/** /**
* <p> * <p>
@@ -237,7 +239,22 @@ public class CEditorDraftingProcess extends ACEditorBase<PaperCard, DeckGroup> i
final DeckGroup finishedDraft = new DeckGroup(s); final DeckGroup finishedDraft = new DeckGroup(s);
final LimitedPlayer player = this.boosterDraft.getHumanPlayer(); final LimitedPlayer player = this.boosterDraft.getHumanPlayer();
// Why is human deck just imported from LimitedPlayer?
//Deck humanDeck = player.getDeck().copyTo(s);
// If we do the above, we shouldn't need remove from card pool below
Deck humanDeck = (Deck) this.getPlayersDeck().copyTo(s); Deck humanDeck = (Deck) this.getPlayersDeck().copyTo(s);
for(PaperCard card : player.getRemovedFromCardPool()) {
// This is awkward. We are duplicating the deck construction logic
// So we need to remove from the deck twice
// This may be problematic for trading cards from your card pool
humanDeck.get(DeckSection.Sideboard).remove(card);
// These cards need to be added to a quest deck if there is an associated quest
// Although quest Drafting process happened in #CEditorQuestDraftingProcess
// Probably need to make these files closer to each other
}
humanDeck.setDraftNotes(player.getSerializedDraftNotes()); humanDeck.setDraftNotes(player.getSerializedDraftNotes());
finishedDraft.setHumanDeck(humanDeck); finishedDraft.setHumanDeck(humanDeck);
finishedDraft.addAiDecks(computer); finishedDraft.addAiDecks(computer);

View File

@@ -17,8 +17,6 @@
*/ */
package forge.screens.deckeditor.controllers; package forge.screens.deckeditor.controllers;
import java.util.Map.Entry;
import forge.deck.Deck; import forge.deck.Deck;
import forge.deck.DeckGroup; import forge.deck.DeckGroup;
import forge.deck.DeckSection; import forge.deck.DeckSection;
@@ -31,19 +29,15 @@ import forge.gui.framework.FScreen;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.itemmanager.CardManager; import forge.itemmanager.CardManager;
import forge.itemmanager.ItemManagerConfig; import forge.itemmanager.ItemManagerConfig;
import forge.screens.deckeditor.views.VAllDecks; import forge.screens.deckeditor.views.*;
import forge.screens.deckeditor.views.VBrawlDecks;
import forge.screens.deckeditor.views.VCommanderDecks;
import forge.screens.deckeditor.views.VCurrentDeck;
import forge.screens.deckeditor.views.VDeckgen;
import forge.screens.deckeditor.views.VOathbreakerDecks;
import forge.screens.deckeditor.views.VTinyLeadersDecks;
import forge.screens.home.quest.CSubmenuQuestDraft; import forge.screens.home.quest.CSubmenuQuestDraft;
import forge.screens.home.quest.VSubmenuQuestDraft; import forge.screens.home.quest.VSubmenuQuestDraft;
import forge.screens.match.controllers.CDetailPicture; import forge.screens.match.controllers.CDetailPicture;
import forge.util.ItemPool; import forge.util.ItemPool;
import forge.util.Localizer; import forge.util.Localizer;
import java.util.Map.Entry;
/** /**
* Updates the deck editor UI as necessary draft selection mode. * Updates the deck editor UI as necessary draft selection mode.
* *
@@ -211,6 +205,7 @@ public class CEditorQuestDraftingProcess extends ACEditorBase<PaperCard, DeckGro
* </p> * </p>
*/ */
private void saveDraft() { private void saveDraft() {
// This should be inheriting more from CEditorDraftingProcess
saved = true; saved = true;

View File

@@ -0,0 +1,8 @@
Name:Animus of Predation
ManaCost:4 G
Types:Creature Avatar
PT:4/4
Draft:Draft CARDNAME face up.
Draft:As you draft a card, you may remove it from the draft face up. (It isnt in your card pool.)
S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Flying & First Strike & Double Strike & Deathtouch & Haste & Hexproof & Indestructible & Lifelink & Menace & Reach & Vigilance | FromDraftNotes$ True | Description$ If you removed a creature card with flying from the draft with cards named Animus of Predation, Animus of Predation has flying. The same is true for first strike, double strike, deathtouch, haste, hexproof, indestructible, lifelink, menace, reach, and vigilance.
Oracle:Draft Animus of Predation face up.\nAs you draft a card, you may remove it from the draft face up. (It isnt in your card pool.)\nIf you removed a creature card with flying from the draft with cards named Animus of Predation, Animus of Predation has flying. The same is true for first strike, double strike, deathtouch, haste, hexproof, indestructible, lifelink, menace, reach, and vigilance.

View File

@@ -11,7 +11,6 @@ import forge.item.PaperCard;
import forge.util.TextUtil; import forge.util.TextUtil;
import java.util.*; import java.util.*;
//import forge.gamemodes.limited.powers.DraftPower;
public class LimitedPlayer { public class LimitedPlayer {
// A Player class for inside some type of limited environment, like Draft. // A Player class for inside some type of limited environment, like Draft.
@@ -63,6 +62,14 @@ public class LimitedPlayer {
return serialized; return serialized;
} }
public Deck getDeck() {
return deck;
}
public List<PaperCard> getRemovedFromCardPool() {
return removedFromCardPool;
}
public PaperCard chooseCard() { public PaperCard chooseCard() {
// A non-AI 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 // TODO Archdemon of Paliano random draft while active
@@ -85,17 +92,66 @@ public class LimitedPlayer {
chooseFrom.remove(bestPick); chooseFrom.remove(bestPick);
draftedThisRound++;
if ((playerFlags & AnimusRemoveFromPool) == AnimusRemoveFromPool && if ((playerFlags & AnimusRemoveFromPool) == AnimusRemoveFromPool &&
removeWithAnimus(bestPick)) { removeWithAnimus(bestPick)) {
removedFromCardPool.add(bestPick); removedFromCardPool.add(bestPick);
addLog(name() + " removed " + bestPick.getName() + " from the draft for Animus of Predation.");
List<String> keywords = new ArrayList<String>();
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<String> 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; return true;
} else { } else {
CardPool pool = deck.getOrCreate(section); CardPool pool = deck.getOrCreate(section);
pool.add(bestPick); pool.add(bestPick);
} }
draftedThisRound++;
if (bestPick.getRules().getMainPart().getDraftActions() == null) { if (bestPick.getRules().getMainPart().getDraftActions() == null) {
return true; return true;
} }
@@ -133,7 +189,7 @@ public class LimitedPlayer {
List<String> note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList()); List<String> note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList());
note.add(String.join(",", chosenColors)); note.add(String.join(",", chosenColors));
addLog(name() + " revealed " + bestPick.getName() + " and noted " + chosenColors + " chosen."); addLog(name() + " revealed " + bestPick.getName() + " and noted " + String.join(",", chosenColors) + " chosen colors.");
} }
else { else {
addLog(name() + " revealed " + bestPick.getName() + " as they drafted it."); addLog(name() + " revealed " + bestPick.getName() + " as they drafted it.");
@@ -148,9 +204,8 @@ public class LimitedPlayer {
// TODO Paliano Vanguard // TODO Paliano Vanguard
// As you draft a VALID, you may Note its [name/type/], and turn this face down // 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 isnt in your card pool.)")) { if (Iterables.contains(draftActions, "As you draft a card, you may remove it from the draft face up. (It isnt in your card pool.)")) {
// Animus of Predation
playerFlags |= AnimusRemoveFromPool; playerFlags |= AnimusRemoveFromPool;
} }
// As you draft a VALID, you may remove it face up. (It's no longer in your draft pool) // As you draft a VALID, you may remove it face up. (It's no longer in your draft pool)

View File

@@ -63,7 +63,7 @@ public class LimitedPlayerAI extends LimitedPlayer {
protected String chooseColor(List<String> colors, LimitedPlayer player, String title) { protected String chooseColor(List<String> colors, LimitedPlayer player, String title) {
if (player.equals(this)) { if (player.equals(this)) {
// For Paliano, choose one of my colors // For Paliano, choose one of my colors
// For Regicie, random is fine? // For Regicide, random is fine?
} else { } else {
// For Paliano, if player has revealed anything, try to avoid that color // For Paliano, if player has revealed anything, try to avoid that color
// For Regicide, don't choose one of my colors // For Regicide, don't choose one of my colors