Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chosen letter support (Unsets) #6636

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions forge-ai/src/main/java/forge/ai/PlayerControllerAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,11 @@ public int chooseSprocket(Card assignee, boolean forceDifferent) {
return nextSprocket;
}

@Override
public List<String> chooseLetter(int n, SpellAbility sa, List<String> letters) {
return Aggregates.random(letters, n);
}

@Override
public PlanarDice choosePDRollToIgnore(List<PlanarDice> rolls) {
//TODO create AI logic for this
Expand Down
1 change: 1 addition & 0 deletions forge-ai/src/main/java/forge/ai/simulation/GameCopier.java
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ private void addCard(Game newGame, ZoneType zone, Card c, Player aiPlayer) {

newCard.setSprocket(c.getSprocket());

if (c.hasChosenLetter()) newCard.setChosenLetters(Lists.newArrayList(c.getChosenLetters()));
newCard.setSVars(c.getSVars());
newCard.copyChangedSVarsFrom(c);
}
Expand Down
25 changes: 25 additions & 0 deletions forge-game/src/main/java/forge/game/ForgeScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
import forge.util.Expressions;
import org.apache.commons.lang3.StringUtils;

import java.text.Normalizer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ForgeScript {
Expand Down Expand Up @@ -91,6 +93,29 @@ public static boolean cardStateHasProperty(CardState cardState, String property,
default:
return false;
}
} else if (property.startsWith("ChosenLetter")) {
final List<String> letters = source.getChosenLetters();
if (letters == null) return false; // some things check before letters have been chosen
String name = Normalizer.normalize(cardState.getName().toUpperCase(), Normalizer.Form.NFD);
if (name.startsWith("A-")) name = name.substring(2); // remove Alchemy tag
if (property.contains("FirstWord")) name = name.split(" ")[0];
boolean found = false;
if (property.endsWith("Starts")) {
for (String l : letters) {
if (name.startsWith(l)) {
found = true;
break;
}
}
} else if (property.endsWith("Contains")) {
for (String l : letters) {
if (name.contains(l)) {
found = true;
break;
}
}
}
return found;
} else if (property.equals("Outlaw")) {
return type.isOutlaw();
} else if (property.startsWith("non")) {
Expand Down
12 changes: 12 additions & 0 deletions forge-game/src/main/java/forge/game/ability/AbilityUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,18 @@ public static int xCount(Card c, final String s, final CardTraitBase ctb) {
}
return count;
}
// Count$TriggeredAmountChosenLetterInName
if (sq[0].startsWith("TriggeredAmountChosenLetterInName")) {
if (c.getChosenLetters() == null) return 0; // just in case
final SpellAbility root = sa.getRootAbility();
Card card = (Card) root.getTriggeringObject(AbilityKey.Card);
String name = card.getCurrentState().getName();
if (name.startsWith("A-")) name = name.substring(2); // remove Alchemy tag
int count = StringUtils.countMatches(name, c.getChosenLetters().get(0));
count += StringUtils.countMatches(name, c.getChosenLetters().get(0).toLowerCase());
return count;
}

// Count$ManaProduced
if (sq[0].startsWith("AmountManaProduced")) {
final SpellAbility root = sa.getRootAbility();
Expand Down
1 change: 1 addition & 0 deletions forge-game/src/main/java/forge/game/ability/ApiType.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public enum ApiType {
ChooseColor (ChooseColorEffect.class),
ChooseDirection (ChooseDirectionEffect.class),
ChooseEvenOdd (ChooseEvenOddEffect.class),
ChooseLetter (ChooseLetterEffect.class),
ChooseNumber (ChooseNumberEffect.class),
ChoosePlayer (ChoosePlayerEffect.class),
ChooseSector (ChooseSectorEffect.class),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package forge.game.ability.effects;

import com.google.common.collect.Lists;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;

import java.util.*;

public class ChooseLetterEffect extends SpellAbilityEffect {

@Override
public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard();
List<String> letters = Lists.newArrayList("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z");
List<String> consonants = Lists.newArrayList("B", "C", "D", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q",
"R", "S", "T", "V", "W", "X", "Y", "Z");
List<String> choices = sa.hasParam("Consonant") ? consonants : letters;
Hanmac marked this conversation as resolved.
Show resolved Hide resolved
if (sa.hasParam("Exclude")) choices.removeAll(Arrays.asList(sa.getParam("Exclude").split(",")));
int num = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("Num", "1"), sa);
final List<String> chosen = card.getController().getController().chooseLetter(num, sa, choices);
card.setChosenLetters(chosen);
}
}
14 changes: 14 additions & 0 deletions forge-game/src/main/java/forge/game/card/Card.java
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars, ITr
private List<String> notedTypes = new ArrayList<>();
private List<String> chosenColors;
private Set<String> chosenColorID;
private List<String> chosenLetters;
private List<String> chosenName = new ArrayList<>();
private Integer chosenNumber;
private Player chosenPlayer;
Expand Down Expand Up @@ -2016,6 +2017,19 @@ public final void clearChosenNumber() {
view.clearChosenNumber();
}

public boolean hasChosenLetter() {
return chosenLetters != null && !chosenLetters.isEmpty();
}

public final List<String> getChosenLetters() {
return chosenLetters;
}
public final void setChosenLetters(final List<String> letters) {
Collections.sort(letters);
chosenLetters = letters;
view.updateChosenLetters(this);
}
Hanmac marked this conversation as resolved.
Show resolved Hide resolved

public final Card getExiledWith() {
return exiledWith;
}
Expand Down
3 changes: 3 additions & 0 deletions forge-game/src/main/java/forge/game/card/CardCopyService.java
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,9 @@ public Card getLKICopy(Map<Integer, Card> cachedMap) {
newCopy.setChosenType2(copyFrom.getChosenType2());
newCopy.setNamedCards(Lists.newArrayList(copyFrom.getNamedCards()));
newCopy.setChosenColors(Lists.newArrayList(copyFrom.getChosenColors()));
if (copyFrom.hasChosenLetter()) {
newCopy.setChosenLetters(Lists.newArrayList(copyFrom.getChosenLetters()));
}
if (copyFrom.hasChosenNumber()) {
newCopy.setChosenNumber(copyFrom.getChosenNumber());
}
Expand Down
6 changes: 6 additions & 0 deletions forge-game/src/main/java/forge/game/card/CardView.java
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,12 @@ void updateNotedTypes(Card c) {
set(TrackableProperty.NotedTypes, c.getNotedTypes());
}

public List<String> getChosenLetters() {
return get(TrackableProperty.ChosenLetters);
}
void updateChosenLetters(Card c) {
set(TrackableProperty.ChosenLetters, c.getChosenLetters());
}
public String getChosenNumber() {
return get(TrackableProperty.ChosenNumber);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ public final void reveal(List<CardView> cards, ZoneType zone, PlayerView owner,
public abstract PlayerZone chooseStartingHand(List<PlayerZone> zones);
public abstract Mana chooseManaFromPool(List<Mana> manaChoices);

public abstract List<String> chooseLetter(int n, SpellAbility sa, List<String> letters);

public abstract String chooseSomeType(String kindOfType, SpellAbility sa, Collection<String> validTypes, boolean isOptional);
public final String chooseSomeType(String kindOfType, SpellAbility sa, Collection<String> validTypes) {
return chooseSomeType(kindOfType, sa, validTypes, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public enum TrackableProperty {
ChosenColorID(TrackableTypes.StringSetType),
ChosenCards(TrackableTypes.CardViewCollectionType),
ChosenNumber(TrackableTypes.StringType),
ChosenLetters(TrackableTypes.StringListType),
StoredRolls(TrackableTypes.StringListType),
ChosenPlayer(TrackableTypes.PlayerViewType),
PromisedGift(TrackableTypes.PlayerViewType),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,16 @@ public String chooseSomeType(String kindOfType, SpellAbility sa, Collection<Stri
return chooseItem(validTypes);
}

@Override
public List<String> chooseLetter(int n, SpellAbility sa, List<String> letters) {
List<String> chosen = Lists.newArrayList();
for (int i = 0; i <=n; i++) {
String choice = chooseItem(letters);
letters.remove(choice);
chosen.add(choice);
}
return chosen;
}
@Override
public String chooseSector(Card assignee, String ai, List<String> sectors) {
return chooseItem(sectors);
Expand Down
8 changes: 8 additions & 0 deletions forge-gui/res/cardsfolder/a/alpha_guard.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Name:Alpha Guard
ManaCost:1 G
Types:Creature Elf Employee
PT:1/2
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChooseLetter | TriggerDescription$ When CARDNAME enters, choose a letter.
SVar:TrigChooseLetter:DB$ ChooseLetter
A:AB$ Untap | Cost$ T | ValidTgts$ Permanent.Other+ChosenLetterNameStarts | TgtPrompt$ Select another target permanent whose name starts with the chosen letter | SpellDescription$ Untap another target permanent whose name starts with the chosen letter.
Oracle:When Alpha Guard enters, choose a letter.\n{T}: Untap another target permanent whose name starts with the chosen letter.
9 changes: 9 additions & 0 deletions forge-gui/res/cardsfolder/d/decisions_decisions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Name:Decisions, Decisions
ManaCost:3 U
Types:Sorcery
A:SP$ PeekAndReveal | PeekAmount$ 3 | RememberRevealed$ True | SubAbility$ DBChooseConsonant | StackDescription$ SpellDescription | SpellDescription$ Reveal the top three cards of your library, then choose a consonant. (Y counts.)
SVar:DBChooseConsonant:DB$ ChooseLetter | Consonant$ True | SubAbility$ DBChangeZone | StackDescription$ None
SVar:DBChangeZone:DB$ ChangeZoneAll | Origin$ Library | Destination$ Hand | ChangeType$ Card.IsRemembered+ChosenLetterFirstWordContains | ForgetChanged$ True | SubAbility$ DBLibrary | StackDescription$ SpellDescription | SpellDescription$ Put each card that has the chosen letter in the first word of its name from among them into your hand and the rest on the bottom of your library in any order.
SVar:DBLibrary:DB$ ChangeZoneAll | Origin$ Library | Destination$ Library | ChangeType$ Card.IsRemembered | LibraryPosition$ -1 | SubAbility$ DBCleanup | StackDescription$ None
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
Oracle:Reveal the top three cards of your library, then choose a consonant. (Y counts.) Put each card that has the chosen letter in the first word of its name from among them into your hand and the rest on the bottom of your library in any order.
9 changes: 9 additions & 0 deletions forge-gui/res/cardsfolder/g/gray_merchant_of_alphabet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Name:Gray Merchant of Alphabet
ManaCost:3 B B
Types:Creature Zombie Performer
PT:2/4
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChooseLetter | TriggerDescription$ When CARDNAME enters, choose a letter. For each creature you control that has the chosen letter in its name, you gain 1 life and each opponent loses 1 life.
SVar:TrigChooseLetter:DB$ ChooseLetter | SubAbility$ DBGainLife
SVar:DBGainLife:DB$ GainLife | LifeAmount$ Count$Valid Creature.YouCtrl+ChosenLetterNameContains | SubAbility$ DBDrain
SVar:DBDrain:DB$ LoseLife | Defined$ Opponent | LifeAmount$ Count$Valid Creature.YouCtrl+ChosenLetterNameContains
Oracle:When Gray Merchant of Alphabet enters, choose a letter. For each creature you control that has the chosen letter in its name, you gain 1 life and each opponent loses 1 life.
11 changes: 11 additions & 0 deletions forge-gui/res/cardsfolder/k/katerina_of_myras_marvels.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Name:Katerina of Myra's Marvels
ManaCost:2 W W
Types:Legendary Creature Human Performer
PT:3/3
K:ETBReplacement:Other:DBChooseLetter
SVar:DBChooseLetter:DB$ ChooseLetter | SpellDescription$ As CARDNAME enters, choose a letter.
T:Mode$ SpellCast | ValidActivatingPlayer$ You | ValidCard$ Card.ChosenLetterNameStarts | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you cast a spell whose name begins with the chosen letter, create a white 2/2 Cat creature token with flying.
SVar:TrigToken:DB$ Token | TokenScript$ w_2_2_cat_flying
K:Partner
DeckHas:Ability$Token & Type$Cat
Oracle:As Katerina of Myra's Marvels enters, choose a letter.\nWhenever you cast a spell whose name begins with the chosen letter, create a white 2/2 Cat creature token with flying.\nPartner (You can have two commanders if they both have partner.)
7 changes: 7 additions & 0 deletions forge-gui/res/cardsfolder/l/leading_performance.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Name:Leading Performance
ManaCost:1 W
Types:Sorcery
A:SP$ ChooseLetter | Num$ 2 | SubAbility$ PutCounterAll | SpellDescription$ Choose two letters.
SVar:PutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl+ChosenLetterNameStarts | CounterType$ P1P1 | StackDescription$ SpellDescription | SpellDescription$ Put a +1/+1 counter on each creature you control whose name begins with either of the chosen letters.
DeckHas:Ability$Counters
Oracle:Choose two letters. Put a +1/+1 counter on each creature you control whose name begins with either of the chosen letters.
9 changes: 9 additions & 0 deletions forge-gui/res/cardsfolder/m/monkey_monkey_monkey.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Name:Monkey Monkey Monkey
ManaCost:3 G
Types:Creature Monkey
PT:1/1
K:ETBReplacement:Other:DBChooseLetter
SVar:DBChooseLetter:DB$ ChooseLetter | SpellDescription$ As CARDNAME enters, choose a letter.
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ X | AddToughness$ X | Description$ CARDNAME gets +1/+1 for each nonland permanent whose name begins with the chosen letter.
SVar:X:Count$Valid Permanent.nonLand+ChosenLetterNameStarts
Oracle:As Monkey Monkey Monkey enters, choose a letter.\nMonkey Monkey Monkey gets +1/+1 for each nonland permanent whose name begins with the chosen letter.
9 changes: 9 additions & 0 deletions forge-gui/res/cardsfolder/s/staff_of_the_letter_magus.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Name:Staff of the Letter Magus
ManaCost:3
Types:Artifact
K:ETBReplacement:Other:DBChooseLetter
SVar:DBChooseLetter:DB$ ChooseLetter | Consonant$ True | Exclude$ N,R,S,T | SpellDescription$ As CARDNAME enters, choose a consonant other than N, R, S, or T.
T:Mode$ SpellCast | ValidCard$ Card | TriggerZones$ Battlefield | Execute$ TrigGainLife | TriggerDescription$ Whenever a player casts a spell, you gain 1 life for each time the chosen letter appears in that spell's name.
SVar:TrigGainLife:DB$ GainLife | LifeAmount$ Count$TriggeredAmountChosenLetterInName
DeckHas:Ability$LifeGain
Oracle:As Staff of the Letter Magus enters, choose a consonant other than N, R, S, or T.\nWhenever a player casts a spell, you gain 1 life for each time the chosen letter appears in that spell's name.
1 change: 1 addition & 0 deletions forge-gui/res/languages/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,7 @@ lblThereNoCardInPlayerZone=There are no cards in {0} {1}
lblPutCardsOnTheTopLibraryOrGraveyard=Put {0} on the top of library or graveyard?
lblLibrary=Library
lblGraveyard=Graveyard
lblChooseLetter=Choose a letter
lblAssignSectorCreature=Assign {0} to a sector
lblAssignSprocket=Assign {0} to a sprocket
lblAssignSprocketCurrentCount=(Contains {0} Contraption(s))
Expand Down
7 changes: 7 additions & 0 deletions forge-gui/res/tokenscripts/w_2_2_cat_flying.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Name:Cat Token
ManaCost:no cost
Colors:white
Types:Creature Cat
PT:2/2
K:Flying
Oracle:Flying
8 changes: 8 additions & 0 deletions forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,14 @@ public static String composeCardText(final CardStateView state, final GameView g
area.append(Lang.joinHomogenous(card.getChosenCards())).append(")");
}

// chosen letters
if (card.getChosenLetters() != null && !card.getChosenLetters().isEmpty()) {
if (area.length() != 0) area.append("\n");
List<String> cl = card.getChosenLetters();
area.append("(chosen letter").append(cl.size() > 1 ? "s: " : ": ");
area.append(StringUtils.join(cl, ", ")).append(")");
}

// chosen number
if (!card.getChosenNumber().isEmpty()) {
area.append("\n");
Expand Down
11 changes: 11 additions & 0 deletions forge-gui/src/main/java/forge/player/PlayerControllerHuman.java
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,17 @@ private void sortCreatureTypes(List<String> types) {
}
}

@Override
public List<String> chooseLetter(int n, SpellAbility sa, List<String> letters) {
List<String> chosen = Lists.newArrayList();
for (int i = 0; i < n; i++) {
String choice = getGui().one(Localizer.getInstance().getMessage("lblChooseLetter"), letters);
letters.remove(choice);
chosen.add(choice);
}
return chosen;
}

@Override
public String chooseSector(Card assignee, String ai, List<String> sectors) {
String prompt;
Expand Down
Loading