deck: fixed that clipboard import can choose wrong promo card (close #7666)

This commit is contained in:
Oleg Agafonov 2025-08-10 23:59:19 +04:00
parent 2e6f26e589
commit b06682797c
14 changed files with 69 additions and 75 deletions

View file

@ -785,7 +785,7 @@ public final class SystemUtil {
* @return added cards list * @return added cards list
*/ */
private static Set<Card> addNewCardsToGame(Game game, String cardName, String setCode, int amount, Player owner) { private static Set<Card> addNewCardsToGame(Game game, String cardName, String setCode, int amount, Player owner) {
CardInfo cardInfo = CardLookup.instance.lookupCardInfo(cardName, setCode).orElse(null); CardInfo cardInfo = CardLookup.instance.lookupCardInfo(cardName, setCode, null);
if (cardInfo == null || amount <= 0) { if (cardInfo == null || amount <= 0) {
return null; return null;
} }

View file

@ -3,9 +3,10 @@ package mage.cards.decks.importer;
import mage.cards.repository.CardCriteria; import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo; import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository; import mage.cards.repository.CardRepository;
import mage.util.CardUtil;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Objects;
/** /**
* Deck import: helper class to mock cards repository * Deck import: helper class to mock cards repository
@ -14,49 +15,40 @@ public class CardLookup {
public static final CardLookup instance = new CardLookup(); public static final CardLookup instance = new CardLookup();
public Optional<CardInfo> lookupCardInfo(String name) { public CardInfo lookupCardInfo(String name) {
return Optional.ofNullable(CardRepository.instance.findPreferredCoreExpansionCard(name)); return CardRepository.instance.findPreferredCoreExpansionCard(name);
} }
public List<CardInfo> lookupCardInfo(CardCriteria criteria) { public List<CardInfo> lookupCardInfo(CardCriteria criteria) {
// can be override to make fake lookup, e.g. for tests
return CardRepository.instance.findCards(criteria); return CardRepository.instance.findCards(criteria);
} }
public Optional<CardInfo> lookupCardInfo(String name, String set) { public CardInfo lookupCardInfo(String name, String set, String cardNumber) {
if (set == null) { CardCriteria cardCriteria = new CardCriteria();
return lookupCardInfo(name); cardCriteria.name(name);
if (set != null) {
cardCriteria.setCodes(set);
}
if (cardNumber != null) {
int intCardNumber = CardUtil.parseCardNumberAsInt(cardNumber);
cardCriteria.minCardNumber(intCardNumber);
cardCriteria.maxCardNumber(intCardNumber);
}
List<CardInfo> foundCards = lookupCardInfo(cardCriteria);
CardInfo res = null;
// if possible then use strict card number
if (cardNumber != null) {
res = foundCards.stream().filter(c -> Objects.equals(c.getCardNumber(), cardNumber)).findAny().orElse(null);
} }
Optional<CardInfo> result = lookupCardInfo(new CardCriteria().name(name).setCodes(set)) if (res == null) {
.stream() res = foundCards.stream().findAny().orElse(null);
.findAny();
if (result.isPresent()) {
return result;
} }
return lookupCardInfo(name); return res;
}
public Optional<CardInfo> lookupCardInfo(String name, String set, String cardNumber) {
Optional<CardInfo> result;
try {
int intCardNumber = Integer.parseInt(cardNumber);
result = lookupCardInfo(
new CardCriteria()
.name(name)
.setCodes(set)
.minCardNumber(intCardNumber)
.maxCardNumber(intCardNumber))
.stream()
.findAny();
if (result.isPresent()) {
return result;
}
} catch (NumberFormatException ignore) {
/* ignored */
}
return lookupCardInfo(name, set);
} }
} }

View file

