diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index d21715165df..83e82191061 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -43,6 +43,7 @@ import java.util.stream.Collectors; public final class CardDb implements ICardDatabase, IDeckGenPool { public final static String foilSuffix = "+"; public final static char NameSetSeparator = '|'; + public final static String colorIDPrefix = "#"; private final String exlcudedCardName = "Concentrate"; private final String exlcudedCardSet = "DS0"; @@ -91,13 +92,19 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { public int artIndex; public boolean isFoil; public String collectorNumber; + public Set colorID; private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber) { + this(name, edition, artIndex, isFoil, collectorNumber, null); + } + + private CardRequest(String name, String edition, int artIndex, boolean isFoil, String collectorNumber, Set colorID) { cardName = name; this.edition = edition; this.artIndex = artIndex; this.isFoil = isFoil; this.collectorNumber = collectorNumber; + this.colorID = colorID; } public static boolean isFoilCardName(final String cardName){ @@ -126,6 +133,14 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { return requestInfo + NameSetSeparator + artIndex; } + public static String compose(String cardName, String setCode, int artIndex, Set colorID) { + String requestInfo = compose(cardName, setCode); + artIndex = Math.max(artIndex, IPaperCard.DEFAULT_ART_INDEX); + String cid = colorID == null ? "" : NameSetSeparator + + colorID.toString().replace("[", colorIDPrefix).replace(", ", colorIDPrefix).replace("]", ""); + return requestInfo + NameSetSeparator + artIndex + cid; + } + public static String compose(String cardName, String setCode, String collectorNumber) { String requestInfo = compose(cardName, setCode); // CollectorNumber will be wrapped in square brackets @@ -155,6 +170,10 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { return s.startsWith("[") && s.endsWith("]"); } + private static boolean isColorIDString(String s) { + return s.startsWith(colorIDPrefix); + } + private static boolean isArtIndex(String s) { return StringUtils.isNumeric(s) && s.length() <= 2 ; // only artIndex between 1-99 } @@ -172,7 +191,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { String cardName = info[0]; String setCode = info[1]; int artIndex = Integer.parseInt(info[2]); - return new CardRequest(cardName, setCode, artIndex, isFoil, IPaperCard.NO_COLLECTOR_NUMBER); + return new CardRequest(cardName, setCode, artIndex, isFoil, IPaperCard.NO_COLLECTOR_NUMBER, null); } catch (NumberFormatException ex){ return null; } } @@ -184,22 +203,29 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { int setPos; int artPos; int cNrPos; + int clrPos; if (info.length >= 4) { // name|set|artIndex|[collNr] setPos = isSetCode(info[1]) ? 1 : -1; artPos = isArtIndex(info[2]) ? 2 : -1; cNrPos = isCollectorNumber(info[3]) ? 3 : -1; + int pos = cNrPos > 0 ? -1 : 3; + clrPos = pos > 0 ? isColorIDString(info[pos]) ? pos : -1 : -1; } else if (info.length == 3) { // name|set|artIndex (or CollNr) setPos = isSetCode(info[1]) ? 1 : -1; artPos = isArtIndex(info[2]) ? 2 : -1; cNrPos = isCollectorNumber(info[2]) ? 2 : -1; + int pos = cNrPos > 0 ? -1 : 2; + clrPos = pos > 0 ? isColorIDString(info[pos]) ? pos : -1 : -1; } else if (info.length == 2) { // name|set (or artIndex, even if not possible via compose) setPos = isSetCode(info[1]) ? 1 : -1; artPos = isArtIndex(info[1]) ? 1 : -1; cNrPos = -1; + clrPos = -1; } else { setPos = -1; artPos = -1; cNrPos = -1; + clrPos = -1; } String cardName = info[0]; boolean isFoil = false; @@ -210,6 +236,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { int artIndex = artPos > 0 ? Integer.parseInt(info[artPos]) : IPaperCard.NO_ART_INDEX; // default: no art index String collectorNumber = cNrPos > 0 ? info[cNrPos].substring(1, info[cNrPos].length() - 1) : IPaperCard.NO_COLLECTOR_NUMBER; String setCode = setPos > 0 ? info[setPos] : null; + Set colorID = clrPos > 0 ? Arrays.stream(info[clrPos].substring(1).split(colorIDPrefix)).collect(Collectors.toSet()) : null; if (setCode != null && setCode.equals(CardEdition.UNKNOWN.getCode())) { // ??? setCode = null; } @@ -225,7 +252,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { // finally, check whether any between artIndex and CollectorNumber has been set if (collectorNumber.equals(IPaperCard.NO_COLLECTOR_NUMBER) && artIndex == IPaperCard.NO_ART_INDEX) artIndex = IPaperCard.DEFAULT_ART_INDEX; - return new CardRequest(cardName, setCode, artIndex, isFoil, collectorNumber); + return new CardRequest(cardName, setCode, artIndex, isFoil, collectorNumber, colorID); } } @@ -570,6 +597,13 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { return tryGetCard(request); } + @Override + public PaperCard getCard(final String cardName, String setCode, int artIndex, Set colorID) { + String reqInfo = CardRequest.compose(cardName, setCode, artIndex, colorID); + CardRequest request = CardRequest.fromString(reqInfo); + return tryGetCard(request); + } + private PaperCard tryGetCard(CardRequest request) { // Before doing anything, check that a non-null request has been provided if (request == null) @@ -581,8 +615,9 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { // MOST of the extensions have two short codes, 141 out of 221 (so far) // ALSO: Set Code are always UpperCase CardEdition edition = editions.get(reqEditionCode.toUpperCase()); + return this.getCardFromSet(request.cardName, edition, request.artIndex, - request.collectorNumber, request.isFoil); + request.collectorNumber, request.isFoil, request.colorID); } // 2. Card lookup in edition with specified filter didn't work. @@ -624,8 +659,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { } @Override - public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, - String collectorNumber, boolean isFoil) { + public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil) { + return getCardFromSet(cardName, edition, artIndex, collectorNumber, isFoil, null); + } + + @Override + public PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil, Set colorID) { if (edition == null || cardName == null) // preview cards return null; // No cards will be returned @@ -659,7 +698,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { while (!candidate.hasImage() && candidatesIterator.hasNext()) candidate = candidatesIterator.next(); candidate = candidate.hasImage() ? candidate : firstCandidate; - return isFoil ? candidate.getFoiled() : candidate; + return isFoil ? candidate.getFoiled().getColorIDVersion(colorID) : candidate.getColorIDVersion(colorID); } /* @@ -702,6 +741,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, filter); } + @Override + public PaperCard getCardFromEditions(final String cardInfo, final CardArtPreference artPreference, int artIndex, Set colorID) { + return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, null, false, null, colorID); + } + /* * =============================================== * 4. SPECIALISED CARD LOOKUP BASED ON @@ -776,6 +820,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { private PaperCard tryToGetCardFromEditions(String cardInfo, CardArtPreference artPreference, int artIndex, Date releaseDate, boolean releasedBeforeFlag, Predicate filter){ + return this.tryToGetCardFromEditions(cardInfo, artPreference, artIndex, releaseDate, releasedBeforeFlag, filter, null); + } + + private PaperCard tryToGetCardFromEditions(String cardInfo, CardArtPreference artPreference, int artIndex, + Date releaseDate, boolean releasedBeforeFlag, Predicate filter, Set colorID){ if (cardInfo == null) return null; final CardRequest cr = CardRequest.fromString(cardInfo); @@ -856,7 +905,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { } candidate = candidate.hasImage() ? candidate : firstCandidate; //If any, we're sure that at least one candidate is always returned despite it having any image - return cr.isFoil ? candidate.getFoiled() : candidate; + return cr.isFoil ? candidate.getFoiled().getColorIDVersion(colorID) : candidate.getColorIDVersion(colorID); } @Override @@ -1129,6 +1178,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { if (artCount >= IPaperCard.DEFAULT_ART_INDEX) { sb.append(CardDb.NameSetSeparator).append(card.getArtIndex()); // indexes start at 1 to match image file name conventions } + if (card.getColorID() != null) { + sb.append(CardDb.NameSetSeparator); + for (String color : card.getColorID()) + sb.append(CardDb.colorIDPrefix).append(color); + } } return sb; diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index c6e8cdc29c7..3811eeb3ded 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -53,6 +53,7 @@ public final class CardRules implements ICardCharacteristics { private String meldWith; private String partnerWith; private boolean addsWildCardColor; + private int setColorID; private boolean custom; public CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) { @@ -72,6 +73,8 @@ public final class CardRules implements ICardCharacteristics { meldWith = ""; partnerWith = ""; addsWildCardColor = false; + setColorID = 0; + //calculate color identity byte colMask = calculateColorIdentity(mainPart); @@ -95,6 +98,7 @@ public final class CardRules implements ICardCharacteristics { meldWith = newRules.meldWith; partnerWith = newRules.partnerWith; addsWildCardColor = newRules.addsWildCardColor; + setColorID = newRules.setColorID; tokens = newRules.tokens; } @@ -397,6 +401,10 @@ public final class CardRules implements ICardCharacteristics { return addsWildCardColor; } + public int getSetColorID() { + return setColorID; + } + // vanguard card fields, they don't use sides. private int deltaHand; private int deltaLife; @@ -448,6 +456,7 @@ public final class CardRules implements ICardCharacteristics { private String meldWith = ""; private String partnerWith = ""; private boolean addsWildCardColor = false; + private int setColorID = 0; private String handLife = null; private String normalizedName = ""; private Set supportedFunctionalVariants = null; @@ -512,6 +521,7 @@ public final class CardRules implements ICardCharacteristics { result.meldWith = this.meldWith; result.partnerWith = this.partnerWith; result.addsWildCardColor = this.addsWildCardColor; + result.setColorID = this.setColorID; if (!tokens.isEmpty()) { result.tokens = tokens; } @@ -687,6 +697,8 @@ public final class CardRules implements ICardCharacteristics { value = colonPos > 0 ? value.substring(1+colonPos) : null; face.addSVar(variable, value); + } else if (key.startsWith("SETCOLORID")) { + this.setColorID = Integer.parseInt(value); } break; diff --git a/forge-core/src/main/java/forge/card/ICardDatabase.java b/forge-core/src/main/java/forge/card/ICardDatabase.java index 4618e78f83f..15761b234fa 100644 --- a/forge-core/src/main/java/forge/card/ICardDatabase.java +++ b/forge-core/src/main/java/forge/card/ICardDatabase.java @@ -7,6 +7,7 @@ import forge.item.PaperCard; import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.Set; public interface ICardDatabase extends Iterable { /** @@ -50,18 +51,21 @@ public interface ICardDatabase extends Iterable { // [NEW Methods] Including the card CollectorNumber as criterion for DB lookup PaperCard getCard(String cardName, String edition, String collectorNumber); PaperCard getCard(String cardName, String edition, int artIndex, String collectorNumber); + PaperCard getCard(String cardName, String edition, int artIndex, Set colorID); // 2. Card Lookup from a single Expansion Set PaperCard getCardFromSet(String cardName, CardEdition edition, boolean isFoil); // NOT yet used, included for API symmetry PaperCard getCardFromSet(String cardName, CardEdition edition, String collectorNumber, boolean isFoil); PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, boolean isFoil); PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil); + PaperCard getCardFromSet(String cardName, CardEdition edition, int artIndex, String collectorNumber, boolean isFoil, Set colorID); // 3. Card lookup based on CardArtPreference Selection Policy PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference); PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, Predicate filter); PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex); PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Predicate filter); + PaperCard getCardFromEditions(String cardName, CardArtPreference artPreference, int artIndex, Set colorID); // 4. Specialised Card Lookup on CardArtPreference Selection and Release Date PaperCard getCardFromEditionsReleasedBefore(String cardName, CardArtPreference artPreference, Date releaseDate); diff --git a/forge-core/src/main/java/forge/deck/CardPool.java b/forge-core/src/main/java/forge/deck/CardPool.java index 34265f136d5..fcf68572fc4 100644 --- a/forge-core/src/main/java/forge/deck/CardPool.java +++ b/forge-core/src/main/java/forge/deck/CardPool.java @@ -54,7 +54,7 @@ public class CardPool extends ItemPool { public void add(final String cardRequest, final int amount) { CardDb.CardRequest request = CardDb.CardRequest.fromString(cardRequest); - this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.artIndex, amount); + this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.artIndex, amount, false, request.colorID); } public void add(final String cardName, final String setCode) { @@ -66,14 +66,14 @@ public class CardPool extends ItemPool { } public void add(final String cardName, final String setCode, final int amount, boolean addAny) { - this.add(cardName, setCode, IPaperCard.NO_ART_INDEX, amount, addAny); + this.add(cardName, setCode, IPaperCard.NO_ART_INDEX, amount, addAny, null); } // NOTE: ART indices are "1" -based public void add(String cardName, String setCode, int artIndex, final int amount) { - this.add(cardName, setCode, artIndex, amount, false); + this.add(cardName, setCode, artIndex, amount, false, null); } - public void add(String cardName, String setCode, int artIndex, final int amount, boolean addAny) { + public void add(String cardName, String setCode, int artIndex, final int amount, boolean addAny, Set colorID) { Map dbs = StaticData.instance().getAvailableDatabases(); PaperCard paperCard = null; String selectedDbName = ""; @@ -83,7 +83,7 @@ public class CardPool extends ItemPool { for (Map.Entry entry: dbs.entrySet()){ String dbName = entry.getKey(); CardDb db = entry.getValue(); - paperCard = db.getCard(cardName, setCode, artIndex); + paperCard = db.getCard(cardName, setCode, artIndex, colorID); if (paperCard != null) { selectedDbName = dbName; break; @@ -125,7 +125,7 @@ public class CardPool extends ItemPool { int cnt = artGroups[i - 1]; if (cnt <= 0) continue; - PaperCard randomCard = cardDb.getCard(cardName, setCode, i); + PaperCard randomCard = cardDb.getCard(cardName, setCode, i, colorID); this.add(randomCard, cnt); } } diff --git a/forge-core/src/main/java/forge/deck/Deck.java b/forge-core/src/main/java/forge/deck/Deck.java index 8cbbcbcc3b9..974bd4592d1 100644 --- a/forge-core/src/main/java/forge/deck/Deck.java +++ b/forge-core/src/main/java/forge/deck/Deck.java @@ -337,7 +337,7 @@ public class Deck extends DeckBase implements Iterable originalRequest : originalCardRequests){ String cardRequest = originalRequest.getLeft(); diff --git a/forge-core/src/main/java/forge/item/IPaperCard.java b/forge-core/src/main/java/forge/item/IPaperCard.java index 72d2f9b6b8b..51cf00fe92f 100644 --- a/forge-core/src/main/java/forge/item/IPaperCard.java +++ b/forge-core/src/main/java/forge/item/IPaperCard.java @@ -234,6 +234,7 @@ public interface IPaperCard extends InventoryItem, Serializable { String getEdition(); String getCollectorNumber(); String getFunctionalVariant(); + Set getColorID(); int getArtIndex(); boolean isFoil(); boolean isToken(); diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 02116812ec6..7ecbce6c09b 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -28,6 +28,8 @@ import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.io.ObjectInputStream; +import java.util.Optional; +import java.util.Set; /** * A lightweight version of a card that matches real-world cards, to use outside of games (eg. inventory, decks, trade). @@ -55,6 +57,7 @@ public class PaperCard implements Comparable, InventoryItemFromSet, private final boolean foil; private Boolean hasImage; private final boolean noSell; + private Set colorID; private String sortableName; private final String functionalVariant; @@ -85,6 +88,11 @@ public class PaperCard implements Comparable, InventoryItemFromSet, return functionalVariant; } + @Override + public Set getColorID() { + return colorID; + } + @Override public int getArtIndex() { return artIndex; @@ -156,7 +164,16 @@ public class PaperCard implements Comparable, InventoryItemFromSet, this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, false); return sellable; } - + public PaperCard getColorIDVersion(Set colors) { + if (colors == null && this.colorID == null) + return this; + if (this.colorID != null && this.colorID.equals(colors)) + return this; + if (colors != null && colors.equals(this.colorID)) + return this; + return new PaperCard(this.rules, this.edition, this.rarity, + this.artIndex, this.foil, String.valueOf(collectorNumber), this.artist, this.functionalVariant, this.noSell, colors); + } @Override public String getItemType() { final Localizer localizer = Localizer.getInstance(); @@ -190,6 +207,12 @@ public class PaperCard implements Comparable, InventoryItemFromSet, public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0, final int artIndex0, final boolean foil0, final String collectorNumber0, final String artist0, final String functionalVariant, final boolean noSell0) { + this(rules0, edition0, rarity0, artIndex0, foil0, collectorNumber0, artist0, functionalVariant, noSell0, null); + } + + public PaperCard(final CardRules rules0, final String edition0, final CardRarity rarity0, + final int artIndex0, final boolean foil0, final String collectorNumber0, + final String artist0, final String functionalVariant, final boolean noSell0, final Set colorID0) { if (rules0 == null || edition0 == null || rarity0 == null) { throw new IllegalArgumentException("Cannot create card without rules, edition or rarity"); } @@ -206,6 +229,7 @@ public class PaperCard implements Comparable, InventoryItemFromSet, sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(rules0.getName())); this.functionalVariant = functionalVariant != null ? functionalVariant : IPaperCard.NO_FUNCTIONAL_VARIANT; noSell = noSell0; + colorID = colorID0; } public static PaperCard FAKE_CARD = new PaperCard(CardRules.getUnsupportedCardNamed("Fake Card"), "Fake Edition", CardRarity.Common); @@ -232,6 +256,9 @@ public class PaperCard implements Comparable, InventoryItemFromSet, } if (!getCollectorNumber().equals(other.getCollectorNumber())) return false; + // colorID can be NULL + if (getColorID() != other.getColorID()) + return false; return (other.foil == foil) && (other.artIndex == artIndex); } @@ -244,10 +271,11 @@ public class PaperCard implements Comparable, InventoryItemFromSet, public int hashCode() { final int code = (name.hashCode() * 11) + (edition.hashCode() * 59) + (artIndex * 2) + (getCollectorNumber().hashCode() * 383); + final int id = Optional.ofNullable(colorID).map(Set::hashCode).orElse(0); if (foil) { - return code + 1; + return code + id + 1; } - return code; + return code + id; } // FIXME: Check diff --git a/forge-core/src/main/java/forge/item/PaperToken.java b/forge-core/src/main/java/forge/item/PaperToken.java index c13b0435880..4f96abee9e0 100644 --- a/forge-core/src/main/java/forge/item/PaperToken.java +++ b/forge-core/src/main/java/forge/item/PaperToken.java @@ -2,6 +2,7 @@ package forge.item; import java.util.ArrayList; import java.util.Locale; +import java.util.Set; import forge.card.*; import org.apache.commons.lang3.StringUtils; @@ -152,6 +153,11 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard { return IPaperCard.NO_FUNCTIONAL_VARIANT; } + @Override + public Set getColorID() { + return null; + } + @Override public int getArtIndex() { return artIndex; diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index e6fe765968f..46dd526406d 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -572,6 +572,10 @@ public class GameAction { game.getTriggerHandler().registerActiveTrigger(copied, false); } + if (c.hasChosenColorSpire()) { + copied.setChosenColorID(ImmutableSet.copyOf(c.getChosenColorID())); + } + // update state for view copied.updateStateForView(); @@ -2223,14 +2227,18 @@ public class GameAction { c.setChosenNumber(chosen); } for (Card c : spires) { - if (!c.hasChosenColor()) { - List colorChoices = new ArrayList<>(MagicColor.Constant.ONLY_COLORS); - String prompt = CardTranslation.getTranslatedName(c.getName()) + ": " + - Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(2)); - SpellAbility sa = new SpellAbility.EmptySa(ApiType.ChooseColor, c, takesAction); - sa.putParam("AILogic", "MostProminentInComputerDeck"); - List chosenColors = takesAction.getController().chooseColors(prompt, sa, 2, 2, colorChoices); - c.setChosenColors(chosenColors); + // TODO: only do this for the AI, for the player part, get the encoded color from the deck file and pass + // it to either player or the papercard object so it feels like rule based for the player side.. + if (!c.hasChosenColorSpire()) { + if (takesAction.isAI()) { + List colorChoices = new ArrayList<>(MagicColor.Constant.ONLY_COLORS); + String prompt = CardTranslation.getTranslatedName(c.getName()) + ": " + + Localizer.getInstance().getMessage("lblChooseNColors", Lang.getNumeral(2)); + SpellAbility sa = new SpellAbility.EmptySa(ApiType.ChooseColor, c, takesAction); + sa.putParam("AILogic", "MostProminentInComputerDeck"); + Set chosenColors = new HashSet<>(takesAction.getController().chooseColors(prompt, sa, 2, 2, colorChoices)); + c.setChosenColorID(chosenColors); + } } } takesAction = game.getNextPlayerAfter(takesAction); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java index 7b5750284d5..170c07fcfee 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CloneEffect.java @@ -199,6 +199,8 @@ public class CloneEffect extends SpellAbilityEffect { if (sa.hasParam("RememberCloneOrigin")) { tgtCard.addRemembered(cardToCopy); } + // spire + tgtCard.setChosenColorID(cardToCopy.getChosenColorID()); game.fireEvent(new GameEventCardStatsChanged(tgtCard)); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java index 08ac7418f67..17ee13acdb4 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java @@ -318,6 +318,8 @@ public class CopyPermanentEffect extends TokenEffectBase { copy.setState(copy.getCurrentStateName(), true, true); } } + // spire + copy.setChosenColorID(original.getChosenColorID()); copy.setTokenSpawningAbility(sa); copy.setGamePieceType(GamePieceType.TOKEN); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java index 5d393c39783..50b209c4cfe 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java @@ -119,7 +119,7 @@ public class ManaEffect extends SpellAbilityEffect { } } - if (choiceString.toString().isEmpty() && "Combo ColorIdentity".equals(abMana.getOrigProduced())) { + if (choiceString.toString().isEmpty() && ("Combo ColorIdentity".equals(abMana.getOrigProduced()) || "Combo Spire".equals(abMana.getOrigProduced()))) { // No mana could be produced here (non-EDH match?), so cut short continue; } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 383828f37bc..1f5819e494b 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -293,6 +293,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr private String chosenType2 = ""; private List notedTypes = new ArrayList<>(); private List chosenColors; + private Set chosenColorID; private List chosenName = new ArrayList<>(); private Integer chosenNumber; private Player chosenPlayer; @@ -399,6 +400,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr view.updateSickness(this); view.updateClassLevel(this); view.updateDraftAction(this); + if (paperCard != null) + setChosenColorID(paperCard.getColorID()); } public boolean changeToState(final CardStateName state) { @@ -2118,7 +2121,19 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr public boolean hasChosenColor(String s) { return chosenColors != null && chosenColors.contains(s); } - + public final Set getChosenColorID() { + if (chosenColorID == null) { + return Sets.newHashSet(); + } + return chosenColorID; + } + public final void setChosenColorID(final Set s) { + chosenColorID = s; + view.updateChosenColorID(this); + } + public boolean hasChosenColorSpire() { + return chosenColorID != null && !chosenColorID.isEmpty(); + } public final Card getChosenCard() { return getChosenCards().getFirst(); } diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 1f25f0380d2..5ce321d5fac 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -433,7 +433,12 @@ public class CardView extends GameEntityView { void updateChosenColors(Card c) { set(TrackableProperty.ChosenColors, c.getChosenColors()); } - + public Set getChosenColorID() { + return get(TrackableProperty.ChosenColorID); + } + void updateChosenColorID(Card c) { + set(TrackableProperty.ChosenColorID, c.getChosenColorID()); + } public FCollectionView getMergedCardsCollection() { return get(TrackableProperty.MergedCardsCollection); } 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 8fd49d86057..742a5dc318d 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -654,6 +654,10 @@ public class AbilityManaPart implements java.io.Serializable { if (origProduced.contains("Chosen")) { origProduced = origProduced.replace("Chosen", getChosenColor(sa)); } + // replace Chosen for Spire colors + if (origProduced.contains("ColorID")) { + origProduced = origProduced.replace("ColorID", getChosenColorID(sa)); + } if (origProduced.contains("NotedColors")) { // Should only be used for Paliano, the High City if (sa.getActivatingPlayer() == null) { @@ -697,6 +701,21 @@ public class AbilityManaPart implements java.io.Serializable { return sb.length() == 0 ? "" : sb.substring(0, sb.length() - 1); } + public String getChosenColorID(SpellAbility sa) { + if (sa == null) { + return ""; + } + Card card = sa.getHostCard(); + if (card != null && card.hasChosenColorSpire()) { + StringBuilder values = new StringBuilder(); + for (String s : card.getChosenColorID()) { + values.append(MagicColor.toShortString(MagicColor.fromName(s))).append(" "); + } + return values.toString(); + } + return ""; + } + public String getChosenColor(SpellAbility sa) { if (sa == null) { return ""; diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index 58481162c46..9ce53471afb 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -67,6 +67,7 @@ public enum TrackableProperty { ChosenType2(TrackableTypes.StringType), NotedTypes(TrackableTypes.StringListType), ChosenColors(TrackableTypes.StringListType), + ChosenColorID(TrackableTypes.StringSetType), ChosenCards(TrackableTypes.CardViewCollectionType), ChosenNumber(TrackableTypes.StringType), StoredRolls(TrackableTypes.StringListType), diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java index 6c5dbc3dc0d..853c5af9df9 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java @@ -20,8 +20,10 @@ package forge.screens.deckeditor.controllers; import java.awt.Toolkit; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; +import java.util.HashSet; import java.util.List; import java.util.Map.Entry; +import java.util.Set; import javax.swing.JMenu; import javax.swing.JPopupMenu; @@ -30,6 +32,7 @@ import javax.swing.SwingUtilities; import com.google.common.collect.Iterables; +import forge.card.MagicColor; import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.DeckBase; @@ -65,6 +68,7 @@ import forge.toolbox.FLabel; import forge.toolbox.FSkin; import forge.util.Aggregates; import forge.util.ItemPool; +import forge.util.Lang; import forge.util.Localizer; import forge.view.FView; @@ -576,5 +580,23 @@ public abstract class ACEditorBase 0) { + GuiUtils.addMenuItem(menu, label, null, () -> { + Set colors = new HashSet<>(GuiChoose.getChoices(localizer.getMessage("lblChooseNColors", Lang.getNumeral(val)), val, val, MagicColor.Constant.ONLY_COLORS)); + // make an updated version + PaperCard updated = existingCard.getColorIDVersion(colors); + // remove *quantity* instances of existing card + CDeckEditorUI.SINGLETON_INSTANCE.removeSelectedCards(false, 1); + // add *quantity* into the deck and set them as selected + cardManager.addItem(updated, 1); + cardManager.setSelectedItem(updated); + }, true, true); + } + } } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java index be000243440..f3be724aab0 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/CEditorConstructed.java @@ -375,6 +375,7 @@ public final class CEditorConstructed extends CDeckEditor { if (foilAvailable) { cmb.addMakeFoils(); } + cmb.addSetColorID(); } /* (non-Javadoc) diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java index 3a2abbc3234..8578e757cd5 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java +++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java @@ -12,6 +12,7 @@ import forge.Forge.KeyInputAdapter; import forge.Graphics; import forge.assets.*; import forge.card.CardEdition; +import forge.card.MagicColor; import forge.deck.io.DeckPreferences; import forge.gamemodes.limited.BoosterDraft; import forge.gamemodes.planarconquest.ConquestUtil; @@ -1799,6 +1800,8 @@ public class FDeckEditor extends TabPageScreen { CardManagerPage cardSourceSection; DeckSection destination = DeckSection.matchingSection(card); final DeckSectionPage destinationPage = parentScreen.getPageForSection(destination); + // val for colorID setup + int val; switch (deckSection) { default: case Main: @@ -1841,6 +1844,19 @@ public class FDeckEditor extends TabPageScreen { } } addCommanderItems(menu, card); + if ((val = card.getRules().getSetColorID()) > 0) { + menu.addItem(new FMenuItem(Forge.getLocalizer().getMessage("lblColorIdentity"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, e -> { + //sort options so current option is on top and selected by default + Set colorChoices = new HashSet<>(MagicColor.Constant.ONLY_COLORS); + GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblChooseAColor", Lang.getNumeral(val)), val, val, colorChoices, new Callback<>() { + @Override + public void run(List result) { + addCard(card.getColorIDVersion(new HashSet<>(result))); + removeCard(card); + } + }); + })); + } break; case Sideboard: cardSourceSection = parentScreen.isLimitedEditor() ? parentScreen.getMainDeckPage() : parentScreen.getCatalogPage(); @@ -1880,6 +1896,19 @@ public class FDeckEditor extends TabPageScreen { } } addCommanderItems(menu, card); + if ((val = card.getRules().getSetColorID()) > 0) { + menu.addItem(new FMenuItem(Forge.getLocalizer().getMessage("lblColorIdentity"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, e -> { + //sort options so current option is on top and selected by default + Set colorChoices = new HashSet<>(MagicColor.Constant.ONLY_COLORS); + GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblChooseAColor", Lang.getNumeral(val)), val, val, colorChoices, new Callback<>() { + @Override + public void run(List result) { + addCard(card.getColorIDVersion(new HashSet<>(result))); + removeCard(card); + } + }); + })); + } break; case Commander: if (parentScreen.editorType != EditorType.PlanarConquest || isPartnerCommander(card)) { diff --git a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java index 60097618cc1..1c6d0d26b2d 100644 --- a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java +++ b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java @@ -44,6 +44,7 @@ public class ImageView extends ItemView { private static final float PADDING = Utils.scale(5); private static final float PILE_SPACING_Y = 0.1f; private static final FSkinFont LABEL_FONT = FSkinFont.get(12); + private TextRenderer textRenderer = new TextRenderer(true); private static FSkinColor getGroupHeaderForeColor() { if (Forge.isMobileAdventureMode) @@ -1029,6 +1030,7 @@ public class ImageView extends ItemView { private boolean selected, deckSelectMode, showRanking; private final float IMAGE_SIZE = CardRenderer.MANA_SYMBOL_SIZE; private DeckProxy deckProxy = null; + private StringBuffer colorID = new StringBuffer(); private FImageComplex deckCover = null; private Texture dpImg = null; //private TextureRegion tr; @@ -1055,6 +1057,20 @@ public class ImageView extends ItemView { draftRankImage = FSkinImage.DRAFTRANK_C; } } + if (((PaperCard) item).getColorID() != null) { + for (String s : ((PaperCard) item).getColorID()) { + if ("white".equalsIgnoreCase(s)) + colorID.append("{W}"); + if ("green".equalsIgnoreCase(s)) + colorID.append("{G}"); + if ("red".equalsIgnoreCase(s)) + colorID.append("{R}"); + if ("blue".equalsIgnoreCase(s)) + colorID.append("{U}"); + if ("black".equalsIgnoreCase(s)) + colorID.append("{B}"); + } + } } } @@ -1136,6 +1152,10 @@ public class ImageView extends ItemView { } } } + // spire colors + if (!colorID.isEmpty()) { + textRenderer.drawText(g, colorID.toString(), FSkinFont.forHeight(w / 5), Color.WHITE, x, y + h / 4, w, h, y, h, false, Align.center, true); + } } else if (item instanceof ConquestCommander) { CardRenderer.drawCard(g, ((ConquestCommander) item).getCard(), x, y, w, h, pos); } else if (deckSelectMode) { diff --git a/forge-gui/res/cardsfolder/c/cryptic_spires.txt b/forge-gui/res/cardsfolder/c/cryptic_spires.txt index 9bcbec5ba29..d7031b33257 100644 --- a/forge-gui/res/cardsfolder/c/cryptic_spires.txt +++ b/forge-gui/res/cardsfolder/c/cryptic_spires.txt @@ -4,5 +4,6 @@ Types:Land Text:As you create your deck, circle two of the colors below. R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True -A:AB$ Mana | Cost$ T | Produced$ Combo Chosen | SpellDescription$ Add one mana of either of the circled colors. +A:AB$ Mana | Cost$ T | Produced$ Combo ColorID | SpellDescription$ Add one mana of either of the circled colors. Oracle:As you create your deck, circle two of the colors below.\nCryptic Spires enters tapped.\n{T}: Add one mana of either of the circled colors. +SETCOLORID:2 diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java index 59ebe64a76c..76cc1c0dcdc 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -487,6 +487,16 @@ public class CardDetailUtil { area.append(")"); } + // chosen spire + if (card.getChosenColorID() != null && !card.getChosenColorID().isEmpty()) { + if (area.length() != 0) { + area.append("\n"); + } + area.append("(").append(Localizer.getInstance().getMessage("lblSelected")).append(": "); + area.append(Lang.joinHomogenous(card.getChosenColorID().stream().map(DeckRecognizer::getLocalisedMagicColorName).collect(Collectors.toList()))); + area.append(")"); + } + // chosen color if (card.getChosenColors() != null && !card.getChosenColors().isEmpty()) { if (area.length() != 0) { diff --git a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java index 77e25d479b6..2f910ef7ecc 100644 --- a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java +++ b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java @@ -57,8 +57,9 @@ public enum ColumnDef { NAME("lblName", "lblName", 180, false, SortState.ASC, from -> { if (from.getKey() instanceof PaperCard) { + String spire = ((PaperCard) from.getKey()).getColorID() == null ? "" : ((PaperCard) from.getKey()).getColorID().toString(); String sortableName = ((PaperCard)from.getKey()).getSortableName(); - return sortableName == null ? TextUtil.toSortableName(from.getKey().getName()) : sortableName; + return sortableName == null ? TextUtil.toSortableName(from.getKey().getName() + spire) : sortableName + spire; } return TextUtil.toSortableName(from.getKey().getName()); },