@ -66,14 +66,13 @@ public class CodDeckImporter extends XmlDeckImporter {
private static Function<Node, Stream<DeckCardInfo>> toDeckCardInfo(CardLookup lookup, StringBuilder errors) { private static Function<Node, Stream<DeckCardInfo>> toDeckCardInfo(CardLookup lookup, StringBuilder errors) {
return node -> { return node -> {
String name = node.getAttributes().getNamedItem("name").getNodeValue().trim(); String name = node.getAttributes().getNamedItem("name").getNodeValue().trim();
Optional<CardInfo> cardInfo = lookup.lookupCardInfo(name); CardInfo cardInfo = lookup.lookupCardInfo(name);
if (cardInfo.isPresent()) { if (cardInfo != null) {
CardInfo info = cardInfo.get();
int amount = getQuantityFromNode(node); int amount = getQuantityFromNode(node);
DeckCardInfo.makeSureCardAmountFine(amount, info.getName()); DeckCardInfo.makeSureCardAmountFine(amount, cardInfo.getName());
return Collections.nCopies( return Collections.nCopies(
amount, amount,
new DeckCardInfo(info.getName(), info.getCardNumber(), info.getSetCode()) new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())
).stream(); ).stream();
} else { } else {
errors.append("Could not find card: '").append(name).append("'\n"); errors.append("Could not find card: '").append(name).append("'\n");

View file

@ -33,11 +33,10 @@ public class DecDeckImporter extends PlainTextDeckImporter {
String lineName = line.substring(delim).trim(); String lineName = line.substring(delim).trim();
try { try {
int num = Integer.parseInt(lineNum); int num = Integer.parseInt(lineNum);
Optional<CardInfo> cardLookup = getCardLookup().lookupCardInfo(lineName); CardInfo cardInfo = getCardLookup().lookupCardInfo(lineName);
if (!cardLookup.isPresent()) { if (cardInfo == null) {
sbMessage.append("Could not find card: '").append(lineName).append("' at line ").append(lineCount).append('\n'); sbMessage.append("Could not find card: '").append(lineName).append("' at line ").append(lineCount).append('\n');
} else { } else {
CardInfo cardInfo = cardLookup.get();
DeckCardInfo.makeSureCardAmountFine(num, cardInfo.getName()); DeckCardInfo.makeSureCardAmountFine(num, cardInfo.getName());
DeckCardInfo deckCardInfo = new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode()); DeckCardInfo deckCardInfo = new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode());
for (int i = 0; i < num; i++) { for (int i = 0; i < num; i++) {

View file

@ -32,7 +32,7 @@ public class DekDeckImporter extends PlainTextDeckImporter {
} }
boolean isSideboard = "true".equals(extractAttribute(line, "Sideboard")); boolean isSideboard = "true".equals(extractAttribute(line, "Sideboard"));
CardInfo cardInfo = getCardLookup().lookupCardInfo(cardName).orElse(null); CardInfo cardInfo = getCardLookup().lookupCardInfo(cardName);
if (cardInfo == null) { if (cardInfo == null) {
sbMessage.append("Could not find card: '").append(cardName).append("' at line ").append(lineCount).append('\n'); sbMessage.append("Could not find card: '").append(cardName).append("' at line ").append(lineCount).append('\n');
} else { } else {

View file

@ -29,9 +29,9 @@ public class DraftLogImporter extends PlainTextDeckImporter {
Matcher pickMatcher = PICK_PATTERN.matcher(line); Matcher pickMatcher = PICK_PATTERN.matcher(line);
if (pickMatcher.matches()) { if (pickMatcher.matches()) {
String name = pickMatcher.group(1); String name = pickMatcher.group(1);
CardInfo card = getCardLookup().lookupCardInfo(name, currentSet).orElse(null); CardInfo cardInfo = getCardLookup().lookupCardInfo(name, currentSet, null);
if (card != null) { if (cardInfo != null) {
deckList.getCards().add(new DeckCardInfo(card.getName(), card.getCardNumber(), card.getSetCode())); deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode()));
} else { } else {
sbMessage.append("couldn't find: \"").append(name).append("\"\n"); sbMessage.append("couldn't find: \"").append(name).append("\"\n");
} }

View file

@ -36,9 +36,9 @@ public class MWSDeckImporter extends PlainTextDeckImporter {
int num = Integer.parseInt(lineNum); int num = Integer.parseInt(lineNum);
CardInfo cardInfo = null; CardInfo cardInfo = null;
if (setCode.isEmpty()) { if (setCode.isEmpty()) {
cardInfo = getCardLookup().lookupCardInfo(lineName, setCode).orElse(null); cardInfo = getCardLookup().lookupCardInfo(lineName, setCode, null);
} else { } else {
cardInfo = getCardLookup().lookupCardInfo(lineName).orElse(null); cardInfo = getCardLookup().lookupCardInfo(lineName);
} }
if (cardInfo == null) { if (cardInfo == null) {

View file

@ -65,12 +65,12 @@ public class MtgaImporter extends PlainTextDeckImporter {
CardInfo found; CardInfo found;
int count = Integer.parseInt(pattern.group(1)); int count = Integer.parseInt(pattern.group(1));
String name = pattern.group(2); String name = pattern.group(2);
if (pattern.group(3) != null && pattern.group(4) != null) { if (pattern.group(3) != null) {
String set = SET_REMAPPING.getOrDefault(pattern.group(3), pattern.group(3)); String set = SET_REMAPPING.getOrDefault(pattern.group(3), pattern.group(3));
String cardNumber = pattern.group(4); String cardNumber = pattern.groupCount() >= 4 ? pattern.group(4) : null;
found = lookup.lookupCardInfo(name, set, cardNumber).orElse(null); found = lookup.lookupCardInfo(name, set, cardNumber);
} else { } else {
found = lookup.lookupCardInfo(name).orElse(null); found = lookup.lookupCardInfo(name, null, null);
} }
if (found == null) { if (found == null) {
@ -110,9 +110,8 @@ public class MtgaImporter extends PlainTextDeckImporter {
// by card marks // by card marks
Matcher pattern = MtgaImporter.MTGA_PATTERN.matcher(CardNameUtil.normalizeCardName(firstLine)); Matcher pattern = MtgaImporter.MTGA_PATTERN.matcher(CardNameUtil.normalizeCardName(firstLine));
return pattern.matches() return pattern.matches()
&& pattern.groupCount() >= 4 && pattern.groupCount() >= 3
&& pattern.group(3) != null && pattern.group(3) != null;
&& pattern.group(4) != null;
} }
} }

View file

@ -63,11 +63,10 @@ public class MtgjsonDeckImporter extends JsonDeckImporter {
} }
int num = JsonUtil.getAsInt(card, "count"); int num = JsonUtil.getAsInt(card, "count");
Optional<CardInfo> cardLookup = getCardLookup().lookupCardInfo(name, setCode); CardInfo cardInfo = getCardLookup().lookupCardInfo(name, setCode, null);
if (!cardLookup.isPresent()) { if (cardInfo == null) {
sbMessage.append("Could not find card: '").append(name).append("'\n"); sbMessage.append("Could not find card: '").append(name).append("'\n");
} else { } else {
CardInfo cardInfo = cardLookup.get();
for (int i = 0; i < num; i++) { for (int i = 0; i < num; i++) {
list.add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); list.add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode()));
} }

View file

@ -63,12 +63,11 @@ public class O8dDeckImporter extends XmlDeckImporter {
private static Function<Node, Stream<DeckCardInfo>> toDeckCardInfo(CardLookup lookup, StringBuilder errors) { private static Function<Node, Stream<DeckCardInfo>> toDeckCardInfo(CardLookup lookup, StringBuilder errors) {
return node -> { return node -> {
String name = node.getTextContent(); String name = node.getTextContent();
Optional<CardInfo> cardInfo = lookup.lookupCardInfo(name); CardInfo cardInfo = lookup.lookupCardInfo(name);
if (cardInfo.isPresent()) { if (cardInfo != null) {
CardInfo info = cardInfo.get();
return Collections.nCopies( return Collections.nCopies(
getQuantityFromNode(node), getQuantityFromNode(node),
new DeckCardInfo(info.getName(), info.getCardNumber(), info.getSetCode())).stream(); new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())).stream();
} else { } else {
errors.append("Could not find card: '").append(name).append("'\n"); errors.append("Could not find card: '").append(name).append("'\n");
return Stream.empty(); return Stream.empty();

View file

@ -29,11 +29,11 @@ public final class EmblemOfCard extends Emblem {
String setCode, String setCode,
String infoTypeForError String infoTypeForError
) { ) {
int cardNumberInt = CardUtil.parseCardNumberAsInt(cardNumber); int intCardNumber = CardUtil.parseCardNumberAsInt(cardNumber);
List<CardInfo> found = CardRepository.instance.findCards(new CardCriteria() List<CardInfo> found = CardRepository.instance.findCards(new CardCriteria()
.name(cardName) .name(cardName)
.minCardNumber(cardNumberInt) .minCardNumber(intCardNumber)
.maxCardNumber(cardNumberInt) .maxCardNumber(intCardNumber)
.setCodes(setCode)); .setCodes(setCode));
return found.stream() return found.stream()
.filter(ci -> ci.getCardNumber().equals(cardNumber)) .filter(ci -> ci.getCardNumber().equals(cardNumber))

View file

@ -5,6 +5,9 @@
1 Expansion // Explosion (GRN) 224 1 Expansion // Explosion (GRN) 224
1 Forest (XLN) 277 1 Forest (XLN) 277
1 Teferi, Hero of Dominaria (DAR) 207 1 Teferi, Hero of Dominaria (DAR) 207
1 Benalish Marshal (PDOM) 6p
1 Benalish Marshal (PDOM) 6s
1 Benalish Marshal
3 Unmoored Ego (GRN) 212 3 Unmoored Ego (GRN) 212
1 Beacon Bolt (GRN) 154 1 Beacon Bolt (GRN) 154

View file

@ -29,26 +29,29 @@ public class FakeCardLookup extends CardLookup {
return this; return this;
} }
public Optional<CardInfo> lookupCardInfo(String cardName) { public CardInfo lookupCardInfo(String cardName) {
CardInfo card = lookup.get(cardName); CardInfo card = lookup.get(cardName);
if (card != null) { if (card != null) {
return Optional.of(card); return card;
} }
if (alwaysMatches) { if (alwaysMatches) {
return Optional.of(new CardInfo() {{ return new CardInfo() {{
name = cardName; name = cardName;
}}); }};
} }
return Optional.empty(); return null;
} }
@Override @Override
public List<CardInfo> lookupCardInfo(CardCriteria criteria) { public List<CardInfo> lookupCardInfo(CardCriteria criteria) {
return lookupCardInfo(criteria.getName()) CardInfo found = lookupCardInfo(criteria.getName());
.map(Collections::singletonList) if (found != null) {
.orElse(Collections.emptyList()); return new ArrayList<>(Collections.singletonList(found));
} else {
return Collections.emptyList();
}
} }
} }

View file

@ -36,11 +36,12 @@ public class MtgaImporterTest {
.addMain("Expansion // Explosion", 1) .addMain("Expansion // Explosion", 1)
.addMain("Forest", 1) .addMain("Forest", 1)
.addMain("Teferi, Hero of Dominaria", 1) .addMain("Teferi, Hero of Dominaria", 1)
.addMain("Benalish Marshal", 3)
.addSide("Unmoored Ego", 3) .addSide("Unmoored Ego", 3)
.addSide("Beacon Bolt", 1) .addSide("Beacon Bolt", 1)
.verify(deck, 8, 4); .verify(deck, 11, 4);
} }
} }