mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
Merge branch 'master' into rework/continuous-effects-layers
# Conflicts: # Mage.Sets/src/mage/cards/a/AettirAndPriwen.java # Mage.Sets/src/mage/cards/b/BlueDragon.java # Mage.Sets/src/mage/cards/k/KondasBanner.java # Mage.Sets/src/mage/cards/t/ThranWeaponry.java # Mage.Sets/src/mage/cards/t/TideShaper.java
This commit is contained in:
commit
e64a10bf10
2767 changed files with 25528 additions and 26335 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -12,6 +12,7 @@ syntax: glob
|
|||
*.log.*
|
||||
*/gamelogs
|
||||
*/gamelogsJson
|
||||
*/gamesHistory
|
||||
|
||||
# Mage.Client
|
||||
Mage.Client/plugins/images
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ git:
|
|||
before_install:
|
||||
- echo "MAVEN_OPTS='-Xmx2g'" > ~/.mavenrc
|
||||
script:
|
||||
- mvn test -B -Dlog4j.configuration=file:${TRAVIS_BUILD_DIR}/.travis/log4j.properties
|
||||
- mvn test -B -Dxmage.dataCollectors.printGameLogs=false -Dlog4j.configuration=file:${TRAVIS_BUILD_DIR}/.travis/log4j.properties
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
|
|
@ -87,14 +87,6 @@
|
|||
<artifactId>jetlang</artifactId>
|
||||
<version>0.2.23</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- amazon s3 cloud lib to upload game logs from experimental client -->
|
||||
<!-- TODO: feature must be removed as unused or implemented for all -->
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>1.12.78</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- GUI lib TODO: unused and can be deleted? -->
|
||||
<groupId>com.jgoodies</groupId>
|
||||
|
|
|
|||
|
|
@ -63,6 +63,9 @@ public final class Constants {
|
|||
public static final String RESOURCE_SYMBOL_FOLDER_SVG = "svg";
|
||||
public static final String RESOURCE_SYMBOL_FOLDER_PNG = "png";
|
||||
|
||||
// download rarity icons to large folder by default
|
||||
public static final String RESOURCE_PATH_SYMBOLS_RARITY_DEFAULT_PATH = RESOURCE_PATH_SYMBOLS + File.separator + RESOURCE_SYMBOL_FOLDER_LARGE;
|
||||
|
||||
public enum ResourceSymbolSize {
|
||||
SMALL, // TODO: delete SMALL, MEDIUM and LARGE as outdated (svg or generated png works fine)
|
||||
MEDIUM,
|
||||
|
|
@ -74,12 +77,12 @@ public final class Constants {
|
|||
// resources - sets
|
||||
public static final String RESOURCE_PATH_SETS = File.separator + "sets";
|
||||
public static final String RESOURCE_SET_FOLDER_SMALL = "small";
|
||||
public static final String RESOURCE_SET_FOLDER_MEDIUM = ""; // empty, medium images laydown in "sets" folder, TODO: delete that and auto gen, use png for html, not gif
|
||||
public static final String RESOURCE_SET_FOLDER_LARGE = "large";
|
||||
public static final String RESOURCE_SET_FOLDER_SVG = "svg";
|
||||
|
||||
public enum ResourceSetSize {
|
||||
SMALL,
|
||||
MEDIUM,
|
||||
LARGE,
|
||||
SVG
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public class DeckGeneratorPool {
|
|||
// List of cards so far in the deck
|
||||
private final List<Card> deckCards = new ArrayList<>();
|
||||
// List of reserve cards found to fix up undersized decks
|
||||
private final List<Card> reserveSpells = new ArrayList<>();
|
||||
private final Map<String, Card> reserveSpells = new HashMap<>();
|
||||
private final Deck deck;
|
||||
|
||||
/**
|
||||
|
|
@ -170,12 +170,13 @@ public class DeckGeneratorPool {
|
|||
* @param card the card to add.
|
||||
*/
|
||||
public void addCard(Card card) {
|
||||
Object cnt = cardCounts.get((card.getName()));
|
||||
if (cnt == null)
|
||||
cardCounts.put(card.getName(), 0);
|
||||
int existingCount = cardCounts.get((card.getName()));
|
||||
cardCounts.put(card.getName(), existingCount + 1);
|
||||
int count = cardCounts.getOrDefault(card.getName(), 0);
|
||||
cardCounts.put(card.getName(), count + 1);
|
||||
deckCards.add(card);
|
||||
|
||||
if (deckCards.stream().distinct().collect(Collectors.toList()).size() != deckCards.size()) {
|
||||
System.out.println("wtf " + card.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public void clearCards(boolean isClearReserve) {
|
||||
|
|
@ -198,8 +199,8 @@ public class DeckGeneratorPool {
|
|||
// Only cards with CMC < 7 and don't already exist in the deck
|
||||
// can be added to our reserve pool as not to overwhelm the curve
|
||||
// with high CMC cards and duplicates.
|
||||
if (cardCMC < 7 && getCardCount(card.getName()) == 0) {
|
||||
this.reserveSpells.add(card);
|
||||
if (cardCMC < 7 && getCardCount(card.getName()) == 0 && !this.reserveSpells.containsKey(card.getName())) {
|
||||
this.reserveSpells.put(card.getName(), card);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -416,48 +417,38 @@ public class DeckGeneratorPool {
|
|||
* @return a fixed list of cards for this deck.
|
||||
*/
|
||||
private List<Card> getFixedSpells() {
|
||||
int spellSize = deckCards.size();
|
||||
int spellsSize = deckCards.size();
|
||||
int nonLandSize = (deckSize - landCount);
|
||||
|
||||
// Less spells than needed
|
||||
if (spellSize < nonLandSize) {
|
||||
|
||||
int spellsNeeded = nonLandSize - spellSize;
|
||||
|
||||
// If we haven't got enough spells in reserve to fulfil the amount we need, skip adding any.
|
||||
if (reserveSpells.size() >= spellsNeeded) {
|
||||
|
||||
List<Card> spellsToAdd = new ArrayList<>(spellsNeeded);
|
||||
|
||||
// Initial reservoir
|
||||
for (int i = 0; i < spellsNeeded; i++)
|
||||
spellsToAdd.add(reserveSpells.get(i));
|
||||
|
||||
for (int i = spellsNeeded + 1; i < reserveSpells.size() - 1; i++) {
|
||||
int j = RandomUtil.nextInt(i);
|
||||
Card randomCard = reserveSpells.get(j);
|
||||
if (isValidSpellCard(randomCard) && j < spellsToAdd.size()) {
|
||||
spellsToAdd.set(j, randomCard);
|
||||
// fewer spells than needed - add
|
||||
if (spellsSize < nonLandSize) {
|
||||
int needExtraSpells = nonLandSize - spellsSize;
|
||||
List<Card> possibleSpells = new ArrayList<>(reserveSpells.values());
|
||||
while (needExtraSpells > 0) {
|
||||
Card card = RandomUtil.randomFromCollection(possibleSpells);
|
||||
if (card == null) {
|
||||
break;
|
||||
}
|
||||
if (isValidSpellCard(card)) {
|
||||
needExtraSpells--;
|
||||
deckCards.add(card);
|
||||
}
|
||||
// Add randomly selected spells needed
|
||||
deckCards.addAll(spellsToAdd);
|
||||
possibleSpells.remove(card);
|
||||
}
|
||||
}
|
||||
|
||||
// More spells than needed
|
||||
else if (spellSize > (deckSize - landCount)) {
|
||||
int spellsRemoved = (spellSize) - (deckSize - landCount);
|
||||
for (int i = 0; i < spellsRemoved; ++i) {
|
||||
deckCards.remove(RandomUtil.nextInt(deckCards.size()));
|
||||
// more spells than needed - remove
|
||||
if (spellsSize > nonLandSize) {
|
||||
int removeCount = spellsSize - nonLandSize;
|
||||
for (int i = 0; i < removeCount; ++i) {
|
||||
deckCards.remove(RandomUtil.randomFromCollection(deckCards));
|
||||
}
|
||||
}
|
||||
|
||||
// Check we have exactly the right amount of cards for a deck.
|
||||
if (deckCards.size() != nonLandSize) {
|
||||
logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors");
|
||||
logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors (wrong non land cards amount)");
|
||||
}
|
||||
// Return the fixed amount
|
||||
|
||||
return deckCards;
|
||||
}
|
||||
|
||||
|
|
@ -619,48 +610,75 @@ public class DeckGeneratorPool {
|
|||
if (needCommandersCount > 0 && !genPool.cardCounts.isEmpty()) {
|
||||
throw new IllegalArgumentException("Wrong code usage: generateSpells with creatures and commanders must be called as first");
|
||||
}
|
||||
List<CardInfo> cardPool = CardRepository.instance.findCards(criteria);
|
||||
List<Card> cardsPool = CardRepository.instance.findCards(criteria).stream()
|
||||
.map(CardInfo::createMockCard)
|
||||
.filter(genPool::isValidSpellCard)
|
||||
.collect(Collectors.toList());
|
||||
List<Card> commandersPool = cardsPool.stream()
|
||||
.filter(genPool::isValidCommander)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<DeckGeneratorCMC.CMC> deckCMCs = genPool.getCMCsForSpellCount(needCardsCount);
|
||||
int count = 0;
|
||||
int usedCardsCount = 0;
|
||||
int validCommanders = 0;
|
||||
int reservesAdded = 0;
|
||||
if (cardPool.size() > 0 && cardPool.size() >= needCardsCount) {
|
||||
if (cardsPool.size() > 0 && cardsPool.size() >= needCardsCount) {
|
||||
int tries = 0;
|
||||
List<Card> possibleCards = new ArrayList<>(cardsPool);
|
||||
List<Card> possibleCommanders = new ArrayList<>(commandersPool);
|
||||
while (true) {
|
||||
tries++;
|
||||
|
||||
// can't finish deck, stop and use reserved cards later
|
||||
if (tries > DeckGenerator.MAX_TRIES) {
|
||||
logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors");
|
||||
logger.info("Can't generate full deck for selected settings - try again or choose more sets and less colors (max tries exceeded)");
|
||||
break;
|
||||
}
|
||||
|
||||
// can finish deck - but make sure it has commander
|
||||
if (count >= needCardsCount) {
|
||||
if (usedCardsCount >= needCardsCount) {
|
||||
if (validCommanders < needCommandersCount) {
|
||||
// reset deck search from scratch (except reserved cards)
|
||||
count = 0;
|
||||
usedCardsCount = 0;
|
||||
validCommanders = 0;
|
||||
deckCMCs = genPool.getCMCsForSpellCount(needCardsCount);
|
||||
genPool.clearCards(false);
|
||||
genPool.clearCards(true);
|
||||
possibleCards = new ArrayList<>(cardsPool);
|
||||
possibleCommanders = new ArrayList<>(commandersPool);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Card card = cardPool.get(RandomUtil.nextInt(cardPool.size())).createMockCard();
|
||||
if (possibleCards.isEmpty()) {
|
||||
throw new IllegalStateException("Not enough cards to generate deck (possible cards is empty)");
|
||||
}
|
||||
|
||||
// choose commander first
|
||||
Card card = null;
|
||||
if (validCommanders < needCommandersCount && !possibleCommanders.isEmpty()) {
|
||||
card = RandomUtil.randomFromCollection(possibleCommanders);
|
||||
}
|
||||
|
||||
// choose other cards after commander
|
||||
if (card == null) {
|
||||
card = RandomUtil.randomFromCollection(possibleCards);
|
||||
}
|
||||
|
||||
if (!genPool.isValidSpellCard(card)) {
|
||||
possibleCards.remove(card);
|
||||
possibleCommanders.remove(card);
|
||||
continue;
|
||||
}
|
||||
|
||||
int cardCMC = card.getManaValue();
|
||||
for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) {
|
||||
if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) {
|
||||
int currentAmount = deckCMC.getAmount();
|
||||
if (currentAmount > 0) {
|
||||
deckCMC.setAmount(currentAmount - 1);
|
||||
int needAmount = deckCMC.getAmount();
|
||||
if (needAmount > 0) {
|
||||
deckCMC.setAmount(needAmount - 1);
|
||||
genPool.addCard(card.copy());
|
||||
count++;
|
||||
usedCardsCount++;
|
||||
// make sure it has compatible commanders
|
||||
if (genPool.isValidCommander(card)) {
|
||||
validCommanders++;
|
||||
|
|
@ -674,7 +692,7 @@ public class DeckGeneratorPool {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Not enough cards to generate deck.");
|
||||
throw new IllegalStateException("Not enough cards to generate deck (cards pool too small)");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import mage.client.dialog.AddLandDialog;
|
|||
import mage.client.dialog.PreferencesDialog;
|
||||
import mage.client.plugins.impl.Plugins;
|
||||
import mage.client.util.Event;
|
||||
import mage.client.util.GUISizeHelper;
|
||||
import mage.client.util.Listener;
|
||||
import mage.client.util.audio.AudioManager;
|
||||
import mage.components.CardInfoPane;
|
||||
|
|
@ -31,7 +30,6 @@ import mage.util.XmageThreadFactory;
|
|||
import mage.view.CardView;
|
||||
import mage.view.SimpleCardView;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.mage.card.arcane.ManaSymbols;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
|
|
@ -534,7 +532,8 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
}
|
||||
}
|
||||
});
|
||||
refreshDeck(true);
|
||||
|
||||
refreshDeck(true, false);
|
||||
|
||||
// auto-import dropped files from OS
|
||||
if (mode == DeckEditorMode.FREE_BUILDING) {
|
||||
|
|
@ -673,18 +672,26 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
|
||||
private void refreshDeck() {
|
||||
if (this.isVisible()) { // TODO: test auto-close deck with active lands dialog, e.g. on timeout
|
||||
refreshDeck(false);
|
||||
refreshDeck(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshDeck(boolean useLayout) {
|
||||
private void refreshDeck(boolean useLayout, boolean useDeckValidation) {
|
||||
try {
|
||||
setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||
this.txtDeckName.setText(deck.getName());
|
||||
deckArea.loadDeck(deck, useLayout, bigCard);
|
||||
} finally {
|
||||
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||
if (useDeckValidation) {
|
||||
validateDeck();
|
||||
}
|
||||
} finally {
|
||||
MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDeck() {
|
||||
this.deckLegalityDisplay.setVisible(true);
|
||||
this.deckLegalityDisplay.validateDeck(deck);
|
||||
}
|
||||
|
||||
private void setTimeout(int s) {
|
||||
|
|
@ -762,7 +769,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
|
||||
if (newDeck != null) {
|
||||
deck = newDeck;
|
||||
refreshDeck();
|
||||
refreshDeck(false, true);
|
||||
}
|
||||
|
||||
// save last deck import folder
|
||||
|
|
@ -818,7 +825,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
Deck deckToAppend = Deck.load(DeckImporter.importDeckFromFile(tempDeckPath, errorMessages, false), true, true);
|
||||
processAndShowImportErrors(errorMessages);
|
||||
this.deck = Deck.append(deckToAppend, this.deck);
|
||||
refreshDeck();
|
||||
refreshDeck(false, true);
|
||||
} catch (GameException e1) {
|
||||
JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE);
|
||||
} finally {
|
||||
|
|
@ -922,7 +929,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
|
||||
if (newDeck != null) {
|
||||
deck = newDeck;
|
||||
refreshDeck();
|
||||
refreshDeck(false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1441,7 +1448,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
|
||||
if (newDeck != null) {
|
||||
deck = newDeck;
|
||||
refreshDeck(true);
|
||||
refreshDeck(true, true);
|
||||
}
|
||||
|
||||
// save last deck history
|
||||
|
|
@ -1474,7 +1481,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
// in deck editor mode - clear all cards
|
||||
deck = new Deck();
|
||||
}
|
||||
refreshDeck();
|
||||
refreshDeck(false, true);
|
||||
}//GEN-LAST:event_btnNewActionPerformed
|
||||
|
||||
private void btnExitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExitActionPerformed
|
||||
|
|
@ -1483,7 +1490,9 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
|
||||
private void btnAddLandActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAddLandActionPerformed
|
||||
AddLandDialog dialog = new AddLandDialog();
|
||||
dialog.showDialog(deck, mode, this::refreshDeck);
|
||||
dialog.showDialog(deck, mode, () -> {
|
||||
this.refreshDeck(false, true);
|
||||
});
|
||||
}//GEN-LAST:event_btnAddLandActionPerformed
|
||||
|
||||
private void btnGenDeckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnGenDeckActionPerformed
|
||||
|
|
@ -1501,7 +1510,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
} finally {
|
||||
MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
refreshDeck();
|
||||
refreshDeck(false, true);
|
||||
}//GEN-LAST:event_btnGenDeckActionPerformed
|
||||
|
||||
private void btnSubmitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSubmitActionPerformed
|
||||
|
|
@ -1548,8 +1557,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
|||
}//GEN-LAST:event_btnExportActionPerformed
|
||||
|
||||
private void btnLegalityActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLegalityActionPerformed
|
||||
this.deckLegalityDisplay.setVisible(true);
|
||||
this.deckLegalityDisplay.validateDeck(deck);
|
||||
validateDeck();
|
||||
}//GEN-LAST:event_btnLegalityActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public class DraftPickLogger {
|
|||
private void appendToDraftLog(String data) {
|
||||
if (logging) {
|
||||
try {
|
||||
Files.write(logPath, data.getBytes(), StandardOpenOption.APPEND);
|
||||
Files.write(logPath, data.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||
} catch (IOException ex) {
|
||||
LOGGER.error(null, ex);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,10 +257,7 @@ public class CallbackClientImpl implements CallbackClient {
|
|||
if (panel != null) {
|
||||
Session session = SessionHandler.getSession();
|
||||
if (session.isJsonLogActive()) {
|
||||
UUID gameId = callback.getObjectId();
|
||||
appendJsonEvent("GAME_OVER", callback.getObjectId(), message);
|
||||
String logFileName = "game-" + gameId + ".json";
|
||||
S3Uploader.upload(logFileName, gameId.toString());
|
||||
}
|
||||
panel.endMessage(callback.getMessageId(), message.getGameView(), message.getOptions(), message.getMessage());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
package mage.client.remote;
|
||||
|
||||
import com.amazonaws.AmazonClientException;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.services.s3.transfer.TransferManager;
|
||||
import com.amazonaws.services.s3.transfer.Upload;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class S3Uploader {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(S3Uploader.class);
|
||||
|
||||
public static Boolean upload(String filePath, String keyName) throws Exception {
|
||||
String existingBucketName = System.getenv("S3_BUCKET") != null ? System.getenv("S3_BUCKET")
|
||||
: "xmage-game-logs-dev";
|
||||
|
||||
String accessKeyId = System.getenv("AWS_ACCESS_ID");
|
||||
String secretKeyId = System.getenv("AWS_SECRET_KEY");
|
||||
|
||||
if (accessKeyId == null || accessKeyId.isEmpty()
|
||||
|| secretKeyId == null || secretKeyId.isEmpty()
|
||||
|| existingBucketName.isEmpty()) {
|
||||
logger.info("Aborting json log sync.");
|
||||
return false;
|
||||
}
|
||||
|
||||
String path = new File("./" + filePath).getCanonicalPath();
|
||||
logger.info("Syncing " + path + " to bucket: " + existingBucketName + " with AWS Access Id: " + accessKeyId);
|
||||
|
||||
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKeyId, secretKeyId);
|
||||
TransferManager tm = new TransferManager(awsCreds);
|
||||
Upload upload = tm.upload(existingBucketName, "/game/" + keyName + ".json", new File(path));
|
||||
|
||||
try {
|
||||
upload.waitForUploadResult();
|
||||
logger.info("Sync Complete For " + path + " to bucket: " + existingBucketName + " with AWS Access Id: " + accessKeyId);
|
||||
new File(path);
|
||||
return true;
|
||||
} catch (AmazonClientException amazonClientException) {
|
||||
logger.fatal("Unable to upload file, upload was aborted.", amazonClientException);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -896,7 +896,7 @@ public class TablesPanel extends javax.swing.JPanel {
|
|||
formatFilterList.add(RowFilter.regexFilter("^Limited", TablesTableModel.COLUMN_DECK_TYPE));
|
||||
}
|
||||
if (btnFormatOther.isSelected()) {
|
||||
formatFilterList.add(RowFilter.regexFilter("^Momir Basic|^Constructed - Pauper|^Constructed - Frontier|^Constructed - Extended|^Constructed - Eternal|^Constructed - Historical|^Constructed - Super|^Constructed - Freeform|^Australian Highlander|^European Highlander|^Canadian Highlander|^Constructed - Old|^Constructed - Historic", TablesTableModel.COLUMN_DECK_TYPE));
|
||||
formatFilterList.add(RowFilter.regexFilter("^Momir Basic|^Constructed - Pauper|^Constructed - Frontier|^Constructed - Extended|^Constructed - Eternal|^Constructed - Historical|^Constructed - Super|^Constructed - Freeform|^Constructed - Freeform Unlimited|^Australian Highlander|^European Highlander|^Canadian Highlander|^Constructed - Old|^Constructed - Historic", TablesTableModel.COLUMN_DECK_TYPE));
|
||||
}
|
||||
|
||||
// skill
|
||||
|
|
|
|||
|
|
@ -89,11 +89,6 @@ public final class ManaSymbols {
|
|||
public static void loadImages() {
|
||||
logger.info("Symbols: loading...");
|
||||
|
||||
// TODO: delete files rename jpg->gif (it was for backward compatibility for one of the old version?)
|
||||
renameSymbols(getResourceSymbolsPath(ResourceSymbolSize.SMALL));
|
||||
renameSymbols(getResourceSymbolsPath(ResourceSymbolSize.MEDIUM));
|
||||
renameSymbols(getResourceSymbolsPath(ResourceSymbolSize.LARGE));
|
||||
//renameSymbols(getSymbolsPath(ResourceSymbolSize.SVG)); // not need
|
||||
// TODO: remove medium sets files to "medium" folder like symbols above?
|
||||
|
||||
// prepare svg's css settings
|
||||
|
|
@ -145,9 +140,9 @@ public final class ManaSymbols {
|
|||
Map<Rarity, Image> rarityImages = new EnumMap<>(Rarity.class);
|
||||
setImages.put(set, rarityImages);
|
||||
|
||||
// load medium size
|
||||
// load large size
|
||||
for (Rarity rarityCode : codes) {
|
||||
File file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + rarityCode.getCode() + ".jpg");
|
||||
File file = new File(getResourceSetsPath(ResourceSetSize.LARGE) + set + '-' + rarityCode.getCode() + ".png");
|
||||
try {
|
||||
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
|
||||
int width = image.getWidth(null);
|
||||
|
|
@ -167,7 +162,7 @@ public final class ManaSymbols {
|
|||
|
||||
// generate small size
|
||||
try {
|
||||
File file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM));
|
||||
File file = new File(getResourceSetsPath(ResourceSetSize.LARGE));
|
||||
if (!file.exists()) {
|
||||
file.mkdirs();
|
||||
}
|
||||
|
|
@ -175,11 +170,11 @@ public final class ManaSymbols {
|
|||
for (Rarity code : codes) {
|
||||
File newFile = new File(pathRoot + '-' + code + ".png");
|
||||
if (!(MageFrame.isSkipSmallSymbolGenerationForExisting() && newFile.exists())) {// skip if option enabled and file already exists
|
||||
file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".png");
|
||||
file = new File(getResourceSetsPath(ResourceSetSize.LARGE) + set + '-' + code + ".png");
|
||||
if (file.exists()) {
|
||||
continue;
|
||||
}
|
||||
file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".jpg");
|
||||
file = new File(getResourceSetsPath(ResourceSetSize.LARGE) + set + '-' + code + ".png");
|
||||
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
|
||||
try {
|
||||
int width = image.getWidth(null);
|
||||
|
|
@ -239,7 +234,7 @@ public final class ManaSymbols {
|
|||
}
|
||||
}
|
||||
|
||||
private static File getSymbolFileNameAsGIF(String symbol, int size) {
|
||||
private static File getSymbolFileNameAsPNG(String symbol, int size) {
|
||||
|
||||
ResourceSymbolSize needSize = null;
|
||||
if (size <= 15) {
|
||||
|
|
@ -250,15 +245,15 @@ public final class ManaSymbols {
|
|||
needSize = ResourceSymbolSize.LARGE;
|
||||
}
|
||||
|
||||
return new File(getResourceSymbolsPath(needSize) + symbol + ".gif");
|
||||
return new File(getResourceSymbolsPath(needSize) + symbol + ".png");
|
||||
}
|
||||
|
||||
private static BufferedImage loadSymbolAsGIF(String symbol, int resizeToWidth, int resizeToHeight) {
|
||||
File file = getSymbolFileNameAsGIF(symbol, resizeToWidth);
|
||||
return loadSymbolAsGIF(file, resizeToWidth, resizeToHeight);
|
||||
private static BufferedImage loadSymbolAsPNG(String symbol, int resizeToWidth, int resizeToHeight) {
|
||||
File file = getSymbolFileNameAsPNG(symbol, resizeToWidth);
|
||||
return loadSymbolAsPNG(file, resizeToWidth, resizeToHeight);
|
||||
}
|
||||
|
||||
private static BufferedImage loadSymbolAsGIF(File sourceFile, int resizeToWidth, int resizeToHeight) {
|
||||
private static BufferedImage loadSymbolAsPNG(File sourceFile, int resizeToWidth, int resizeToHeight) {
|
||||
|
||||
BufferedImage image = null;
|
||||
|
||||
|
|
@ -315,9 +310,9 @@ public final class ManaSymbols {
|
|||
|
||||
// gif (if svg fails)
|
||||
if (image == null) {
|
||||
file = getSymbolFileNameAsGIF(symbol, size);
|
||||
file = getSymbolFileNameAsPNG(symbol, size);
|
||||
if (file.exists()) {
|
||||
image = loadSymbolAsGIF(file, size, size);
|
||||
image = loadSymbolAsPNG(file, size, size);
|
||||
}
|
||||
}
|
||||
if (image == null) {
|
||||
|
|
@ -352,29 +347,6 @@ public final class ManaSymbols {
|
|||
return errorInfo.isEmpty();
|
||||
}
|
||||
|
||||
private static void renameSymbols(String path) {
|
||||
File file = new File(path);
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg");
|
||||
try {
|
||||
Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if (matcher.matches(file)) {
|
||||
Path gifPath = file.resolveSibling(file.getFileName().toString().replaceAll("\\.jpg$", ".gif"));
|
||||
Files.move(file, gifPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
logger.error("Couldn't rename mana symbols on " + path, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getResourceSymbolsPath(ResourceSymbolSize needSize) {
|
||||
// return real path to symbols (default or user defined)
|
||||
|
||||
|
|
@ -420,8 +392,8 @@ public final class ManaSymbols {
|
|||
case SMALL:
|
||||
path = path + Constants.RESOURCE_SET_FOLDER_SMALL;
|
||||
break;
|
||||
case MEDIUM:
|
||||
path = path + Constants.RESOURCE_SET_FOLDER_MEDIUM;
|
||||
case LARGE:
|
||||
path = path + Constants.RESOURCE_SET_FOLDER_LARGE;
|
||||
break;
|
||||
case SVG:
|
||||
path = path + Constants.RESOURCE_SET_FOLDER_SVG;
|
||||
|
|
|
|||
|
|
@ -663,7 +663,9 @@ public class CardPluginImpl implements CardPlugin {
|
|||
// mana symbols (low quality)
|
||||
jobs = new GathererSymbols();
|
||||
for (DownloadJob job : jobs) {
|
||||
downloader.add(job);
|
||||
// TODO: gatherer removed mana symbols icons after 2025, see https://github.com/magefree/mage/issues/13797
|
||||
// remove GathererSymbols code after few releases as unused (2025.06.28)
|
||||
// downloader.add(job);
|
||||
}
|
||||
|
||||
// set code symbols (low quality)
|
||||
|
|
|
|||
|
|
@ -46,8 +46,10 @@ public class Downloader extends AbstractLaternaBean {
|
|||
|
||||
public Downloader() {
|
||||
// prepare 10 threads and start to waiting new download jobs from queue
|
||||
// TODO: gatherer website has download rate limits, so limit max threads as temporary solution
|
||||
int maxThreads = 3;
|
||||
PoolFiberFactory f = new PoolFiberFactory(pool);
|
||||
for (int i = 0, numThreads = 10; i < numThreads; i++) {
|
||||
for (int i = 0, numThreads = maxThreads; i < numThreads; i++) {
|
||||
Fiber fiber = f.create();
|
||||
fiber.start();
|
||||
fibers.add(fiber);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir;
|
|||
/**
|
||||
* Download: set code symbols download from wizards web size
|
||||
* <p>
|
||||
* Warning, it's outdated source with low quality images. TODO: must migrate to scryfall like mana icons
|
||||
* Warning, it's outdated source with low quality images.
|
||||
* TODO: must migrate to scryfall like mana icons,
|
||||
* see https://github.com/magefree/mage/issues/13261
|
||||
*/
|
||||
public class GathererSets implements Iterable<DownloadJob> {
|
||||
|
||||
|
|
@ -41,9 +43,10 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
|
||||
private static File outDir;
|
||||
|
||||
private static final int DAYS_BEFORE_RELEASE_TO_DOWNLOAD = +14; // Try to load the symbolsBasic eralies 14 days before release date
|
||||
private static final int DAYS_BEFORE_RELEASE_TO_DOWNLOAD = +14; // Try to load the symbolsBasic 14 days before release date
|
||||
private static final Logger logger = Logger.getLogger(GathererSets.class);
|
||||
|
||||
// TODO: find all possible sets from ExpansionRepository instead custom
|
||||
private static final String[] symbolsBasic = {"10E", "9ED", "8ED", "7ED", "6ED", "5ED", "4ED", "3ED", "2ED", "LEB", "LEA",
|
||||
"HOP",
|
||||
"ARN", "ATQ", "LEG", "DRK", "FEM", "HML",
|
||||
|
|
@ -61,14 +64,16 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
"TSP", "TSB", "PLC", "FUT",
|
||||
"LRW", "MOR",
|
||||
"SHM", "EVE",
|
||||
"MED", "ME2", "ME3", "ME4",
|
||||
"ME2", "ME3", "ME4",
|
||||
"POR", "P02", "PTK",
|
||||
"ARC", "DD3EVG",
|
||||
"W16", "W17",
|
||||
// "PALP" -- Gatherer does not have the set Asia Pacific Land Program
|
||||
// "ATH" -- has cards from many sets, symbol does not exist on gatherer
|
||||
// "CP", "DPA", "PELP", "PGPX", "PGRU", "H17", "JR", "SWS", // need to fix
|
||||
"H09", "PD2", "PD3", "UNH", "CM1", "V11", "A25", "UST", "IMA", "DD2", "EVG", "DDC", "DDE", "DDD", "CHR", "G18", "GVL", "S00", "S99", "UGL" // ok
|
||||
"H09", "PD2", "PD3", "UNH", "CM1", "V11", "A25", "UST", "IMA", "DD2",
|
||||
"EVG", "DDC", "DDE", "DDD", "CHR", "G18", "GVL", "S00", "S99", "UGL",
|
||||
"BTD" // ok
|
||||
// current testing
|
||||
};
|
||||
|
||||
|
|
@ -98,21 +103,25 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
"GNT", "UMA", "GRN",
|
||||
"RNA", "WAR", "MH1",
|
||||
"M20",
|
||||
"C19", "ELD", "MB1", "GN2", "J20", "THB", "UND", "C20", "IKO", "M21",
|
||||
"C19", "ELD", "MB1", "GN2", "THB", "UND", "C20", "IKO", "M21",
|
||||
"JMP", "2XM", "ZNR", "KLR", "CMR", "KHC", "KHM", "TSR", "STX", "STA",
|
||||
"C21", "MH2", "AFR", "AFC", "J21", "MID", "MIC", "VOW", "VOC", "YMID",
|
||||
"NEC", "YNEO", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "YDMU", "40K", "GN3",
|
||||
"UNF", "BRO", "BRC", "BOT", "30A", "J22", "SCD", "DMR", "ONE", "ONC",
|
||||
"NEC", "YNEO", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "40K", "GN3",
|
||||
"UNF", "BRO", "BRC", "BOT", "J22", "DMR", "ONE", "ONC",
|
||||
"MOM", "MOC", "MUL", "MAT", "LTR", "CMM", "WOE", "WHO", "RVR", "WOT",
|
||||
"WOC", "SPG", "LCI", "LCC", "REX", "PIP", "MKM", "MKC", "CLU", "OTJ",
|
||||
"OTC", "OTP", "BIG", "MH3", "M3C", "ACR", "BLB"
|
||||
"OTC", "OTP", "BIG", "MH3", "M3C", "ACR", "BLB", "BLC", "DSK", "DSC",
|
||||
"MB2", "FDN", "INR", "J25", "DRC", "DFT", "TDC", "TDM", "FCA", "FIC",
|
||||
"FIN", "SIS", "SIR", "SLD", "AKR", "MD1", "ANB", "LTC", "BRR", "HA1",
|
||||
"HA2", "HA3", "HA4", "HA5", "ZNC", "EOE", "EOC", "SPE", "TLA"
|
||||
// "HHO", "ANA" -- do not exist on gatherer
|
||||
};
|
||||
|
||||
private static final String[] symbolsOnlyMyth = {
|
||||
"DRB", "V09", "V10", "V12", "V13", "V14", "V15", "V16", "V17", "EXP", "MED"
|
||||
"DRB", "V09", "V10", "V12", "V13", "V14", "V15", "V16", "V17", "EXP", "MED", "ZNE"
|
||||
// "HTR16" does not exist
|
||||
};
|
||||
|
||||
private static final String[] symbolsOnlySpecial = {
|
||||
"MPS", "MP2"
|
||||
};
|
||||
|
|
@ -130,6 +139,7 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
codeReplacements.put("APC", "AP");
|
||||
codeReplacements.put("ARN", "AN");
|
||||
codeReplacements.put("ATQ", "AQ");
|
||||
codeReplacements.put("BTD", "BD");
|
||||
codeReplacements.put("CMA", "CM1");
|
||||
codeReplacements.put("CHR", "CH");
|
||||
codeReplacements.put("DVD", "DD3_DVD");
|
||||
|
|
@ -165,14 +175,16 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
codeReplacements.put("UGIN", "FRF_UGIN");
|
||||
codeReplacements.put("UGL", "UG");
|
||||
codeReplacements.put("ULG", "GU");
|
||||
codeReplacements.put("UNF", "UNFS");
|
||||
codeReplacements.put("USG", "UZ");
|
||||
codeReplacements.put("VIS", "VI");
|
||||
codeReplacements.put("WTH", "WL");
|
||||
codeReplacements.put("YMID", "Y22");
|
||||
codeReplacements.put("YNEO", "Y22NEO");
|
||||
}
|
||||
|
||||
public GathererSets() {
|
||||
|
||||
outDir = new File(getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS);
|
||||
outDir = new File(getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS_RARITY_DEFAULT_PATH);
|
||||
|
||||
if (!outDir.exists()) {
|
||||
outDir.mkdirs();
|
||||
|
|
@ -287,9 +299,9 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
canDownload = false;
|
||||
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
||||
canDownload = true;
|
||||
jobs.add(generateDownloadJob(symbol, "C", "C"));
|
||||
jobs.add(generateDownloadJob(symbol, "U", "U"));
|
||||
jobs.add(generateDownloadJob(symbol, "R", "R"));
|
||||
jobs.add(generateDownloadJob(symbol, "C", "common"));
|
||||
jobs.add(generateDownloadJob(symbol, "U", "uncommon"));
|
||||
jobs.add(generateDownloadJob(symbol, "R", "rare"));
|
||||
}
|
||||
CheckSearchResult(symbol, exp, canDownload, true, true, true, false);
|
||||
}
|
||||
|
|
@ -299,10 +311,10 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
canDownload = false;
|
||||
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
||||
canDownload = true;
|
||||
jobs.add(generateDownloadJob(symbol, "C", "C"));
|
||||
jobs.add(generateDownloadJob(symbol, "U", "U"));
|
||||
jobs.add(generateDownloadJob(symbol, "R", "R"));
|
||||
jobs.add(generateDownloadJob(symbol, "M", "M"));
|
||||
jobs.add(generateDownloadJob(symbol, "C", "common"));
|
||||
jobs.add(generateDownloadJob(symbol, "U", "uncommon"));
|
||||
jobs.add(generateDownloadJob(symbol, "R", "rare"));
|
||||
jobs.add(generateDownloadJob(symbol, "M", "mythic"));
|
||||
}
|
||||
CheckSearchResult(symbol, exp, canDownload, true, true, true, true);
|
||||
}
|
||||
|
|
@ -312,7 +324,7 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
canDownload = false;
|
||||
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
||||
canDownload = true;
|
||||
jobs.add(generateDownloadJob(symbol, "M", "M"));
|
||||
jobs.add(generateDownloadJob(symbol, "M", "mythic"));
|
||||
}
|
||||
CheckSearchResult(symbol, exp, canDownload, false, false, false, true);
|
||||
}
|
||||
|
|
@ -322,7 +334,7 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
canDownload = false;
|
||||
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
||||
canDownload = true;
|
||||
jobs.add(generateDownloadJob(symbol, "M", "S"));
|
||||
jobs.add(generateDownloadJob(symbol, "M", "special"));
|
||||
}
|
||||
CheckSearchResult(symbol, exp, canDownload, false, false, false, true);
|
||||
}
|
||||
|
|
@ -334,11 +346,22 @@ public class GathererSets implements Iterable<DownloadJob> {
|
|||
}
|
||||
|
||||
private DownloadJob generateDownloadJob(String set, String rarity, String urlRarity) {
|
||||
File dst = new File(outDir, set + '-' + rarity + ".jpg");
|
||||
File dst = new File(outDir, set + '-' + rarity + ".png");
|
||||
if (codeReplacements.containsKey(set)) {
|
||||
set = codeReplacements.get(set);
|
||||
}
|
||||
String url = "https://gatherer.wizards.com/Handlers/Image.ashx?type=symbol&set=" + set + "&size=small&rarity=" + urlRarity;
|
||||
// example:
|
||||
// - small: https://gatherer-static.wizards.com/set_symbols/FIN/small-common-FIN.png
|
||||
// - big: https://gatherer-static.wizards.com/set_symbols/FIN/large-rare-FIN.png
|
||||
|
||||
String useSet = set.toUpperCase(Locale.ENGLISH);
|
||||
String useSize = "large"; // allow: small, large
|
||||
String url = String.format("https://gatherer-static.wizards.com/set_symbols/%s/%s-%s-%s.png",
|
||||
useSet,
|
||||
useSize,
|
||||
urlRarity,
|
||||
useSet
|
||||
);
|
||||
return new DownloadJob(set + '-' + rarity, url, toFile(dst), false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public class GathererSymbols implements Iterable<DownloadJob> {
|
|||
continue;
|
||||
}
|
||||
String symbol = sym.replaceAll("/", "");
|
||||
File dst = new File(dir, symbol + ".gif");
|
||||
File dst = new File(dir, symbol + ".png");
|
||||
|
||||
// workaround for miss icons on Gatherer (no cards with it, so no icons)
|
||||
// TODO: comment and try download without workaround, keep fix for symbols with "Resource not found" error
|
||||
|
|
|
|||
|
|
@ -588,7 +588,10 @@ public class ScryfallImageSupportCards {
|
|||
add("FIN"); // Final Fantasy
|
||||
add("FIC"); // Final Fantasy Commander
|
||||
add("FCA"); // Final Fantasy: Through the Ages
|
||||
add("EOE"); // Edge of Eternities
|
||||
add("EOC"); // Edge of Eternities Commander
|
||||
add("SPE"); // Marvel's Spider-Man Eternal
|
||||
add("TLA"); // Avatar: The Last Airbender
|
||||
|
||||
// Custom sets using Scryfall images - must provide a direct link for each card in directDownloadLinks
|
||||
add("CALC"); // Custom Alchemized versions of existing cards
|
||||
|
|
|
|||
|
|
@ -352,6 +352,9 @@ public class ScryfallImageSupportTokens {
|
|||
put("ELD/Rat", "https://api.scryfall.com/cards/teld/6/en?format=image");
|
||||
put("ELD/Wolf", "https://api.scryfall.com/cards/teld/14/en?format=image");
|
||||
|
||||
// UND
|
||||
put("UND/Goblin", "https://api.scryfall.com/cards/tund/2?format=image");
|
||||
|
||||
// THB
|
||||
put("THB/Elemental", "https://api.scryfall.com/cards/tthb/8/en?format=image");
|
||||
put("THB/Goat", "https://api.scryfall.com/cards/tthb/1/en?format=image");
|
||||
|
|
@ -829,12 +832,18 @@ public class ScryfallImageSupportTokens {
|
|||
put("SLD/Food/3", "https://api.scryfall.com/cards/sld/2011?format=image");
|
||||
put("SLD/Food/4", "https://api.scryfall.com/cards/sld/2012?format=image");
|
||||
put("SLD/Food/5", "https://api.scryfall.com/cards/sld/2013?format=image");
|
||||
put("SLD/Food/6", "https://api.scryfall.com/cards/sld/2064?format=image");
|
||||
put("SLD/Goblin", "https://api.scryfall.com/cards/sld/219?format=image");
|
||||
put("SLD/Hydra", "https://api.scryfall.com/cards/sld/1334?format=image");
|
||||
put("SLD/Icingdeath, Frost Tongue", "https://api.scryfall.com/cards/sld/1018?format=image");
|
||||
put("SLD/Marit Lage", "https://api.scryfall.com/cards/sld/1681?format=image");
|
||||
put("SLD/Mechtitan", "https://api.scryfall.com/cards/sld/1969?format=image");
|
||||
put("SLD/Myr", "https://api.scryfall.com/cards/sld/2101?format=image");
|
||||
put("SLD/Saproling", "https://api.scryfall.com/cards/sld/1139?format=image");
|
||||
put("SLD/Shapeshifter/1", "https://api.scryfall.com/cards/sld/1906?format=image");
|
||||
put("SLD/Shapeshifter/2", "https://api.scryfall.com/cards/sld/1907?format=image");
|
||||
put("SLD/Shapeshifter/3", "https://api.scryfall.com/cards/sld/1908?format=image");
|
||||
put("SLD/Shapeshifter/4", "https://api.scryfall.com/cards/sld/1909?format=image");
|
||||
put("SLD/Shrine", "https://api.scryfall.com/cards/sld/1835?format=image");
|
||||
put("SLD/Spirit/1", "https://api.scryfall.com/cards/sld/1341?format=image");
|
||||
put("SLD/Spirit/2", "https://api.scryfall.com/cards/sld/1852?format=image");
|
||||
|
|
@ -843,6 +852,8 @@ public class ScryfallImageSupportTokens {
|
|||
put("SLD/Treasure/2", "https://api.scryfall.com/cards/sld/1736/en?format=image");
|
||||
put("SLD/Treasure/3", "https://api.scryfall.com/cards/sld/1507/en?format=image");
|
||||
put("SLD/Treasure/4", "https://api.scryfall.com/cards/sld/153/en?format=image");
|
||||
put("SLD/Treasure/5", "https://api.scryfall.com/cards/sld/2065/en?format=image");
|
||||
put("SLD/Treasure/6", "https://api.scryfall.com/cards/sld/2094/en?format=image");
|
||||
put("SLD/Walker/1", "https://api.scryfall.com/cards/sld/148/en?format=image");
|
||||
put("SLD/Walker/2", "https://api.scryfall.com/cards/sld/149/en?format=image");
|
||||
put("SLD/Walker/3", "https://api.scryfall.com/cards/sld/150/en?format=image");
|
||||
|
|
@ -1843,6 +1854,12 @@ public class ScryfallImageSupportTokens {
|
|||
put("40K/Tyranid Gargoyle", "https://api.scryfall.com/cards/t40k/9/en?format=image");
|
||||
put("40K/Tyranid Warrior", "https://api.scryfall.com/cards/t40k/19/en?format=image");
|
||||
|
||||
// UNF
|
||||
put("UNF/Clown Robot/1", "https://api.scryfall.com/cards/tunf/2?format=image");
|
||||
put("UNF/Clown Robot/2", "https://api.scryfall.com/cards/tunf/3?format=image");
|
||||
put("UNF/Storm Crow", "https://api.scryfall.com/cards/tunf/5?format=image");
|
||||
put("UNF/Squirrel", "https://api.scryfall.com/cards/tunf/8?format=image");
|
||||
|
||||
// BRO
|
||||
put("BRO/Bear", "https://api.scryfall.com/cards/tbro/2/en?format=image");
|
||||
put("BRO/Construct/1", "https://api.scryfall.com/cards/tbro/5/en?format=image");
|
||||
|
|
@ -2026,6 +2043,7 @@ public class ScryfallImageSupportTokens {
|
|||
|
||||
// DIS
|
||||
put("DIS/Emblem Momir", "https://api.scryfall.com/cards/pmoa/61/en?format=image");
|
||||
put("DIS/Elemental", "https://api.scryfall.com/cards/togw/9?format=image");
|
||||
|
||||
// MUL
|
||||
put("MUL/Elemental", "https://api.scryfall.com/cards/tmul/2/en?format=image");
|
||||
|
|
@ -2162,26 +2180,20 @@ public class ScryfallImageSupportTokens {
|
|||
put("WOE/Young Hero", "https://api.scryfall.com/cards/twoe/16/en?format=image");
|
||||
|
||||
// WOC
|
||||
put("WOC/Cat/1", "https://api.scryfall.com/cards/twoc/6/en?format=image");
|
||||
put("WOC/Cat/2", "https://api.scryfall.com/cards/twoc/5/en?format=image");
|
||||
put("WOC/Elephant", "https://api.scryfall.com/cards/twoc/13/en?format=image");
|
||||
put("WOC/Faerie", "https://api.scryfall.com/cards/twoc/10/en?format=image");
|
||||
put("WOC/Faerie Rogue/1", "https://api.scryfall.com/cards/twoc/11/en?format=image");
|
||||
put("WOC/Faerie Rogue/2", "https://api.scryfall.com/cards/twoc/16/en?format=image");
|
||||
put("WOC/Human Monk", "https://api.scryfall.com/cards/twoc/14/en?format=image");
|
||||
put("WOC/Human Soldier", "https://api.scryfall.com/cards/twoc/7/en?format=image");
|
||||
put("WOC/Monster", "https://api.scryfall.com/cards/twoc/1/en?format=image");
|
||||
put("WOC/Ox", "https://api.scryfall.com/cards/twoc/8/en?format=image");
|
||||
put("WOC/Pegasus", "https://api.scryfall.com/cards/twoc/9/en?format=image");
|
||||
put("WOC/Pirate", "https://api.scryfall.com/cards/twoc/12/en?format=image");
|
||||
put("WOC/Royal", "https://api.scryfall.com/cards/twoc/2/en?format=image");
|
||||
put("WOC/Saproling", "https://api.scryfall.com/cards/twoc/15/en?format=image");
|
||||
put("WOC/Sorcerer", "https://api.scryfall.com/cards/twoc/3/en?format=image");
|
||||
put("WOC/Spirit", "https://api.scryfall.com/cards/twoc/17/en?format=image");
|
||||
put("WOC/Virtuous", "https://api.scryfall.com/cards/twoc/3/en?format=image");
|
||||
put("WOC/Virtuous", "https://api.scryfall.com/cards/twoc/2/en?format=image");
|
||||
|
||||
// WHO
|
||||
put("WHO/Alien", "https://api.scryfall.com/cards/twho/2?format=image");
|
||||
put("WHO/Alien Angel", "https://api.scryfall.com/cards/twho/11?format=image");
|
||||
put("WHO/Alien Insect", "https://api.scryfall.com/cards/twho/19/en?format=image");
|
||||
put("WHO/Alien Rhino", "https://api.scryfall.com/cards/twho/3/en?format=image");
|
||||
put("WHO/Alien Salamander", "https://api.scryfall.com/cards/twho/16?format=image");
|
||||
|
|
@ -2536,12 +2548,14 @@ public class ScryfallImageSupportTokens {
|
|||
put("DSK/Everywhere", "https://api.scryfall.com/cards/tdsk/16?format=image");
|
||||
put("DSK/Glimmer", "https://api.scryfall.com/cards/tdsk/4?format=image");
|
||||
put("DSK/Gremlin", "https://api.scryfall.com/cards/tdsk/11?format=image");
|
||||
put("DSK/Horror", "https://api.scryfall.com/cards/tdsk/10?format=image");
|
||||
put("DSK/Insect/1", "https://api.scryfall.com/cards/tdsk/13?format=image");
|
||||
put("DSK/Insect/2", "https://api.scryfall.com/cards/tdsk/5?format=image");
|
||||
put("DSK/Primo, the Indivisible", "https://api.scryfall.com/cards/tdsk/14?format=image");
|
||||
put("DSK/Shard", "https://api.scryfall.com/cards/tdsk/2?format=image");
|
||||
put("DSK/Spider", "https://api.scryfall.com/cards/tdsk/12?format=image");
|
||||
put("DSK/Spirit", "https://api.scryfall.com/cards/tdsk/8?format=image");
|
||||
put("DSK/Spirit/1", "https://api.scryfall.com/cards/tdsk/6?format=image");
|
||||
put("DSK/Spirit/2", "https://api.scryfall.com/cards/tdsk/8?format=image");
|
||||
put("DSK/Treasure", "https://api.scryfall.com/cards/tdsk/15?format=image");
|
||||
|
||||
// DSC
|
||||
|
|
@ -2698,7 +2712,8 @@ public class ScryfallImageSupportTokens {
|
|||
put("TDC/Gold", "https://api.scryfall.com/cards/ttdc/29/en?format=image");
|
||||
put("TDC/Human", "https://api.scryfall.com/cards/ttdc/5/en?format=image");
|
||||
put("TDC/Inkling", "https://api.scryfall.com/cards/ttdc/28?format=image");
|
||||
put("TDC/Insect", "https://api.scryfall.com/cards/ttdc/22/en?format=image");
|
||||
put("TDC/Insect/1", "https://api.scryfall.com/cards/ttdc/22/en?format=image");
|
||||
put("TDC/Insect/2", "https://api.scryfall.com/cards/ttdc/23/en?format=image");
|
||||
put("TDC/Karox Bladewing", "https://api.scryfall.com/cards/ttdc/19?format=image");
|
||||
put("TDC/Myr", "https://api.scryfall.com/cards/ttdc/30/en?format=image");
|
||||
put("TDC/Plant", "https://api.scryfall.com/cards/ttdc/24/en?format=image");
|
||||
|
|
@ -2710,6 +2725,7 @@ public class ScryfallImageSupportTokens {
|
|||
put("TDC/Spider", "https://api.scryfall.com/cards/ttdc/25?format=image");
|
||||
put("TDC/Spirit", "https://api.scryfall.com/cards/ttdc/6/en?format=image");
|
||||
put("TDC/Thopter", "https://api.scryfall.com/cards/ttdc/33/en?format=image");
|
||||
put("TDC/Wall", "https://api.scryfall.com/cards/ttdc/7/en?format=image");
|
||||
|
||||
// ACR
|
||||
put("ACR/Assassin", "https://api.scryfall.com/cards/tacr/4?format=image");
|
||||
|
|
@ -2723,7 +2739,51 @@ public class ScryfallImageSupportTokens {
|
|||
put("DD2/Elemental Shaman", "https://api.scryfall.com/cards/tdd2/1?format=image");
|
||||
|
||||
// FIN
|
||||
put("FIN/Hero/1", "https://api.scryfall.com/cards/tfin/2/en?format=image");
|
||||
put("FIN/Hero/2", "https://api.scryfall.com/cards/tfin/3/en?format=image");
|
||||
put("FIN/Hero/3", "https://api.scryfall.com/cards/tfin/4/en?format=image");
|
||||
put("FIN/Hero/4", "https://api.scryfall.com/cards/tfin/5/en?format=image");
|
||||
put("FIN/Hero/5", "https://api.scryfall.com/cards/tfin/6/en?format=image");
|
||||
put("FIN/Hero/6", "https://api.scryfall.com/cards/tfin/7/en?format=image");
|
||||
put("FIN/Hero/7", "https://api.scryfall.com/cards/tfin/8/en?format=image");
|
||||
put("FIN/Hero/8", "https://api.scryfall.com/cards/tfin/9/en?format=image");
|
||||
put("FIN/Hero/9", "https://api.scryfall.com/cards/tfin/26/en?format=image");
|
||||
put("FIN/Hero/10", "https://api.scryfall.com/cards/tfin/27/en?format=image");
|
||||
put("FIN/Hero/11", "https://api.scryfall.com/cards/tfin/28/en?format=image");
|
||||
put("FIN/Hero/12", "https://api.scryfall.com/cards/tfin/29/en?format=image");
|
||||
put("FIN/Hero/13", "https://api.scryfall.com/cards/tfin/30/en?format=image");
|
||||
put("FIN/Hero/14", "https://api.scryfall.com/cards/tfin/31/en?format=image");
|
||||
put("FIN/Hero/15", "https://api.scryfall.com/cards/tfin/32/en?format=image");
|
||||
put("FIN/Hero/16", "https://api.scryfall.com/cards/tfin/33/en?format=image");
|
||||
put("FIN/Knight", "https://api.scryfall.com/cards/tfin/10/en?format=image");
|
||||
put("FIN/Moogle/1", "https://api.scryfall.com/cards/tfin/11/en?format=image");
|
||||
put("FIN/Moogle/2", "https://api.scryfall.com/cards/tfin/34/en?format=image");
|
||||
put("FIN/Robot", "https://api.scryfall.com/cards/tfin/12/en?format=image");
|
||||
put("FIN/Horror", "https://api.scryfall.com/cards/tfin/13/en?format=image");
|
||||
put("FIN/Wizard/1", "https://api.scryfall.com/cards/tfin/14/en?format=image");
|
||||
put("FIN/Wizard/2", "https://api.scryfall.com/cards/tfin/15/en?format=image");
|
||||
put("FIN/Wizard/3", "https://api.scryfall.com/cards/tfin/35/en?format=image");
|
||||
put("FIN/Bird/1", "https://api.scryfall.com/cards/tfin/16/en?format=image");
|
||||
put("FIN/Bird/2", "https://api.scryfall.com/cards/tfin/17/en?format=image");
|
||||
put("FIN/Frog", "https://api.scryfall.com/cards/tfin/18/en?format=image");
|
||||
put("FIN/Angelo", "https://api.scryfall.com/cards/tfin/19/en?format=image");
|
||||
put("FIN/Darkstar", "https://api.scryfall.com/cards/tfin/20/en?format=image");
|
||||
put("FIN/Elemental", "https://api.scryfall.com/cards/tfin/21/en?format=image");
|
||||
put("FIN/Food", "https://api.scryfall.com/cards/tfin/22?format=image");
|
||||
put("FIN/Treasure/1", "https://api.scryfall.com/cards/tfin/23/en?format=image");
|
||||
put("FIN/Treasure/2", "https://api.scryfall.com/cards/tfin/36/en?format=image");
|
||||
put("FIN/Emblem Sephiroth", "https://api.scryfall.com/cards/tfin/24/en?format=image");
|
||||
|
||||
// FIC
|
||||
put("FIC/Human Soldier", "https://api.scryfall.com/cards/tfic/1/en?format=image");
|
||||
put("FIC/Soldier", "https://api.scryfall.com/cards/tfic/2/en?format=image");
|
||||
put("FIC/Spirit", "https://api.scryfall.com/cards/tfic/3/en?format=image");
|
||||
put("FIC/Bird", "https://api.scryfall.com/cards/tfic/4/en?format=image");
|
||||
put("FIC/Squid", "https://api.scryfall.com/cards/tfic/5/en?format=image");
|
||||
put("FIC/Zombie", "https://api.scryfall.com/cards/tfic/6/en?format=image");
|
||||
put("FIC/Rebel", "https://api.scryfall.com/cards/tfic/7/en?format=image");
|
||||
put("FIC/The Blackjack", "https://api.scryfall.com/cards/tfic/8/en?format=image");
|
||||
put("FIC/Clue", "https://api.scryfall.com/cards/tfic/9/en?format=image");
|
||||
|
||||
// JVC
|
||||
put("JVC/Elemental Shaman", "https://api.scryfall.com/cards/tjvc/4?format=image");
|
||||
|
|
@ -2756,6 +2816,7 @@ public class ScryfallImageSupportTokens {
|
|||
// UGL
|
||||
put("UGL/Goblin", "https://api.scryfall.com/cards/tugl/4?format=image");
|
||||
put("UGL/Pegasus", "https://api.scryfall.com/cards/tugl/1?format=image");
|
||||
put("UGL/Rabid Sheep", "https://api.scryfall.com/cards/tugl/5?format=image");
|
||||
put("UGL/Soldier", "https://api.scryfall.com/cards/tugl/2?format=image");
|
||||
put("UGL/Squirrel", "https://api.scryfall.com/cards/tugl/6?format=image");
|
||||
put("UGL/Zombie", "https://api.scryfall.com/cards/tugl/3?format=image");
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
|
|||
|
||||
@Override
|
||||
public boolean isNeedCancel() {
|
||||
return this.needCancel || (this.errorCount > MAX_ERRORS_COUNT_BEFORE_CANCEL) || Thread.interrupted();
|
||||
return this.needCancel || (this.errorCount > MAX_ERRORS_COUNT_BEFORE_CANCEL) || Thread.currentThread().isInterrupted();
|
||||
}
|
||||
|
||||
private void setNeedCancel(boolean needCancel) {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
package mage.cards.action.impl;
|
||||
|
||||
import mage.cards.action.ActionCallback;
|
||||
import mage.cards.action.TransferData;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
|
||||
/**
|
||||
* Callback that does nothing on any action
|
||||
*
|
||||
* @author nantuko84
|
||||
*/
|
||||
public class EmptyCallback implements ActionCallback {
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e, TransferData data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e, TransferData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e, TransferData data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e, TransferData data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved(int mouseWheelRotation, TransferData data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideOpenComponents() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e, TransferData data, boolean doubleClick) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e, TransferData data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e, TransferData data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuCard(MouseEvent e, TransferData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuPanel(MouseEvent e, Component sourceComponent) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
package mage.filters;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.BufferedImageOp;
|
||||
import java.awt.image.ColorModel;
|
||||
|
||||
/**
|
||||
* Mage abstract class that implements single-input/single-output
|
||||
* operations performed on {@link java.awt.image.BufferedImage}.
|
||||
*
|
||||
* @author nantuko
|
||||
*/
|
||||
public abstract class MageBufferedImageOp implements BufferedImageOp {
|
||||
|
||||
/**
|
||||
* Creates compatible image for @param src image.
|
||||
*/
|
||||
@Override
|
||||
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dest) {
|
||||
if (dest == null) {
|
||||
dest = src.getColorModel();
|
||||
}
|
||||
return new BufferedImage(dest, dest.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dest.isAlphaPremultiplied(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderingHints getRenderingHints() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle2D getBounds2D(BufferedImage src) {
|
||||
return new Rectangle(0, 0, src.getWidth(), src.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getPoint2D(Point2D srcPt, Point2D destPt) {
|
||||
if (destPt == null) {
|
||||
destPt = new Point2D.Double();
|
||||
}
|
||||
destPt.setLocation(srcPt.getX(), srcPt.getY());
|
||||
return destPt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets ARGB pixels from image. Solves the performance
|
||||
* issue of BufferedImage.getRGB method.
|
||||
*/
|
||||
public int[] getRGB(BufferedImage image, int x, int y, int width, int height, int[] pixels) {
|
||||
int type = image.getType();
|
||||
if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB) {
|
||||
return (int[]) image.getRaster().getDataElements(x, y, width, height, pixels);
|
||||
}
|
||||
return image.getRGB(x, y, width, height, pixels, 0, width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets ARGB pixels in image. Solves the performance
|
||||
* issue of BufferedImage.setRGB method.
|
||||
*/
|
||||
public void setRGB(BufferedImage image, int x, int y, int width, int height, int[] pixels) {
|
||||
int type = image.getType();
|
||||
if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB) {
|
||||
image.getRaster().setDataElements(x, y, width, height, pixels);
|
||||
} else {
|
||||
image.setRGB(x, y, width, height, pixels, 0, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package mage.utils;
|
||||
|
||||
import mage.view.TableView;
|
||||
|
||||
/**
|
||||
* Used to write less code for ActionWithResult anonymous classes with UUID return type.
|
||||
*
|
||||
* @author noxx
|
||||
*/
|
||||
public abstract class ActionWithUUIDResult extends ActionWithNullNegativeResult<TableView> {
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package mage.utils.testers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Part of testable game dialogs
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class AmountTestableResult extends BaseTestableResult {
|
||||
|
||||
Integer amount = null;
|
||||
|
||||
boolean aiAssertEnabled = false;
|
||||
int aiAssertMinAmount = 0;
|
||||
int aiAssertMaxAmount = 0;
|
||||
|
||||
public void onFinish(String resDebugSource, boolean status, List<String> info, int amount) {
|
||||
this.onFinish(resDebugSource, status, info);
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResAssert() {
|
||||
if (!this.aiAssertEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// not finished
|
||||
if (this.amount == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.getResStatus()) {
|
||||
return String.format("Wrong status: need %s, but get %s",
|
||||
true, // res must be true all the time
|
||||
this.getResStatus()
|
||||
);
|
||||
}
|
||||
|
||||
// wrong amount
|
||||
if (this.amount < this.aiAssertMinAmount || this.amount > this.aiAssertMaxAmount) {
|
||||
return String.format("Wrong amount: need [%d, %d], but get %d",
|
||||
this.aiAssertMinAmount,
|
||||
this.aiAssertMaxAmount,
|
||||
this.amount
|
||||
);
|
||||
}
|
||||
|
||||
// all fine
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClear() {
|
||||
super.onClear();
|
||||
this.amount = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package mage.utils.testers;
|
|||
import mage.abilities.Ability;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.util.DebugUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -25,22 +26,33 @@ class AnnounceXTestableDialog extends BaseTestableDialog {
|
|||
|
||||
public AnnounceXTestableDialog(boolean isYou, boolean isMana, int min, int max) {
|
||||
super(String.format("player.announceX(%s)", isYou ? "you" : "AI"),
|
||||
String.format("%s from %d to %d", isMana ? "mana" : "cost", min, max), "");
|
||||
String.format("%s from %d to %d", isMana ? "mana" : "cost", min, max), "",
|
||||
new AmountTestableResult());
|
||||
this.isYou = isYou;
|
||||
this.isMana = isMana;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
private AnnounceXTestableDialog aiMustChoose(int minAmount, int maxAmount) {
|
||||
// require min/max cause AI logic uses random choices
|
||||
AmountTestableResult res = ((AmountTestableResult) this.getResult());
|
||||
res.aiAssertEnabled = true;
|
||||
res.aiAssertMinAmount = minAmount;
|
||||
res.aiAssertMaxAmount = maxAmount;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
Player choosingPlayer = this.isYou ? player : opponent;
|
||||
String message = "<font color=green>message</font> with html";
|
||||
int chooseRes;
|
||||
chooseRes = choosingPlayer.announceX(this.min, this.max, message, game, source, this.isMana);
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
|
||||
return result;
|
||||
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
int chooseRes = choosingPlayer.announceX(this.min, this.max, message, game, source, this.isMana);
|
||||
List<String> res = new ArrayList<>();
|
||||
res.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
|
||||
|
||||
((AmountTestableResult) this.getResult()).onFinish(chooseDebugSource, true, res, chooseRes);
|
||||
}
|
||||
|
||||
static public void register(TestableDialogsRunner runner) {
|
||||
|
|
@ -48,17 +60,17 @@ class AnnounceXTestableDialog extends BaseTestableDialog {
|
|||
List<Boolean> isManas = Arrays.asList(false, true);
|
||||
for (boolean isYou : isYous) {
|
||||
for (boolean isMana : isManas) {
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 0));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 1));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 3));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 50));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 500));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 1));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 3));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 50));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 3));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 10));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 10, 10));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 0).aiMustChoose(0, 0));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 1).aiMustChoose(0, 1));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 3).aiMustChoose(0, 3));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 50).aiMustChoose(0, 50));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 500).aiMustChoose(0, 500));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 1).aiMustChoose(1, 1));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 3).aiMustChoose(1, 3));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 50).aiMustChoose(1, 50));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 3).aiMustChoose(3, 3));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 10).aiMustChoose(3, 10));
|
||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 10, 10).aiMustChoose(10, 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package mage.utils.testers;
|
||||
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.mageobject.ManaValuePredicate;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetPermanentOrPlayer;
|
||||
|
||||
/**
|
||||
|
|
@ -15,14 +16,27 @@ import mage.target.common.TargetPermanentOrPlayer;
|
|||
*/
|
||||
abstract class BaseTestableDialog implements TestableDialog {
|
||||
|
||||
private Integer regNumber; // dialog number in runner (use it to find results and debugging)
|
||||
private final String group;
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final TestableResult result;
|
||||
|
||||
public BaseTestableDialog(String group, String name, String description) {
|
||||
public BaseTestableDialog(String group, String name, String description, TestableResult result) {
|
||||
this.group = group;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegNumber(Integer regNumber) {
|
||||
this.regNumber = regNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getRegNumber() {
|
||||
return this.regNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -41,13 +55,23 @@ abstract class BaseTestableDialog implements TestableDialog {
|
|||
}
|
||||
|
||||
@Override
|
||||
final public void showResult(Player player, Game game, String result) {
|
||||
public void prepare() {
|
||||
this.result.onClear();
|
||||
}
|
||||
|
||||
@Override
|
||||
final public void showResult(Player player, Game game) {
|
||||
// show message with result
|
||||
game.informPlayer(player, result);
|
||||
game.informPlayer(player, String.join("<br>", getResult().getResDetails()));
|
||||
// reset game and gui (in most use cases it must return to player's priority)
|
||||
game.firePriorityEvent(player.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestableResult getResult() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
static Target createAnyTarget(int min, int max) {
|
||||
return createAnyTarget(min, max, false);
|
||||
}
|
||||
|
|
@ -56,19 +80,18 @@ abstract class BaseTestableDialog implements TestableDialog {
|
|||
return new TargetPermanentOrPlayer(min, max).withNotTarget(notTarget);
|
||||
}
|
||||
|
||||
static Target createCreatureTarget(int min, int max) {
|
||||
return createCreatureTarget(min, max, false);
|
||||
}
|
||||
private static final FilterPermanent impossibleFilter = new FilterPermanent();
|
||||
|
||||
private static Target createCreatureTarget(int min, int max, boolean notTarget) {
|
||||
return new TargetCreaturePermanent(min, max).withNotTarget(notTarget);
|
||||
static {
|
||||
impossibleFilter.add(new ManaValuePredicate(ComparisonType.OR_LESS, -1));
|
||||
}
|
||||
|
||||
static Target createImpossibleTarget(int min, int max) {
|
||||
return createImpossibleTarget(min, max, false);
|
||||
return new TargetPermanent(min, max, impossibleFilter);
|
||||
}
|
||||
|
||||
private static Target createImpossibleTarget(int min, int max, boolean notTarget) {
|
||||
return new TargetCreaturePermanent(min, max, new FilterCreaturePermanent(SubType.TROOPER, "rare type"), notTarget);
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getGroup() + " - " + this.getName() + " - " + this.getDescription();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
package mage.utils.testers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Part of testable game dialogs
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class BaseTestableResult implements TestableResult {
|
||||
|
||||
boolean isFinished = false;
|
||||
String resDebugSource = ""; // source code line to find starting place to debug
|
||||
boolean resStatus = false;
|
||||
List<String> resInfo = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public String getResDebugSource() {
|
||||
return this.resDebugSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getResStatus() {
|
||||
return this.resStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getResDetails() {
|
||||
return this.resInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResAssert() {
|
||||
return null; // TODO: implement
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(String resDebugSource, boolean resStatus, List<String> resDetails) {
|
||||
this.isFinished = true;
|
||||
this.resDebugSource = resDebugSource;
|
||||
this.resStatus = resStatus;
|
||||
this.resInfo = resDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return this.isFinished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClear() {
|
||||
this.isFinished = false;
|
||||
this.resStatus = false;
|
||||
this.resInfo.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package mage.utils.testers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Part of testable game dialogs
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class ChoiceTestableResult extends BaseTestableResult {
|
||||
|
||||
String choice = null;
|
||||
|
||||
public void onFinish(String resDebugSource, boolean status, List<String> info, String choice) {
|
||||
this.onFinish(resDebugSource, status, info);
|
||||
this.choice = choice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResAssert() {
|
||||
return null; // TODO: implement
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClear() {
|
||||
super.onClear();
|
||||
this.choice = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import mage.players.Player;
|
|||
import mage.target.TargetAmount;
|
||||
import mage.target.Targets;
|
||||
import mage.target.common.TargetAnyTargetAmount;
|
||||
import mage.util.DebugUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -32,27 +33,39 @@ class ChooseAmountTestableDialog extends BaseTestableDialog {
|
|||
public ChooseAmountTestableDialog(boolean isYou, String name, int distributeAmount, int targetsMin, int targetsMax) {
|
||||
super(String.format("player.chooseTarget(%s, amount)", isYou ? "you" : "AI"),
|
||||
name,
|
||||
String.format("%d between %d-%d targets", distributeAmount, targetsMin, targetsMax));
|
||||
String.format("%d between %d-%d targets", distributeAmount, targetsMin, targetsMax),
|
||||
new TargetTestableResult());
|
||||
this.isYou = isYou;
|
||||
this.distributeAmount = distributeAmount;
|
||||
this.targetsMin = targetsMin;
|
||||
this.targetsMax = targetsMax;
|
||||
}
|
||||
|
||||
private ChooseAmountTestableDialog aiMustChoose(boolean resStatus, int targetsCount) {
|
||||
// TODO: AI use default distribution, improve someday
|
||||
TargetTestableResult res = ((TargetTestableResult) this.getResult());
|
||||
res.aiAssertEnabled = true;
|
||||
res.aiAssertResStatus = resStatus;
|
||||
res.aiAssertTargetsCount = targetsCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
TargetAmount choosingTarget = new TargetAnyTargetAmount(this.distributeAmount, this.targetsMin, this.targetsMax);
|
||||
Player choosingPlayer = this.isYou ? player : opponent;
|
||||
|
||||
// TODO: add "damage" word in ability text, so chooseTargetAmount an show diff dialog (due inner logic - distribute damage or 1/1)
|
||||
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
boolean chooseRes = choosingPlayer.chooseTargetAmount(Outcome.Benefit, choosingTarget, source, game);
|
||||
List<String> result = new ArrayList<>();
|
||||
List<String> res = new ArrayList<>();
|
||||
if (chooseRes) {
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, res);
|
||||
} else {
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, res);
|
||||
}
|
||||
return result;
|
||||
|
||||
((TargetTestableResult) this.getResult()).onFinish(chooseDebugSource, chooseRes, res, choosingTarget);
|
||||
}
|
||||
|
||||
static public void register(TestableDialogsRunner runner) {
|
||||
|
|
@ -61,53 +74,55 @@ class ChooseAmountTestableDialog extends BaseTestableDialog {
|
|||
|
||||
List<Boolean> isYous = Arrays.asList(false, true);
|
||||
|
||||
// current AI will choose 1 target and assign all values to it (except with outcome.Damage)
|
||||
// TODO: add use cases for damage effects?
|
||||
for (boolean isYou : isYous) {
|
||||
// up to
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 3));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 5));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 0).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 1).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 3).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 0, 0, 5).aiMustChoose(false, 0));
|
||||
//
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 1, 0, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 3));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 5));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 1, 0, 0).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 1).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 3).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 1, 0, 5).aiMustChoose(true, 1));
|
||||
//
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 2, 0, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 3));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 5));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 2, 0, 0).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 1).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 3).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 2, 0, 5).aiMustChoose(true, 1));
|
||||
//
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 3, 0, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 3));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 5));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 3, 0, 0).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 1).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 3).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 3, 0, 5).aiMustChoose(true, 1));
|
||||
//
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 5, 0, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 3));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 5));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to, invalid", 5, 0, 0).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 1).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 3).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "up to", 5, 0, 5).aiMustChoose(true, 1));
|
||||
|
||||
// need target
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 3));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 5));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 1).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 3).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 0, 1, 5).aiMustChoose(false, 0));
|
||||
//
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 3));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 5));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 1).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 3).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 1, 1, 5).aiMustChoose(true, 1));
|
||||
//
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 3));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 5));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 1).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 3).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 2, 1, 5).aiMustChoose(true, 1));
|
||||
//
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 3));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 5));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 1).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 3).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 3, 1, 5).aiMustChoose(true, 1));
|
||||
//
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 3));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 5));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 1).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 3).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseAmountTestableDialog(isYou, "need", 5, 1, 5).aiMustChoose(true, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import mage.players.Player;
|
|||
import mage.target.TargetCard;
|
||||
import mage.target.Targets;
|
||||
import mage.target.common.TargetCardInHand;
|
||||
import mage.util.DebugUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -38,14 +39,15 @@ class ChooseCardsTestableDialog extends BaseTestableDialog {
|
|||
super(String.format("%s(%s, %s, cards)",
|
||||
isTargetChoice ? "player.chooseTarget" : "player.choose",
|
||||
isYou ? "you" : "AI",
|
||||
notTarget ? "not target" : "target"), name, target.toString());
|
||||
notTarget ? "not target" : "target"), name, target.toString(),
|
||||
new TargetTestableResult());
|
||||
this.isTargetChoice = isTargetChoice;
|
||||
this.target = target.withNotTarget(notTarget);
|
||||
this.isYou = isYou;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
TargetCard choosingTarget = this.target.copy();
|
||||
Player choosingPlayer = this.isYou ? player : opponent;
|
||||
|
||||
|
|
@ -56,19 +58,23 @@ class ChooseCardsTestableDialog extends BaseTestableDialog {
|
|||
Cards choosingCards = new CardsImpl(all.stream().limit(100).collect(Collectors.toList()));
|
||||
|
||||
boolean chooseRes;
|
||||
String chooseDebugSource;
|
||||
if (this.isTargetChoice) {
|
||||
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
chooseRes = choosingPlayer.chooseTarget(Outcome.Benefit, choosingCards, choosingTarget, source, game);
|
||||
} else {
|
||||
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
chooseRes = choosingPlayer.choose(Outcome.Benefit, choosingCards, choosingTarget, source, game);
|
||||
}
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
List<String> res = new ArrayList<>();
|
||||
if (chooseRes) {
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, res);
|
||||
} else {
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, res);
|
||||
}
|
||||
return result;
|
||||
|
||||
((TargetTestableResult) this.getResult()).onFinish(chooseDebugSource, chooseRes, res, choosingTarget);
|
||||
}
|
||||
|
||||
static public void register(TestableDialogsRunner runner) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import mage.choices.*;
|
|||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.util.DebugUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -24,28 +25,36 @@ class ChooseChoiceTestableDialog extends BaseTestableDialog {
|
|||
Choice choice;
|
||||
|
||||
public ChooseChoiceTestableDialog(boolean isYou, String name, Choice choice) {
|
||||
super(String.format("player.choose(%s, choice)", isYou ? "you" : "AI"), name, choice.getClass().getSimpleName());
|
||||
super(String.format("player.choose(%s, choice)", isYou ? "you" : "AI"),
|
||||
name,
|
||||
choice.getClass().getSimpleName(),
|
||||
new ChoiceTestableResult()
|
||||
);
|
||||
this.isYou = isYou;
|
||||
this.choice = choice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
Player choosingPlayer = this.isYou ? player : opponent;
|
||||
Choice dialog = this.choice.copy();
|
||||
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
boolean chooseRes = choosingPlayer.choose(Outcome.Benefit, dialog, game);
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
|
||||
result.add("");
|
||||
List<String> res = new ArrayList<>();
|
||||
res.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
|
||||
res.add("");
|
||||
String choice;
|
||||
if (dialog.isKeyChoice()) {
|
||||
String key = dialog.getChoiceKey();
|
||||
result.add(String.format("* selected key: %s (%s)", key, dialog.getKeyChoices().getOrDefault(key, null)));
|
||||
choice = dialog.getKeyChoices().getOrDefault(key, null);
|
||||
res.add(String.format("* selected key: %s (%s)", key, choice));
|
||||
} else {
|
||||
result.add(String.format("* selected value: %s", dialog.getChoice()));
|
||||
choice = dialog.getChoice();
|
||||
res.add(String.format("* selected value: %s", choice));
|
||||
}
|
||||
|
||||
return result;
|
||||
((ChoiceTestableResult) this.getResult()).onFinish(chooseDebugSource, chooseRes, res, choice);
|
||||
}
|
||||
|
||||
static public void register(TestableDialogsRunner runner) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import mage.constants.Outcome;
|
|||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.DebugUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -28,14 +29,18 @@ class ChoosePileTestableDialog extends BaseTestableDialog {
|
|||
int pileSize2;
|
||||
|
||||
public ChoosePileTestableDialog(boolean isYou, int pileSize1, int pileSize2) {
|
||||
super(String.format("player.choosePile(%s)", isYou ? "you" : "AI"), "pile sizes: " + pileSize1 + " and " + pileSize2, "");
|
||||
super(String.format("player.choosePile(%s)", isYou ? "you" : "AI"),
|
||||
"pile sizes: " + pileSize1 + " and " + pileSize2,
|
||||
"",
|
||||
new BaseTestableResult()
|
||||
);
|
||||
this.isYou = isYou;
|
||||
this.pileSize1 = pileSize1;
|
||||
this.pileSize2 = pileSize2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
// TODO: it's ok to show broken title - must add html support in windows's title someday
|
||||
String mainMessage = "main <font color=green>message</font> with html" + CardUtil.getSourceLogName(game, source);
|
||||
|
||||
|
|
@ -47,11 +52,13 @@ class ChoosePileTestableDialog extends BaseTestableDialog {
|
|||
List<Card> pile2 = all.stream().limit(this.pileSize2).collect(Collectors.toList());
|
||||
|
||||
Player choosingPlayer = this.isYou ? player : opponent;
|
||||
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
boolean chooseRes = choosingPlayer.choosePile(Outcome.Benefit, mainMessage, pile1, pile2, game);
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
|
||||
result.add(" * selected pile: " + (chooseRes ? "pile 1" : "pile 2"));
|
||||
return result;
|
||||
List<String> res = new ArrayList<>();
|
||||
res.add(getGroup() + " - " + this.getName() + " - " + (chooseRes ? "TRUE" : "FALSE"));
|
||||
res.add(" * selected pile: " + (chooseRes ? "pile 1" : "pile 2"));
|
||||
|
||||
this.getResult().onFinish(chooseDebugSource, chooseRes, res);
|
||||
}
|
||||
|
||||
static public void register(TestableDialogsRunner runner) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import mage.game.Game;
|
|||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.Targets;
|
||||
import mage.util.DebugUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -34,7 +35,11 @@ class ChooseTargetTestableDialog extends BaseTestableDialog {
|
|||
isPlayerChoice ? "player.choose" : "target.choose",
|
||||
isTargetChoice ? "Target" : "", // chooseTarget or choose
|
||||
isYou ? "you" : "AI",
|
||||
notTarget ? "not target" : "target"), name, target.toString());
|
||||
notTarget ? "not target" : "target"),
|
||||
name,
|
||||
target.toString(),
|
||||
new TargetTestableResult()
|
||||
);
|
||||
this.isPlayerChoice = isPlayerChoice;
|
||||
this.isTargetChoice = isTargetChoice;
|
||||
this.target = target.withNotTarget(notTarget);
|
||||
|
|
@ -42,34 +47,48 @@ class ChooseTargetTestableDialog extends BaseTestableDialog {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
Target choosingTarget = this.target.copy();
|
||||
Player choosingPlayer = this.isYou ? player : opponent;
|
||||
|
||||
boolean chooseRes;
|
||||
String chooseDebugSource;
|
||||
if (this.isPlayerChoice) {
|
||||
// player.chooseXXX
|
||||
if (this.isTargetChoice) {
|
||||
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
chooseRes = choosingPlayer.chooseTarget(Outcome.Benefit, choosingTarget, source, game);
|
||||
} else {
|
||||
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
chooseRes = choosingPlayer.choose(Outcome.Benefit, choosingTarget, source, game);
|
||||
}
|
||||
} else {
|
||||
// target.chooseXXX
|
||||
if (this.isTargetChoice) {
|
||||
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
chooseRes = choosingTarget.chooseTarget(Outcome.Benefit, choosingPlayer.getId(), source, game);
|
||||
} else {
|
||||
chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
chooseRes = choosingTarget.choose(Outcome.Benefit, choosingPlayer.getId(), source, game);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
List<String> res = new ArrayList<>();
|
||||
if (chooseRes) {
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, result);
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "TRUE", new Targets(choosingTarget), source, game, res);
|
||||
} else {
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, result);
|
||||
Targets.printDebugTargets(getGroup() + " - " + this.getName() + " - " + "FALSE", new Targets(choosingTarget), source, game, res);
|
||||
}
|
||||
return result;
|
||||
|
||||
((TargetTestableResult) this.getResult()).onFinish(chooseDebugSource, chooseRes, res, choosingTarget);
|
||||
}
|
||||
|
||||
private ChooseTargetTestableDialog aiMustChoose(boolean resStatus, int targetsCount) {
|
||||
TargetTestableResult res = ((TargetTestableResult) this.getResult());
|
||||
res.aiAssertEnabled = true;
|
||||
res.aiAssertResStatus = resStatus;
|
||||
res.aiAssertTargetsCount = targetsCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
static public void register(TestableDialogsRunner runner) {
|
||||
|
|
@ -84,37 +103,29 @@ class ChooseTargetTestableDialog extends BaseTestableDialog {
|
|||
for (boolean isYou : isYous) {
|
||||
for (boolean isTargetChoice : isTargetChoices) {
|
||||
for (boolean isPlayerChoice : isPlayerChoices) {
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0 e.g. X=0", createAnyTarget(0, 0))); // simulate X=0
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1", createAnyTarget(1, 1)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3", createAnyTarget(3, 3)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 5", createAnyTarget(5, 5)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any max", createAnyTarget(0, Integer.MAX_VALUE)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-1", createAnyTarget(0, 1)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-3", createAnyTarget(0, 3)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-5", createAnyTarget(0, 5)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-3", createAnyTarget(1, 3)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-3", createAnyTarget(2, 3)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-5", createAnyTarget(1, 5)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-5", createAnyTarget(2, 5)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3-5", createAnyTarget(3, 5)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 4-5", createAnyTarget(4, 5))); // impossible on 3 targets
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0 e.g. X=0", createAnyTarget(0, 0)).aiMustChoose(false, 0)); // simulate X=0
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1", createAnyTarget(1, 1)).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3", createAnyTarget(3, 3)).aiMustChoose(true, 3));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 5", createAnyTarget(5, 5)).aiMustChoose(true, 5));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any max", createAnyTarget(0, Integer.MAX_VALUE)).aiMustChoose(true, 6 + 1)); // 6 own cards + 1 own player
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-1", createAnyTarget(0, 1)).aiMustChoose(true, 1));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-3", createAnyTarget(0, 3)).aiMustChoose(true, 3));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 0-5", createAnyTarget(0, 5)).aiMustChoose(true, 5));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-3", createAnyTarget(1, 3)).aiMustChoose(true, 3));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-3", createAnyTarget(2, 3)).aiMustChoose(true, 3));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 1-5", createAnyTarget(1, 5)).aiMustChoose(true, 5));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 2-5", createAnyTarget(2, 5)).aiMustChoose(true, 5));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 3-5", createAnyTarget(3, 5)).aiMustChoose(true, 5));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "any 4-5", createAnyTarget(4, 5)).aiMustChoose(true, 5));
|
||||
//
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0, e.g. X=0", createImpossibleTarget(0, 0)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1", createImpossibleTarget(1, 1)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 3", createImpossibleTarget(3, 3)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-1", createImpossibleTarget(0, 1)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-3", createImpossibleTarget(0, 3)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1-3", createImpossibleTarget(1, 3)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 2-3", createImpossibleTarget(2, 3)));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible max", createImpossibleTarget(0, Integer.MAX_VALUE)));
|
||||
//
|
||||
/*
|
||||
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 0, e.g. X=0", createCreatureTarget(0, 0))); // simulate X=0
|
||||
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 1", createCreatureTarget(1, 1)));
|
||||
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 3", createCreatureTarget(3, 3)));
|
||||
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures 5", createCreatureTarget(5, 5)));
|
||||
runner.registerDialog(new PlayerChooseTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "creatures max", createCreatureTarget(0, Integer.MAX_VALUE)));
|
||||
*/
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0, e.g. X=0", createImpossibleTarget(0, 0)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1", createImpossibleTarget(1, 1)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 3", createImpossibleTarget(3, 3)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-1", createImpossibleTarget(0, 1)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 0-3", createImpossibleTarget(0, 3)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 1-3", createImpossibleTarget(1, 3)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible 2-3", createImpossibleTarget(2, 3)).aiMustChoose(false, 0));
|
||||
runner.registerDialog(new ChooseTargetTestableDialog(isPlayerChoice, isTargetChoice, notTarget, isYou, "impossible max", createImpossibleTarget(0, Integer.MAX_VALUE)).aiMustChoose(false, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import mage.constants.Outcome;
|
|||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.DebugUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -27,7 +28,11 @@ class ChooseUseTestableDialog extends BaseTestableDialog {
|
|||
String messageAdditional;
|
||||
|
||||
public ChooseUseTestableDialog(boolean isYou, String name, String trueText, String falseText, String messageMain, String messageAdditional) {
|
||||
super(String.format("player.chooseUse(%s)", isYou ? "you" : "AI"), name + buildName(trueText, falseText, messageMain, messageAdditional), "");
|
||||
super(String.format("player.chooseUse(%s)", isYou ? "you" : "AI"),
|
||||
name + buildName(trueText, falseText, messageMain, messageAdditional),
|
||||
"",
|
||||
new BaseTestableResult()
|
||||
);
|
||||
this.isYou = isYou;
|
||||
this.trueText = trueText;
|
||||
this.falseText = falseText;
|
||||
|
|
@ -42,8 +47,9 @@ class ChooseUseTestableDialog extends BaseTestableDialog {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
Player choosingPlayer = this.isYou ? player : opponent;
|
||||
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
boolean chooseRes = choosingPlayer.chooseUse(
|
||||
Outcome.Benefit,
|
||||
messageMain,
|
||||
|
|
@ -53,9 +59,10 @@ class ChooseUseTestableDialog extends BaseTestableDialog {
|
|||
source,
|
||||
game
|
||||
);
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add(chooseRes ? "TRUE" : "FALSE");
|
||||
return result;
|
||||
List<String> res = new ArrayList<>();
|
||||
res.add(chooseRes ? "TRUE" : "FALSE");
|
||||
|
||||
this.getResult().onFinish(chooseDebugSource, chooseRes, res);
|
||||
}
|
||||
|
||||
static public void register(TestableDialogsRunner runner) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package mage.utils.testers;
|
|||
import mage.abilities.Ability;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.util.DebugUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -26,37 +27,53 @@ class GetAmountTestableDialog extends BaseTestableDialog {
|
|||
|
||||
public GetAmountTestableDialog(boolean isYou, int min, int max) {
|
||||
super(String.format("player.getAmount(%s)", isYou ? "you" : "AI"),
|
||||
String.format("from %d to %d", min, max), "");
|
||||
String.format("from %d to %d", min, max),
|
||||
"",
|
||||
new AmountTestableResult()
|
||||
);
|
||||
this.isYou = isYou;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
private GetAmountTestableDialog aiMustChoose(int minAmount, int maxAmount) {
|
||||
// require min/max cause AI logic uses random choices
|
||||
AmountTestableResult res = ((AmountTestableResult) this.getResult());
|
||||
res.aiAssertEnabled = true;
|
||||
res.aiAssertMinAmount = minAmount;
|
||||
res.aiAssertMaxAmount = maxAmount;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
Player choosingPlayer = this.isYou ? player : opponent;
|
||||
String message = "<font color=green>message</font> with html";
|
||||
int chooseRes;
|
||||
chooseRes = choosingPlayer.getAmount(this.min, this.max, message, source, game);
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
|
||||
return result;
|
||||
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
int chooseRes = choosingPlayer.getAmount(this.min, this.max, message, source, game);
|
||||
List<String> res = new ArrayList<>();
|
||||
res.add(getGroup() + " - " + this.getName() + " selected " + chooseRes);
|
||||
|
||||
((AmountTestableResult) this.getResult()).onFinish(chooseDebugSource, true, res, chooseRes);
|
||||
}
|
||||
|
||||
static public void register(TestableDialogsRunner runner) {
|
||||
List<Boolean> isYous = Arrays.asList(false, true);
|
||||
for (boolean isYou : isYous) {
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 0));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 1));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 3));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 50));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 500));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 1));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 3));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 50));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 3));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 10));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 10, 10));
|
||||
// TODO: add good and bad effects:
|
||||
// - on good: choose random big value
|
||||
// - on bad: choose lower value
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 0).aiMustChoose(0, 0));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 1).aiMustChoose(0, 1));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 3).aiMustChoose(0, 3));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 50).aiMustChoose(0, 50));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 500).aiMustChoose(0, 500));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 1).aiMustChoose(1, 1));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 3).aiMustChoose(1, 3));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 50).aiMustChoose(1, 50));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 3).aiMustChoose(3, 3));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 10).aiMustChoose(3, 10));
|
||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 10, 10).aiMustChoose(10, 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ import mage.constants.MultiAmountType;
|
|||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.util.DebugUtil;
|
||||
import mage.util.MultiAmountMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Part of testable game dialogs
|
||||
|
|
@ -36,7 +38,9 @@ class GetMultiAmountTestableDialog extends BaseTestableDialog {
|
|||
public GetMultiAmountTestableDialog(boolean isYou, String info, int totalMin, int totalMax, List<List<Integer>> options) {
|
||||
super(String.format("player.getMultiAmount(%s)", isYou ? "you" : "AI"),
|
||||
String.format("%s, %d options from [%d-%d]", info, options.size(), totalMin, totalMax),
|
||||
"");
|
||||
"",
|
||||
new MultiAmountTestableResult()
|
||||
);
|
||||
this.isYou = isYou;
|
||||
this.totalMin = totalMin;
|
||||
this.totalMax = totalMax;
|
||||
|
|
@ -48,13 +52,35 @@ class GetMultiAmountTestableDialog extends BaseTestableDialog {
|
|||
}
|
||||
}
|
||||
|
||||
private GetMultiAmountTestableDialog aiMustChoose(Integer... needValues) {
|
||||
// TODO: AI use default distribution:
|
||||
// - bad effect: min possible values
|
||||
// - good effect: max possible and distributed values
|
||||
MultiAmountTestableResult res = ((MultiAmountTestableResult) this.getResult());
|
||||
res.aiAssertEnabled = true;
|
||||
res.aiAssertValues = Arrays.stream(needValues).collect(Collectors.toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
private GetMultiAmountTestableDialog aiMustChooseMany(Integer options, Integer perOption) {
|
||||
List<Integer> need = new ArrayList<>();
|
||||
IntStream.rangeClosed(1, options).forEach(x -> {
|
||||
need.add(perOption);
|
||||
});
|
||||
|
||||
MultiAmountTestableResult res = ((MultiAmountTestableResult) this.getResult());
|
||||
res.aiAssertEnabled = true;
|
||||
res.aiAssertValues = need;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
public void showDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
Player choosingPlayer = this.isYou ? player : opponent;
|
||||
//String message = "<font color=green>message</font> with html";
|
||||
List<Integer> chooseRes;
|
||||
List<MultiAmountMessage> options = this.amountOptions.stream().map(MultiAmountMessage::copy).collect(Collectors.toList());
|
||||
chooseRes = choosingPlayer.getMultiAmountWithIndividualConstraints(
|
||||
String chooseDebugSource = DebugUtil.getMethodNameWithSource(0, "class");
|
||||
List<Integer> chooseRes = choosingPlayer.getMultiAmountWithIndividualConstraints(
|
||||
Outcome.Benefit,
|
||||
options,
|
||||
this.totalMin,
|
||||
|
|
@ -63,24 +89,24 @@ class GetMultiAmountTestableDialog extends BaseTestableDialog {
|
|||
game
|
||||
);
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add(getGroup() + " - " + this.getName());
|
||||
List<String> res = new ArrayList<>();
|
||||
res.add(getGroup() + " - " + this.getName());
|
||||
int selectedIndex = -1;
|
||||
int selectedTotal = 0;
|
||||
for (Integer selectedValue : chooseRes) {
|
||||
selectedIndex++;
|
||||
selectedTotal += selectedValue;
|
||||
MultiAmountMessage option = this.amountOptions.get(selectedIndex);
|
||||
result.add(String.format("%d from [%d-%d, def %d]",
|
||||
res.add(String.format("%d from [%d-%d, def %d]",
|
||||
selectedValue,
|
||||
option.min,
|
||||
option.max,
|
||||
option.defaultValue
|
||||
));
|
||||
}
|
||||
result.add("total selected: " + selectedTotal);
|
||||
res.add("total selected: " + selectedTotal);
|
||||
|
||||
return result;
|
||||
((MultiAmountTestableResult) this.getResult()).onFinish(chooseDebugSource, true, res, chooseRes);
|
||||
}
|
||||
|
||||
static public void register(TestableDialogsRunner runner) {
|
||||
|
|
@ -88,28 +114,29 @@ class GetMultiAmountTestableDialog extends BaseTestableDialog {
|
|||
for (boolean isYou : isYous) {
|
||||
// make sure default values are valid due min/max settings
|
||||
|
||||
// TODO: add bad effect for AI (must test default distribution)
|
||||
|
||||
// single target
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 0 def", 0, 1, genSameOptions(1, 0, 1, 0)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 0 def", 0, 3, genSameOptions(1, 0, 3, 0)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 1 def", 1, 1, genSameOptions(1, 1, 1, 1)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 1 def", 1, 3, genSameOptions(1, 1, 3, 1)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 5 def", 0, 10, genSameOptions(1, 0, 10, 5)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 10 def", 10, 10, genSameOptions(1, 0, 10, 10)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 0 def", 0, 1, genSameOptions(1, 0, 1, 0)).aiMustChoose(1));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 0 def", 0, 3, genSameOptions(1, 0, 3, 0)).aiMustChoose(3));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 1 def", 1, 1, genSameOptions(1, 1, 1, 1)).aiMustChoose(1));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 1 def", 1, 3, genSameOptions(1, 1, 3, 1)).aiMustChoose(3));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 5 def", 0, 10, genSameOptions(1, 0, 10, 5)).aiMustChoose(10));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "one, 10 def", 10, 10, genSameOptions(1, 0, 10, 10)).aiMustChoose(10));
|
||||
// multiple targets
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 0 def", 0, 5, genSameOptions(3, 0, 3, 0)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 0 def", 0, 5, genSameOptions(3, 0, 3, 0)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 1 def", 1, 5, genSameOptions(3, 1, 3, 1)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 1 def", 1, 5, genSameOptions(3, 1, 3, 1)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 20 def", 0, 60, genSameOptions(3, 0, 60, 20)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 20 def", 60, 60, genSameOptions(3, 0, 60, 20)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 0 def", 0, 5, genSameOptions(3, 0, 3, 0)).aiMustChoose(2, 2, 1));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 1 def", 1, 5, genSameOptions(3, 1, 3, 1)).aiMustChoose(2, 2, 1));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 20 def", 0, 60, genSameOptions(3, 0, 60, 20)).aiMustChoose(20, 20, 20));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "many, 20 def", 60, 60, genSameOptions(3, 0, 60, 20)).aiMustChoose(20, 20, 20));
|
||||
// big lists
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "big list", 0, 100, genSameOptions(20, 0, 100, 0)));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "big list", 0, 100, genSameOptions(20, 0, 100, 0)).aiMustChooseMany(20, 5));
|
||||
runner.registerDialog(new GetMultiAmountTestableDialog(isYou, "big list", 0, 100, genSameOptions(100, 0, 100, 0)).aiMustChooseMany(100, 1));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<List<Integer>> genSameOptions(int amount, int min, int max, int def) {
|
||||
private static List<List<Integer>> genSameOptions(int options, int min, int max, int def) {
|
||||
List<List<Integer>> res = new ArrayList<>();
|
||||
for (int i = 0; i < amount; i++) {
|
||||
for (int i = 0; i < options; i++) {
|
||||
// min, max, default
|
||||
res.add(Arrays.asList(min, max, def));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package mage.utils.testers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Part of testable game dialogs
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class MultiAmountTestableResult extends BaseTestableResult {
|
||||
|
||||
List<Integer> selectedValues;
|
||||
|
||||
boolean aiAssertEnabled = false;
|
||||
List<Integer> aiAssertValues = new ArrayList<>();
|
||||
|
||||
public void onFinish(String resDebugSource, boolean status, List<String> info, List<Integer> selectedValues) {
|
||||
this.onFinish(resDebugSource, status, info);
|
||||
this.selectedValues = selectedValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResAssert() {
|
||||
if (!this.aiAssertEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// not finished
|
||||
if (this.selectedValues == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// wrong selection
|
||||
String selected = this.selectedValues.toString();
|
||||
String need = this.aiAssertValues.toString();
|
||||
|
||||
if (!selected.equals(need)) {
|
||||
return String.format("Wrong selection: need %s, but get %s",
|
||||
need,
|
||||
selected
|
||||
);
|
||||
}
|
||||
|
||||
// all fine
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClear() {
|
||||
super.onClear();
|
||||
this.selectedValues = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package mage.utils.testers;
|
||||
|
||||
import mage.target.Target;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Part of testable game dialogs
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class TargetTestableResult extends BaseTestableResult {
|
||||
|
||||
Target target = null;
|
||||
|
||||
boolean aiAssertEnabled = false;
|
||||
boolean aiAssertResStatus = false;
|
||||
int aiAssertTargetsCount = 0;
|
||||
|
||||
public void onFinish(String resDebugSource, boolean status, List<String> info, Target target) {
|
||||
this.onFinish(resDebugSource, status, info);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResAssert() {
|
||||
if (!this.aiAssertEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// not finished
|
||||
if (this.target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// wrong choose
|
||||
if (this.getResStatus() != this.aiAssertResStatus) {
|
||||
return String.format("Wrong status: need %s, but get %s",
|
||||
this.aiAssertResStatus,
|
||||
this.getResStatus()
|
||||
);
|
||||
}
|
||||
|
||||
// wrong targets
|
||||
if (this.target.getTargets().size() != this.aiAssertTargetsCount) {
|
||||
return String.format("Wrong targets count: need %d, but get %d",
|
||||
this.aiAssertTargetsCount,
|
||||
this.target.getTargets().size()
|
||||
);
|
||||
}
|
||||
|
||||
// all fine
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClear() {
|
||||
super.onClear();
|
||||
this.target = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,6 @@ import mage.abilities.Ability;
|
|||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Part of testable game dialogs
|
||||
* <p>
|
||||
|
|
@ -17,7 +15,11 @@ import java.util.List;
|
|||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
interface TestableDialog {
|
||||
public interface TestableDialog {
|
||||
|
||||
void setRegNumber(Integer regNumber);
|
||||
|
||||
Integer getRegNumber();
|
||||
|
||||
String getGroup();
|
||||
|
||||
|
|
@ -25,7 +27,20 @@ interface TestableDialog {
|
|||
|
||||
String getDescription();
|
||||
|
||||
List<String> showDialog(Player player, Ability source, Game game, Player opponent);
|
||||
TestableResult getResult();
|
||||
|
||||
void showResult(Player player, Game game, String result);
|
||||
/**
|
||||
* Prepare dialog before show, e.g. clear prev results
|
||||
*/
|
||||
void prepare();
|
||||
|
||||
/**
|
||||
* Show game dialog to the user and save result
|
||||
*/
|
||||
void showDialog(Player player, Ability source, Game game, Player opponent);
|
||||
|
||||
/**
|
||||
* Show result dialog to the user
|
||||
*/
|
||||
void showResult(Player player, Game game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import mage.constants.Outcome;
|
|||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
|
@ -55,7 +57,7 @@ import java.util.stream.Collectors;
|
|||
*/
|
||||
public class TestableDialogsRunner {
|
||||
|
||||
private final List<TestableDialog> dialogs = new ArrayList<>();
|
||||
private final Map<Integer, TestableDialog> dialogs = new LinkedHashMap<>();
|
||||
|
||||
static final int LAST_SELECTED_GROUP_ID = 997;
|
||||
static final int LAST_SELECTED_DIALOG_ID = 998;
|
||||
|
|
@ -79,12 +81,14 @@ public class TestableDialogsRunner {
|
|||
}
|
||||
|
||||
void registerDialog(TestableDialog dialog) {
|
||||
this.dialogs.add(dialog);
|
||||
Integer regNumber = this.dialogs.size() + 1;
|
||||
dialog.setRegNumber(regNumber);
|
||||
this.dialogs.put(regNumber, dialog);
|
||||
}
|
||||
|
||||
public void selectAndShowTestableDialog(Player player, Ability source, Game game, Player opponent) {
|
||||
// select group or fast links
|
||||
List<String> groups = this.dialogs.stream()
|
||||
List<String> groups = this.dialogs.values().stream()
|
||||
.map(TestableDialog::getGroup)
|
||||
.distinct()
|
||||
.sorted()
|
||||
|
|
@ -129,8 +133,9 @@ public class TestableDialogsRunner {
|
|||
// all fine, can show it and finish
|
||||
lastSelectedGroup = needGroup;
|
||||
lastSelectedDialog = needDialog;
|
||||
List<String> resInfo = needDialog.showDialog(player, source, game, opponent);
|
||||
needDialog.showResult(player, game, String.join("<br>", resInfo));
|
||||
needDialog.prepare();
|
||||
needDialog.showDialog(player, source, game, opponent);
|
||||
needDialog.showResult(player, game);
|
||||
}
|
||||
|
||||
private Choice prepareSelectGroupChoice(List<String> groups) {
|
||||
|
|
@ -199,5 +204,9 @@ public class TestableDialogsRunner {
|
|||
}
|
||||
return choice;
|
||||
}
|
||||
|
||||
public Collection<TestableDialog> getDialogs() {
|
||||
return this.dialogs.values();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package mage.utils.testers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Part of testable game dialogs, must contain dialogs result
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public interface TestableResult {
|
||||
|
||||
/**
|
||||
* Get source code line with called dialog, use it as starting debug point
|
||||
*/
|
||||
String getResDebugSource();
|
||||
|
||||
/**
|
||||
* Dialog's result
|
||||
*/
|
||||
boolean getResStatus();
|
||||
|
||||
/**
|
||||
* Dialog's detail result
|
||||
*/
|
||||
List<String> getResDetails();
|
||||
|
||||
/**
|
||||
* Save new result after show dialog
|
||||
*/
|
||||
void onFinish(String chooseDebugSource, boolean resStatus, List<String> resDetails);
|
||||
|
||||
boolean isFinished();
|
||||
|
||||
void onClear();
|
||||
|
||||
/**
|
||||
* Assert dialog result
|
||||
* - null - not ready (dev must setup wanted result)
|
||||
* - empty - good
|
||||
* - not empty - fail (return error message)
|
||||
*/
|
||||
String getResAssert();
|
||||
}
|
||||
|
|
@ -25,7 +25,12 @@ public class ChatMessage implements Serializable {
|
|||
}
|
||||
|
||||
public enum MessageType {
|
||||
USER_INFO, STATUS, GAME, TALK, WHISPER_FROM, WHISPER_TO
|
||||
USER_INFO, // system messages
|
||||
STATUS, // system messages
|
||||
GAME, // game logs
|
||||
TALK, // public chat
|
||||
WHISPER_FROM, // private chat income
|
||||
WHISPER_TO // private chat outcome
|
||||
}
|
||||
|
||||
public enum SoundToPlay {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ public class GameView implements Serializable {
|
|||
// TODO: implement and support in admin tools
|
||||
private int totalErrorsCount;
|
||||
private int totalEffectsCount;
|
||||
private int gameCycle;
|
||||
|
||||
public GameView(GameState state, Game game, UUID createdForPlayerId, UUID watcherUserId) {
|
||||
Player createdForPlayer = null;
|
||||
|
|
@ -214,6 +215,7 @@ public class GameView implements Serializable {
|
|||
this.rollbackTurnsAllowed = game.getOptions().rollbackTurnsAllowed;
|
||||
this.totalErrorsCount = game.getTotalErrorsCount();
|
||||
this.totalEffectsCount = game.getTotalEffectsCount();
|
||||
this.gameCycle = game.getState().getApplyEffectsCounter();
|
||||
}
|
||||
|
||||
private void checkPaid(UUID uuid, StackAbility stackAbility) {
|
||||
|
|
@ -358,4 +360,8 @@ public class GameView implements Serializable {
|
|||
public int getTotalEffectsCount() {
|
||||
return this.totalEffectsCount;
|
||||
}
|
||||
|
||||
public int getGameCycle() {
|
||||
return this.gameCycle;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.common.CanBeYourCommanderAbility;
|
||||
import mage.abilities.common.CommanderChooseColorAbility;
|
||||
import mage.abilities.keyword.CompanionAbility;
|
||||
import mage.abilities.keyword.StationLevelAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.decks.Constructed;
|
||||
import mage.cards.decks.Deck;
|
||||
import mage.cards.decks.DeckValidatorErrorType;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.FilterMana;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.ManaUtil;
|
||||
|
|
@ -51,9 +53,19 @@ public abstract class AbstractCommander extends Constructed {
|
|||
protected abstract boolean checkBanned(Map<String, Integer> counts);
|
||||
|
||||
protected boolean checkCommander(Card commander, Set<Card> commanders) {
|
||||
return commander.hasCardTypeForDeckbuilding(CardType.CREATURE) && commander.isLegendary()
|
||||
|| commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance())
|
||||
|| (validators.stream().anyMatch(validator -> validator.specialCheck(commander)) && commanders.size() == 2);
|
||||
if (commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance())) {
|
||||
return true;
|
||||
}
|
||||
if (commander.isLegendary()
|
||||
&& (commander.hasCardTypeForDeckbuilding(CardType.CREATURE)
|
||||
|| commander.hasSubTypeForDeckbuilding(SubType.VEHICLE)
|
||||
|| commander.hasSubTypeForDeckbuilding(SubType.SPACECRAFT)
|
||||
&& CardUtil
|
||||
.castStream(commander.getAbilities(), StationLevelAbility.class)
|
||||
.anyMatch(StationLevelAbility::hasPT))) {
|
||||
return true;
|
||||
}
|
||||
return commanders.size() == 2 && validators.stream().anyMatch(validator -> validator.specialCheck(commander));
|
||||
}
|
||||
|
||||
protected boolean checkPartners(Set<Card> commanders) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package mage.deck;
|
||||
|
||||
import mage.cards.decks.Deck;
|
||||
import mage.cards.decks.DeckValidator;
|
||||
import mage.cards.decks.DeckValidatorErrorType;
|
||||
|
||||
/**
|
||||
* @author resech
|
||||
*/
|
||||
public class FreeformUnlimited extends DeckValidator {
|
||||
|
||||
public FreeformUnlimited() {
|
||||
this("Constructed - Freeform Unlimited", null);
|
||||
}
|
||||
|
||||
public FreeformUnlimited(String name, String shortName) {
|
||||
super(name, shortName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeckMinSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSideboardMinSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Deck deck) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,6 @@ public class Historic extends Constructed {
|
|||
banned.add("Agent of Treachery");
|
||||
banned.add("Brainstorm");
|
||||
banned.add("Channel");
|
||||
banned.add("Counterspell");
|
||||
banned.add("Dark Ritual");
|
||||
banned.add("Demonic Tutor");
|
||||
banned.add("Fires of Invention");
|
||||
|
|
|
|||
|
|
@ -19,10 +19,13 @@ public class Standard extends Constructed {
|
|||
|
||||
setCodes.addAll(makeLegalSets());
|
||||
|
||||
banned.add("The Meathook Massacre");
|
||||
banned.add("Fable of the Mirror-Breaker");
|
||||
banned.add("Reckoner Bankbuster");
|
||||
banned.add("Invoke Despair");
|
||||
banned.add("Abuelo's Awakening");
|
||||
banned.add("Cori-Steel Cutter");
|
||||
banned.add("Heartfire Hero");
|
||||
banned.add("Hopeless Nightmare");
|
||||
banned.add("Monstrous Rage");
|
||||
banned.add("This Town Ain't Big Enough");
|
||||
banned.add("Up the Beanstalk");
|
||||
}
|
||||
|
||||
static List<String> makeLegalSets() {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import mage.game.stack.StackAbility;
|
|||
import mage.game.stack.StackObject;
|
||||
import mage.player.ai.ma.optimizers.TreeOptimizer;
|
||||
import mage.player.ai.ma.optimizers.impl.*;
|
||||
import mage.player.ai.score.GameStateEvaluator2;
|
||||
import mage.player.ai.util.CombatInfo;
|
||||
import mage.player.ai.util.CombatUtil;
|
||||
import mage.players.Player;
|
||||
|
|
@ -113,6 +114,13 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
this.actionCache = player.actionCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change simulation timeout - used for AI stability tests only
|
||||
*/
|
||||
public void setMaxThinkTimeSecs(int maxThinkTimeSecs) {
|
||||
this.maxThinkTimeSecs = maxThinkTimeSecs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComputerPlayer6 copy() {
|
||||
return new ComputerPlayer6(this);
|
||||
|
|
@ -205,10 +213,8 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
logger.trace("Add Action [" + depth + "] " + node.getAbilities().toString() + " a: " + alpha + " b: " + beta);
|
||||
}
|
||||
Game game = node.getGame();
|
||||
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS
|
||||
&& Thread.interrupted()) {
|
||||
Thread.currentThread().interrupt();
|
||||
logger.debug("interrupted");
|
||||
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) {
|
||||
logger.debug("AI game sim interrupted by timeout");
|
||||
return GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
|
||||
}
|
||||
// Condition to stop deeper simulation
|
||||
|
|
@ -399,7 +405,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
if (effect != null
|
||||
&& stackObject.getControllerId().equals(playerId)) {
|
||||
Target target = effect.getTarget();
|
||||
if (!target.isChoiceCompleted(game)) {
|
||||
if (!target.isChoiceCompleted(getId(), (StackAbility) stackObject, game)) {
|
||||
for (UUID targetId : target.possibleTargets(stackObject.getControllerId(), stackObject.getStackAbility(), game)) {
|
||||
Game sim = game.createSimulationForAI();
|
||||
StackAbility newAbility = (StackAbility) stackObject.copy();
|
||||
|
|
@ -430,6 +436,8 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
* @return
|
||||
*/
|
||||
protected Integer addActionsTimed() {
|
||||
// TODO: all actions added and calculated one by one,
|
||||
// multithreading do not supported here
|
||||
// run new game simulation in parallel thread
|
||||
FutureTask<Integer> task = new FutureTask<>(() -> addActions(root, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE));
|
||||
threadPoolSimulations.execute(task);
|
||||
|
|
@ -445,15 +453,23 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
}
|
||||
} catch (TimeoutException | InterruptedException e) {
|
||||
// AI thinks too long
|
||||
logger.info("ai simulating - timed out");
|
||||
// how-to fix: look at stack info - it can contain bad ability with infinite choose dialog
|
||||
logger.warn("");
|
||||
logger.warn("AI player thinks too long (report it to github):");
|
||||
logger.warn(" - player: " + getName());
|
||||
logger.warn(" - battlefield size: " + root.game.getBattlefield().getAllPermanents().size());
|
||||
logger.warn(" - stack: " + root.game.getStack());
|
||||
logger.warn(" - game: " + root.game);
|
||||
printFreezeNode(root);
|
||||
logger.warn("");
|
||||
task.cancel(true);
|
||||
} catch (ExecutionException e) {
|
||||
// game error
|
||||
logger.error("AI simulation catch game error: " + e, e);
|
||||
logger.error("AI player catch game error in simulation - " + getName() + " - " + root.game + ": " + e, e);
|
||||
task.cancel(true);
|
||||
// real games: must catch and log
|
||||
// unit tests: must raise again for fast fail
|
||||
if (this.isTestsMode()) {
|
||||
if (this.isTestMode() && this.isFastFailInTestMode()) {
|
||||
throw new IllegalStateException("One of the simulated games raise the error: " + e, e);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
|
|
@ -465,11 +481,33 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
return 0;
|
||||
}
|
||||
|
||||
private void printFreezeNode(SimulationNode2 root) {
|
||||
// print simple tree - there are possible multiple child nodes, but ignore it - same for abilities
|
||||
List<String> chain = new ArrayList<>();
|
||||
SimulationNode2 node = root;
|
||||
while (node != null) {
|
||||
if (node.abilities != null && !node.abilities.isEmpty()) {
|
||||
Ability ability = node.abilities.get(0);
|
||||
String sourceInfo = CardUtil.getSourceIdName(node.game, ability);
|
||||
chain.add(String.format("%s: %s",
|
||||
(sourceInfo.isEmpty() ? "unknown" : sourceInfo),
|
||||
ability
|
||||
));
|
||||
}
|
||||
node = node.children == null || node.children.isEmpty() ? null : node.children.get(0);
|
||||
}
|
||||
logger.warn("Possible freeze chain:");
|
||||
if (root != null && chain.isEmpty()) {
|
||||
logger.warn(" - unknown use case"); // maybe can't finish any calc, maybe related to target options, I don't know
|
||||
}
|
||||
chain.forEach(s -> {
|
||||
logger.warn(" - " + s);
|
||||
});
|
||||
}
|
||||
|
||||
protected int simulatePriority(SimulationNode2 node, Game game, int depth, int alpha, int beta) {
|
||||
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS
|
||||
&& Thread.interrupted()) {
|
||||
Thread.currentThread().interrupt();
|
||||
logger.info("interrupted");
|
||||
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) {
|
||||
logger.debug("AI game sim interrupted by timeout");
|
||||
return GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
|
||||
}
|
||||
node.setGameValue(game.getState().getValue(true).hashCode());
|
||||
|
|
@ -497,9 +535,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
int bestValSubNodes = Integer.MIN_VALUE;
|
||||
for (Ability action : allActions) {
|
||||
actionNumber++;
|
||||
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS
|
||||
&& Thread.interrupted()) {
|
||||
Thread.currentThread().interrupt();
|
||||
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) {
|
||||
logger.info("Sim Prio [" + depth + "] -- interrupted");
|
||||
break;
|
||||
}
|
||||
|
|
@ -848,10 +884,12 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
if (targets.isEmpty()) {
|
||||
return super.chooseTarget(outcome, cards, target, source, game);
|
||||
}
|
||||
if (!target.isChoiceCompleted(game)) {
|
||||
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
for (UUID targetId : targets) {
|
||||
target.addTarget(targetId, source, game);
|
||||
if (target.isChoiceCompleted(game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
targets.clear();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -866,10 +904,12 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
|||
if (targets.isEmpty()) {
|
||||
return super.choose(outcome, cards, target, source, game);
|
||||
}
|
||||
if (!target.isChoiceCompleted(game)) {
|
||||
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(getId());
|
||||
if (!target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
for (UUID targetId : targets) {
|
||||
target.add(targetId, game);
|
||||
if (target.isChoiceCompleted(game)) {
|
||||
if (target.isChoiceCompleted(abilityControllerId, source, game)) {
|
||||
targets.clear();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package mage.player.ai;
|
|||
import mage.abilities.Ability;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.Game;
|
||||
import mage.player.ai.score.GameStateEvaluator2;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.Date;
|
||||
|
|
@ -111,8 +112,6 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
|
|||
|
||||
protected void calculateActions(Game game) {
|
||||
if (!getNextAction(game)) {
|
||||
//logger.info("--- calculating possible actions for " + this.getName() + " on " + game.toString());
|
||||
Date startTime = new Date();
|
||||
currentScore = GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
|
||||
Game sim = createSimulation(game);
|
||||
SimulationNode2.resetCount();
|
||||
|
|
@ -143,17 +142,9 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
logger.info('[' + game.getPlayer(playerId).getName() + "][pre] Action: skip");
|
||||
// nothing to choose or freeze/infinite game
|
||||
logger.info("AI player can't find next action: " + getName());
|
||||
}
|
||||
Date endTime = new Date();
|
||||
this.setLastThinkTime((endTime.getTime() - startTime.getTime()));
|
||||
|
||||
/*
|
||||
logger.warn("Last think time: " + this.getLastThinkTime()
|
||||
+ "; actions: " + actions.size()
|
||||
+ "; hand: " + this.getHand().size()
|
||||
+ "; permanents: " + game.getBattlefield().getAllPermanents().size());
|
||||
*/
|
||||
} else {
|
||||
logger.debug("Next Action exists!");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import mage.game.permanent.Permanent;
|
|||
import mage.game.turn.CombatDamageStep;
|
||||
import mage.game.turn.EndOfCombatStep;
|
||||
import mage.game.turn.Step;
|
||||
import mage.player.ai.GameStateEvaluator2;
|
||||
import mage.player.ai.score.GameStateEvaluator2;
|
||||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,152 @@
|
|||
package mage.player.ai;
|
||||
|
||||
import mage.MageItem;
|
||||
import mage.MageObject;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.player.ai.score.GameStateEvaluator2;
|
||||
import mage.players.PlayableObjectsList;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* AI related code - compare and sort possible targets due target/effect type
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class PossibleTargetsComparator {
|
||||
|
||||
UUID abilityControllerId;
|
||||
Game game;
|
||||
PlayableObjectsList playableItems = new PlayableObjectsList();
|
||||
|
||||
public PossibleTargetsComparator(UUID abilityControllerId, Game game) {
|
||||
this.abilityControllerId = abilityControllerId;
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
public void findPlayableItems() {
|
||||
this.playableItems = this.game.getPlayer(this.abilityControllerId).getPlayableObjects(this.game, Zone.ALL);
|
||||
}
|
||||
|
||||
private int getScoreFromBattlefield(MageItem item) {
|
||||
if (item instanceof Permanent) {
|
||||
// use battlefield score instead simple life
|
||||
return GameStateEvaluator2.evaluatePermanent((Permanent) item, game, false);
|
||||
} else {
|
||||
return getScoreFromLife(item);
|
||||
}
|
||||
}
|
||||
|
||||
private String getName(MageItem item) {
|
||||
if (item instanceof Player) {
|
||||
return ((Player) item).getName();
|
||||
} else if (item instanceof MageObject) {
|
||||
return ((MageObject) item).getName();
|
||||
} else {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public static int getLifeForDamage(MageItem item, Game game) {
|
||||
int res = 0;
|
||||
if (item instanceof Player) {
|
||||
res = ((Player) item).getLife();
|
||||
} else if (item instanceof Card) {
|
||||
Card card = (Card) item;
|
||||
if (card.isPlaneswalker(game)) {
|
||||
res = card.getCounters(game).getCount(CounterType.LOYALTY);
|
||||
} else if (card.isBattle(game)) {
|
||||
res = card.getCounters(game).getCount(CounterType.DEFENSE);
|
||||
} else {
|
||||
int damage = 0;
|
||||
if (card instanceof Permanent) {
|
||||
damage = ((Permanent) card).getDamage();
|
||||
}
|
||||
res = Math.max(0, card.getToughness().getValue() - damage);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private int getScoreFromLife(MageItem item) {
|
||||
// TODO: replace permanent/card life by battlefield score?
|
||||
int res = getLifeForDamage(item, game);
|
||||
if (res == 0 && item instanceof Card) {
|
||||
res = ((Card) item).getManaValue();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private boolean isMyItem(MageItem item) {
|
||||
return PossibleTargetsSelector.isMyItem(this.abilityControllerId, item);
|
||||
}
|
||||
|
||||
// sort by name-id at the end, so AI will use same choices in all simulations
|
||||
private final Comparator<MageItem> BY_NAME = (o1, o2) -> getName(o2).compareTo(getName(o1));
|
||||
private final Comparator<MageItem> BY_ID = Comparator.comparing(MageItem::getId);
|
||||
|
||||
private final Comparator<MageItem> BY_ME = (o1, o2) -> Boolean.compare(
|
||||
isMyItem(o2),
|
||||
isMyItem(o1)
|
||||
);
|
||||
|
||||
private final Comparator<MageItem> BY_BIGGER_SCORE = (o1, o2) -> Integer.compare(
|
||||
getScoreFromBattlefield(o2),
|
||||
getScoreFromBattlefield(o1)
|
||||
);
|
||||
|
||||
private final Comparator<MageItem> BY_PLAYABLE = (o1, o2) -> Boolean.compare(
|
||||
this.playableItems.containsObject(o2.getId()),
|
||||
this.playableItems.containsObject(o1.getId())
|
||||
);
|
||||
|
||||
private final Comparator<MageItem> BY_LAND = (o1, o2) -> {
|
||||
boolean isLand1 = o1 instanceof MageObject && ((MageObject) o1).isLand(game);
|
||||
boolean isLand2 = o2 instanceof MageObject && ((MageObject) o2).isLand(game);
|
||||
return Boolean.compare(isLand2, isLand1);
|
||||
};
|
||||
|
||||
private final Comparator<MageItem> BY_TYPE_PLAYER = (o1, o2) -> Boolean.compare(
|
||||
o2 instanceof Player,
|
||||
o1 instanceof Player
|
||||
);
|
||||
|
||||
private final Comparator<MageItem> BY_TYPE_PLANESWALKER = (o1, o2) -> {
|
||||
boolean isPlaneswalker1 = o1 instanceof MageObject && ((MageObject) o1).isPlaneswalker(game);
|
||||
boolean isPlaneswalker2 = o2 instanceof MageObject && ((MageObject) o2).isPlaneswalker(game);
|
||||
return Boolean.compare(isPlaneswalker2, isPlaneswalker1);
|
||||
};
|
||||
|
||||
private final Comparator<MageItem> BY_TYPE_BATTLE = (o1, o2) -> {
|
||||
boolean isBattle1 = o1 instanceof MageObject && ((MageObject) o1).isBattle(game);
|
||||
boolean isBattle2 = o2 instanceof MageObject && ((MageObject) o2).isBattle(game);
|
||||
return Boolean.compare(isBattle2, isBattle1);
|
||||
};
|
||||
|
||||
private final Comparator<MageItem> BY_TYPES = BY_TYPE_PLANESWALKER
|
||||
.thenComparing(BY_TYPE_BATTLE)
|
||||
.thenComparing(BY_TYPE_PLAYER);
|
||||
|
||||
/**
|
||||
* Default sorting for good effects - put the biggest items to the top
|
||||
*/
|
||||
public final Comparator<MageItem> ANY_MOST_VALUABLE_FIRST = BY_TYPES
|
||||
.thenComparing(BY_BIGGER_SCORE)
|
||||
.thenComparing(BY_NAME)
|
||||
.thenComparing(BY_ID);
|
||||
public final Comparator<MageItem> ANY_MOST_VALUABLE_LAST = ANY_MOST_VALUABLE_FIRST.reversed();
|
||||
|
||||
/**
|
||||
* Sorting for discard effects - put the biggest unplayable at the top, lands at the end anyway
|
||||
*/
|
||||
public final Comparator<MageItem> ANY_UNPLAYABLE_AND_USELESS = BY_LAND.reversed()
|
||||
.thenComparing(BY_PLAYABLE.reversed())
|
||||
.thenComparing(ANY_MOST_VALUABLE_FIRST);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
package mage.player.ai;
|
||||
|
||||
import mage.MageItem;
|
||||
import mage.abilities.Ability;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.ControllableOrOwnerable;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.target.common.TargetCardInGraveyardBattlefieldOrStack;
|
||||
import mage.target.common.TargetDiscard;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* AI related code - find possible targets and sort it due priority
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class PossibleTargetsSelector {
|
||||
|
||||
Outcome outcome;
|
||||
Target target;
|
||||
UUID abilityControllerId;
|
||||
Ability source;
|
||||
Game game;
|
||||
|
||||
PossibleTargetsComparator comparators;
|
||||
|
||||
// possible targets lists
|
||||
List<MageItem> me = new ArrayList<>();
|
||||
List<MageItem> opponents = new ArrayList<>();
|
||||
List<MageItem> any = new ArrayList<>(); // for outcomes with any target like copy
|
||||
|
||||
public PossibleTargetsSelector(Outcome outcome, Target target, UUID abilityControllerId, Ability source, Game game) {
|
||||
this.outcome = outcome;
|
||||
this.target = target;
|
||||
this.abilityControllerId = abilityControllerId;
|
||||
this.source = source;
|
||||
this.game = game;
|
||||
this.comparators = new PossibleTargetsComparator(abilityControllerId, game);
|
||||
}
|
||||
|
||||
public void findNewTargets(Set<UUID> fromTargetsList) {
|
||||
// collect new valid targets
|
||||
List<MageItem> found = target.possibleTargets(abilityControllerId, source, game, fromTargetsList).stream()
|
||||
.filter(id -> !target.contains(id))
|
||||
.filter(id -> target.canTarget(abilityControllerId, id, source, game))
|
||||
.map(id -> {
|
||||
Player player = game.getPlayer(id);
|
||||
if (player != null) {
|
||||
return player;
|
||||
} else {
|
||||
return game.getObject(id);
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// split targets between me and opponents
|
||||
found.forEach(item -> {
|
||||
if (isMyItem(abilityControllerId, item)) {
|
||||
this.me.add(item);
|
||||
} else {
|
||||
this.opponents.add(item);
|
||||
}
|
||||
this.any.add(item);
|
||||
});
|
||||
|
||||
if (target instanceof TargetDiscard) {
|
||||
// sort due unplayable
|
||||
sortByUnplayableAndUseless();
|
||||
} else {
|
||||
// sort due good/bad outcome
|
||||
sortByMostValuableTargets();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting for any good/bad effects
|
||||
*/
|
||||
private void sortByMostValuableTargets() {
|
||||
if (isGoodEffect()) {
|
||||
// for good effect must choose the biggest objects
|
||||
this.me.sort(comparators.ANY_MOST_VALUABLE_FIRST);
|
||||
this.opponents.sort(comparators.ANY_MOST_VALUABLE_LAST);
|
||||
this.any.sort(comparators.ANY_MOST_VALUABLE_FIRST);
|
||||
} else {
|
||||
// for bad effect must choose the smallest objects
|
||||
this.me.sort(comparators.ANY_MOST_VALUABLE_LAST);
|
||||
this.opponents.sort(comparators.ANY_MOST_VALUABLE_FIRST);
|
||||
this.any.sort(comparators.ANY_MOST_VALUABLE_LAST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting for discard
|
||||
*/
|
||||
private void sortByUnplayableAndUseless() {
|
||||
// used
|
||||
// no good or bad effect - you must choose
|
||||
comparators.findPlayableItems();
|
||||
this.me.sort(comparators.ANY_UNPLAYABLE_AND_USELESS);
|
||||
this.opponents.sort(comparators.ANY_UNPLAYABLE_AND_USELESS);
|
||||
this.any.sort(comparators.ANY_UNPLAYABLE_AND_USELESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Priority targets. Try to use as much as possible.
|
||||
*/
|
||||
public List<MageItem> getGoodTargets() {
|
||||
if (isAnyEffect()) {
|
||||
return this.any;
|
||||
}
|
||||
|
||||
if (isGoodEffect()) {
|
||||
return this.me;
|
||||
} else {
|
||||
return this.opponents;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional targets. Try to ignore bad targets (e.g. opponent's creatures for your good effect).
|
||||
*/
|
||||
public List<MageItem> getBadTargets() {
|
||||
if (isAnyEffect()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (isGoodEffect()) {
|
||||
return this.opponents;
|
||||
} else {
|
||||
return this.me;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isMyItem(UUID abilityControllerId, MageItem item) {
|
||||
if (item instanceof Player) {
|
||||
return item.getId().equals(abilityControllerId);
|
||||
} else if (item instanceof ControllableOrOwnerable) {
|
||||
return ((ControllableOrOwnerable) item).getControllerOrOwnerId().equals(abilityControllerId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isAnyEffect() {
|
||||
boolean isAnyEffect = outcome.anyTargetHasSameValue();
|
||||
|
||||
if (hasGoodExile()) {
|
||||
isAnyEffect = true;
|
||||
}
|
||||
|
||||
return isAnyEffect;
|
||||
}
|
||||
|
||||
private boolean isGoodEffect() {
|
||||
boolean isGoodEffect = outcome.isGood();
|
||||
|
||||
if (hasGoodExile()) {
|
||||
isGoodEffect = true;
|
||||
}
|
||||
|
||||
return isGoodEffect;
|
||||
}
|
||||
|
||||
private boolean hasGoodExile() {
|
||||
// exile workaround: exile is bad, but exile from library or graveyard in most cases is good
|
||||
// (more exiled -- more good things you get, e.g. delve's pay or search cards with same name)
|
||||
if (outcome == Outcome.Exile) {
|
||||
if (Zone.GRAVEYARD.match(target.getZone())
|
||||
|| Zone.LIBRARY.match(target.getZone())) {
|
||||
// TargetCardInGraveyardBattlefieldOrStack - used for additional payment like Craft, so do not allow big cards for it
|
||||
if (!(target instanceof TargetCardInGraveyardBattlefieldOrStack)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasAnyTargets() {
|
||||
return !this.any.isEmpty();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package mage.player.ai.ma;
|
||||
package mage.player.ai.score;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
package mage.player.ai;
|
||||
package mage.player.ai.score;
|
||||
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.player.ai.ma.ArtificialScoringSystem;
|
||||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package mage.player.ai.ma;
|
||||
package mage.player.ai.score;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.keyword.*;
|
||||
|
|
@ -7,6 +7,7 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* TODO: outdated, replace by edh or commander brackets ability score
|
||||
* @author nantuko
|
||||
*/
|
||||
public final class MagicAbility {
|
||||
|
|
@ -18,10 +19,10 @@ public final class MagicAbility {
|
|||
put(DoubleStrikeAbility.getInstance().getRule(), 100);
|
||||
put(new ExaltedAbility().getRule(), 10);
|
||||
put(FirstStrikeAbility.getInstance().getRule(), 50);
|
||||
put(FlashAbility.getInstance().getRule(), 0);
|
||||
put(FlashAbility.getInstance().getRule(), 20);
|
||||
put(FlyingAbility.getInstance().getRule(), 50);
|
||||
put(new ForestwalkAbility().getRule(), 10);
|
||||
put(HasteAbility.getInstance().getRule(), 0);
|
||||
put(HasteAbility.getInstance().getRule(), 20);
|
||||
put(IndestructibleAbility.getInstance().getRule(), 150);
|
||||
put(InfectAbility.getInstance().getRule(), 60);
|
||||
put(IntimidateAbility.getInstance().getRule(), 50);
|
||||
|
|
@ -46,7 +47,7 @@ public final class MagicAbility {
|
|||
if (!scores.containsKey(ability.getRule())) {
|
||||
//System.err.println("Couldn't find ability score: " + ability.getClass().getSimpleName() + " - " + ability.toString());
|
||||
//TODO: add handling protection from ..., levelup, kicker, etc. abilities
|
||||
return 0;
|
||||
return 2; // more abilities - more score in any use cases
|
||||
}
|
||||
return scores.get(ability.getRule());
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
|
||||
|
||||
package mage.player.ai.simulators;
|
||||
|
||||
import mage.abilities.ActivatedAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.player.ai.ComputerPlayer;
|
||||
import mage.player.ai.PermanentEvaluator;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class ActionSimulator {
|
||||
|
||||
private ComputerPlayer player;
|
||||
private List<Card> playableInstants = new ArrayList<>();
|
||||
private List<ActivatedAbility> playableAbilities = new ArrayList<>();
|
||||
|
||||
private Game game;
|
||||
|
||||
public ActionSimulator(ComputerPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public void simulate(Game game) {
|
||||
|
||||
}
|
||||
|
||||
public int evaluateState() {
|
||||
// must find all leaved opponents
|
||||
Player opponent = game.getPlayer(game.getOpponents(player.getId(), false).stream().findFirst().orElse(null));
|
||||
if (opponent == null) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (game.checkIfGameIsOver()) {
|
||||
if (player.hasLost() || opponent.hasWon()) {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
if (opponent.hasLost() || player.hasWon()) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
int value = player.getLife();
|
||||
value -= opponent.getLife();
|
||||
PermanentEvaluator evaluator = new PermanentEvaluator();
|
||||
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(player.getId())) {
|
||||
value += evaluator.evaluate(permanent, game);
|
||||
}
|
||||
for (Permanent permanent: game.getBattlefield().getAllActivePermanents(player.getId())) {
|
||||
value -= evaluator.evaluate(permanent, game);
|
||||
}
|
||||
value += player.getHand().size();
|
||||
value -= opponent.getHand().size();
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
package mage.player.ai.simulators;
|
||||
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class CombatGroupSimulator implements Serializable {
|
||||
public List<CreatureSimulator> attackers = new ArrayList<>();
|
||||
public List<CreatureSimulator> blockers = new ArrayList<>();
|
||||
public UUID defenderId;
|
||||
public boolean defenderIsPlaneswalker;
|
||||
public int unblockedDamage;
|
||||
private CreatureSimulator attacker;
|
||||
|
||||
public CombatGroupSimulator(UUID defenderId, List<UUID> attackers, List<UUID> blockers, Game game) {
|
||||
this.defenderId = defenderId;
|
||||
for (UUID attackerId: attackers) {
|
||||
Permanent permanent = game.getPermanent(attackerId);
|
||||
this.attackers.add(new CreatureSimulator(permanent));
|
||||
}
|
||||
for (UUID blockerId: blockers) {
|
||||
Permanent permanent = game.getPermanent(blockerId);
|
||||
this.blockers.add(new CreatureSimulator(permanent));
|
||||
}
|
||||
//NOTE: assumes no banding
|
||||
attacker = this.attackers.get(0);
|
||||
}
|
||||
|
||||
private boolean hasFirstOrDoubleStrike() {
|
||||
for (CreatureSimulator creature: attackers) {
|
||||
if (creature.hasDoubleStrike || creature.hasFirstStrike)
|
||||
return true;
|
||||
}
|
||||
for (CreatureSimulator creature: blockers) {
|
||||
if (creature.hasDoubleStrike || creature.hasFirstStrike)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean canBlock(Permanent blocker, Game game) {
|
||||
return blocker.canBlock(attacker.id, game);
|
||||
}
|
||||
|
||||
public void simulateCombat(Game game) {
|
||||
unblockedDamage = 0;
|
||||
|
||||
if (hasFirstOrDoubleStrike())
|
||||
assignDamage(true, game);
|
||||
assignDamage(false, game);
|
||||
}
|
||||
|
||||
private void assignDamage(boolean first, Game game) {
|
||||
if (blockers.isEmpty()) {
|
||||
if (canDamage(attacker, first))
|
||||
unblockedDamage += attacker.power;
|
||||
}
|
||||
else if (blockers.size() == 1) {
|
||||
CreatureSimulator blocker = blockers.get(0);
|
||||
if (canDamage(attacker, first)) {
|
||||
if (attacker.hasTrample) {
|
||||
int lethalDamage = blocker.getLethalDamage(game);
|
||||
if (attacker.power > lethalDamage) {
|
||||
blocker.damage += lethalDamage;
|
||||
unblockedDamage += attacker.power - lethalDamage;
|
||||
}
|
||||
else {
|
||||
blocker.damage += attacker.power;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canDamage(blocker, first)) {
|
||||
attacker.damage += blocker.power;
|
||||
}
|
||||
}
|
||||
else {
|
||||
int damage = attacker.power;
|
||||
for (CreatureSimulator blocker: blockers) {
|
||||
if (damage > 0 && canDamage(attacker, first)) {
|
||||
int lethalDamage = blocker.getLethalDamage(game);
|
||||
if (damage > lethalDamage) {
|
||||
blocker.damage += lethalDamage;
|
||||
damage -= lethalDamage;
|
||||
}
|
||||
else {
|
||||
blocker.damage += damage;
|
||||
damage = 0;
|
||||
}
|
||||
}
|
||||
if (canDamage(blocker, first)) {
|
||||
attacker.damage += blocker.power;
|
||||
}
|
||||
}
|
||||
if (damage > 0) {
|
||||
if (attacker.hasTrample) {
|
||||
unblockedDamage += damage;
|
||||
}
|
||||
else {
|
||||
blockers.get(0).damage += damage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canDamage(CreatureSimulator creature, boolean first) {
|
||||
if (first && (creature.hasFirstStrike || creature.hasDoubleStrike))
|
||||
return true;
|
||||
if (!first && (!creature.hasFirstStrike || creature.hasDoubleStrike))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns 3 attacker survives blockers destroyed
|
||||
* returns 2 both destroyed
|
||||
* returns 1 both survive
|
||||
* returns 0 attacker destroyed blockers survive
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public int evaluateCombat() {
|
||||
int survivingBlockers = 0;
|
||||
for (CreatureSimulator blocker: blockers) {
|
||||
if (blocker.damage < blocker.toughness)
|
||||
survivingBlockers++;
|
||||
}
|
||||
if (attacker.isDead()) {
|
||||
if (survivingBlockers > 0) {
|
||||
return 0;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
else {
|
||||
if (survivingBlockers > 0) {
|
||||
return 1;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
package mage.player.ai.simulators;
|
||||
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.combat.CombatGroup;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class CombatSimulator implements Serializable {
|
||||
|
||||
public List<CombatGroupSimulator> groups = new ArrayList<>();
|
||||
public List<UUID> defenders = new ArrayList<>();
|
||||
public Map<UUID, Integer> playersLife = new HashMap<>();
|
||||
public Map<UUID, Integer> planeswalkerLoyalty = new HashMap<>();
|
||||
public UUID attackerId;
|
||||
public int rating = 0;
|
||||
|
||||
public static CombatSimulator load(Game game) {
|
||||
CombatSimulator simCombat = new CombatSimulator();
|
||||
for (CombatGroup group: game.getCombat().getGroups()) {
|
||||
simCombat.groups.add(new CombatGroupSimulator(group.getDefenderId(), group.getAttackers(), group.getBlockers(), game));
|
||||
}
|
||||
for (UUID defenderId: game.getCombat().getDefenders()) {
|
||||
simCombat.defenders.add(defenderId);
|
||||
Player player = game.getPlayer(defenderId);
|
||||
if (player != null) {
|
||||
simCombat.playersLife.put(defenderId, player.getLife());
|
||||
}
|
||||
else {
|
||||
Permanent permanent = game.getPermanent(defenderId);
|
||||
simCombat.planeswalkerLoyalty.put(defenderId, permanent.getCounters(game).getCount(CounterType.LOYALTY));
|
||||
}
|
||||
}
|
||||
return simCombat;
|
||||
}
|
||||
|
||||
public CombatSimulator() {}
|
||||
|
||||
public void clear() {
|
||||
groups.clear();
|
||||
defenders.clear();
|
||||
attackerId = null;
|
||||
}
|
||||
|
||||
public void simulate(Game game) {
|
||||
for (CombatGroupSimulator group: groups) {
|
||||
group.simulateCombat(game);
|
||||
}
|
||||
}
|
||||
|
||||
public int evaluate() {
|
||||
Map<UUID, Integer> damage = new HashMap<>();
|
||||
int result = 0;
|
||||
for (CombatGroupSimulator group: groups) {
|
||||
if (!damage.containsKey(group.defenderId)) {
|
||||
damage.put(group.defenderId, group.unblockedDamage);
|
||||
}
|
||||
else {
|
||||
damage.put(group.defenderId, damage.get(group.defenderId) + group.unblockedDamage);
|
||||
}
|
||||
}
|
||||
//check for lethal damage to player
|
||||
for (Entry<UUID, Integer> entry: playersLife.entrySet()) {
|
||||
if (damage.containsKey(entry.getKey()) && entry.getValue() <= damage.get(entry.getKey())) {
|
||||
//TODO: check for protection
|
||||
//NOTE: not applicable for mulitplayer games
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
for (CombatGroupSimulator group: groups) {
|
||||
result += group.evaluateCombat();
|
||||
}
|
||||
|
||||
rating = result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package mage.player.ai.simulators;
|
||||
|
||||
import mage.abilities.keyword.DoubleStrikeAbility;
|
||||
import mage.abilities.keyword.FirstStrikeAbility;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class CreatureSimulator implements Serializable {
|
||||
public UUID id;
|
||||
public int damage;
|
||||
public int power;
|
||||
public int toughness;
|
||||
public boolean hasFirstStrike;
|
||||
public boolean hasDoubleStrike;
|
||||
public boolean hasTrample;
|
||||
public Permanent permanent;
|
||||
|
||||
public CreatureSimulator(Permanent permanent) {
|
||||
this.id = permanent.getId();
|
||||
this.damage = permanent.getDamage();
|
||||
this.power = permanent.getPower().getValue();
|
||||
this.toughness = permanent.getToughness().getValue();
|
||||
this.hasDoubleStrike = permanent.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId());
|
||||
this.hasFirstStrike = permanent.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId());
|
||||
this.hasTrample = permanent.getAbilities().containsKey(TrampleAbility.getInstance().getId());
|
||||
this.permanent = permanent;
|
||||
}
|
||||
|
||||
public boolean isDead() {
|
||||
return damage >= toughness;
|
||||
}
|
||||
|
||||
public int getLethalDamage(Game game) {
|
||||
List<FilterCreaturePermanent> usePowerInsteadOfToughnessForDamageLethalityFilters = game.getState().getActivePowerInsteadOfToughnessForDamageLethalityFilters();
|
||||
/*
|
||||
* for handling Zilortha, Strength Incarnate:
|
||||
* 2020-04-17
|
||||
* Any time the game is checking whether damage is lethal or if a creature should be destroyed for having lethal damage marked on it, use the power of your creatures rather than their toughness to check the damage against. This includes being assigned trample damage, damage from Flame Spill, and so on.
|
||||
*/
|
||||
boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream()
|
||||
.anyMatch(filter -> filter.match(permanent, game));
|
||||
int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ?
|
||||
// Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it.
|
||||
Math.max(power, 1) : toughness;
|
||||
return Math.max(lethalDamageThreshold - damage, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ public class ComputerPlayerMCTS extends ComputerPlayer {
|
|||
} catch (ExecutionException e) {
|
||||
// real games: must catch and log
|
||||
// unit tests: must raise again for fast fail
|
||||
if (this.isTestsMode()) {
|
||||
if (this.isTestMode() && this.isFastFailInTestMode()) {
|
||||
throw new IllegalStateException("One of the simulated games raise the error: " + e, e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,6 +288,16 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
|
|||
|
||||
@Override
|
||||
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
||||
|
||||
// nothing to choose
|
||||
target.prepareAmount(source, game);
|
||||
if (target.getAmountRemaining() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (target.getMaxNumberOfTargets() == 0 && target.getMinNumberOfTargets() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<UUID> possibleTargets = target.possibleTargets(playerId, source, game);
|
||||
if (possibleTargets.isEmpty()) {
|
||||
return !target.isRequired(source);
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
return;
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Setting game priority for " + getId() + " [" + DebugUtil.getMethodNameWithSource(1) + ']');
|
||||
logger.debug("Setting game priority for " + getId() + " [" + DebugUtil.getMethodNameWithSource(1, "method") + ']');
|
||||
}
|
||||
game.getState().setPriorityPlayerId(getId());
|
||||
}
|
||||
|
|
@ -328,7 +328,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
while (loop) {
|
||||
// start waiting for next answer
|
||||
response.clear();
|
||||
response.setActiveAction(game, DebugUtil.getMethodNameWithSource(1));
|
||||
response.setActiveAction(game, DebugUtil.getMethodNameWithSource(1, "method"));
|
||||
game.resumeTimer(getTurnControlledBy());
|
||||
responseOpenedForAnswer = true;
|
||||
|
||||
|
|
@ -690,12 +690,8 @@ public class HumanPlayer extends PlayerImpl {
|
|||
return false;
|
||||
}
|
||||
|
||||
// choose one or multiple permanents
|
||||
UUID abilityControllerId = playerId;
|
||||
if (target.getTargetController() != null
|
||||
&& target.getAbilityController() != null) {
|
||||
abilityControllerId = target.getAbilityController();
|
||||
}
|
||||
// choose one or multiple targets
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||
if (options == null) {
|
||||
options = new HashMap<>();
|
||||
}
|
||||
|
|
@ -782,11 +778,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
}
|
||||
|
||||
// choose one or multiple targets
|
||||
UUID abilityControllerId = playerId;
|
||||
if (target.getAbilityController() != null) {
|
||||
abilityControllerId = target.getAbilityController();
|
||||
}
|
||||
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||
Map<String, Serializable> options = new HashMap<>();
|
||||
|
||||
while (canRespond()) {
|
||||
|
|
@ -869,13 +861,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
return false;
|
||||
}
|
||||
|
||||
UUID abilityControllerId;
|
||||
if (target.getTargetController() != null
|
||||
&& target.getAbilityController() != null) {
|
||||
abilityControllerId = target.getAbilityController();
|
||||
} else {
|
||||
abilityControllerId = playerId;
|
||||
}
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||
|
||||
while (canRespond()) {
|
||||
|
||||
|
|
@ -966,13 +952,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
return false;
|
||||
}
|
||||
|
||||
UUID abilityControllerId;
|
||||
if (target.getTargetController() != null
|
||||
&& target.getAbilityController() != null) {
|
||||
abilityControllerId = target.getAbilityController();
|
||||
} else {
|
||||
abilityControllerId = playerId;
|
||||
}
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||
|
||||
while (canRespond()) {
|
||||
boolean required = target.isRequiredExplicitlySet() ? target.isRequired() : target.isRequired(source);
|
||||
|
|
@ -1042,14 +1022,20 @@ public class HumanPlayer extends PlayerImpl {
|
|||
return false;
|
||||
}
|
||||
|
||||
// nothing to choose
|
||||
target.prepareAmount(source, game);
|
||||
if (target.getAmountRemaining() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (target.getMaxNumberOfTargets() == 0 && target.getMinNumberOfTargets() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (source == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UUID abilityControllerId = playerId;
|
||||
if (target.getAbilityController() != null) {
|
||||
abilityControllerId = target.getAbilityController();
|
||||
}
|
||||
UUID abilityControllerId = target.getAffectedAbilityControllerId(this.getId());
|
||||
|
||||
int amountTotal = target.getAmountTotal(game, source);
|
||||
if (amountTotal == 0) {
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@
|
|||
<deckType name="Constructed - Old School 93/94 - EC Rules" jar="mage-deck-constructed.jar" className="mage.deck.OldSchool9394EC"/>
|
||||
<deckType name="Constructed - Premodern" jar="mage-deck-constructed.jar" className="mage.deck.Premodern"/>
|
||||
<deckType name="Constructed - Freeform" jar="mage-deck-constructed.jar" className="mage.deck.Freeform"/>
|
||||
<deckType name="Constructed - Freeform Unlimited" jar="mage-deck-constructed.jar" className="mage.deck.FreeformUnlimited"/>
|
||||
<deckType name="Variant Magic - Commander" jar="mage-deck-constructed.jar" className="mage.deck.Commander"/>
|
||||
<deckType name="Variant Magic - Duel Commander" jar="mage-deck-constructed.jar" className="mage.deck.DuelCommander"/>
|
||||
<deckType name="Variant Magic - MTGO 1v1 Commander" jar="mage-deck-constructed.jar" className="mage.deck.MTGO1v1Commander"/>
|
||||
|
|
|
|||
|
|
@ -192,11 +192,6 @@
|
|||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.11</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
|
|
@ -218,7 +213,7 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-core</artifactId>
|
||||
<version>1.8.0</version>
|
||||
<version>1.13.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@
|
|||
<deckType name="Constructed - Old School 93/94 - EC Rules" jar="mage-deck-constructed-${project.version}.jar" className="mage.deck.OldSchool9394EC"/>
|
||||
<deckType name="Constructed - Premodern" jar="mage-deck-constructed-${project.version}.jar" className="mage.deck.Premodern"/>
|
||||
<deckType name="Constructed - Freeform" jar="mage-deck-constructed-${project.version}.jar" className="mage.deck.Freeform"/>
|
||||
<deckType name="Constructed - Freeform Unlimited" jar="mage-deck-constructed-${project.version}.jar" className="mage.deck.FreeformUnlimited"/>
|
||||
<deckType name="Variant Magic - Commander" jar="mage-deck-constructed-${project.version}.jar" className="mage.deck.Commander"/>
|
||||
<deckType name="Variant Magic - Duel Commander" jar="mage-deck-constructed-${project.version}.jar" className="mage.deck.DuelCommander"/>
|
||||
<deckType name="Variant Magic - MTGO 1v1 Commander" jar="mage-deck-constructed-${project.version}.jar" className="mage.deck.MTGO1v1Commander"/>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import mage.cards.repository.CardInfo;
|
|||
import mage.cards.repository.CardRepository;
|
||||
import mage.constants.Constants;
|
||||
import mage.game.Game;
|
||||
import mage.game.Table;
|
||||
import mage.game.tournament.Tournament;
|
||||
import mage.server.game.GameController;
|
||||
import mage.server.managers.ChatManager;
|
||||
import mage.server.managers.ManagerFactory;
|
||||
|
|
@ -40,11 +42,39 @@ public class ChatManagerImpl implements ChatManager {
|
|||
this.managerFactory = managerFactory;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public UUID createChatSession(String info) {
|
||||
public UUID createRoomChatSession(UUID roomId) {
|
||||
return createChatSession("Room " + roomId)
|
||||
.withRoom(roomId)
|
||||
.getChatId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID createTourneyChatSession(Tournament tournament) {
|
||||
return createChatSession("Tourney " + tournament.getId())
|
||||
.withTourney(tournament)
|
||||
.getChatId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID createTableChatSession(Table table) {
|
||||
return createChatSession("Table " + table.getId())
|
||||
.withTable(table)
|
||||
.getChatId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID createGameChatSession(Game game) {
|
||||
return createChatSession("Game " + game.getId())
|
||||
.withGame(game)
|
||||
.getChatId();
|
||||
}
|
||||
|
||||
private ChatSession createChatSession(String info) {
|
||||
ChatSession chatSession = new ChatSession(managerFactory, info);
|
||||
chatSessions.put(chatSession.getChatId(), chatSession);
|
||||
return chatSession.getChatId();
|
||||
return chatSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -94,7 +124,7 @@ public class ChatManagerImpl implements ChatManager {
|
|||
ChatSession chatSession = chatSessions.get(chatId);
|
||||
Optional<User> user = managerFactory.userManager().getUserByName(userName);
|
||||
if (chatSession != null) {
|
||||
// special commads
|
||||
// special commands
|
||||
if (message.startsWith("\\") || message.startsWith("/")) {
|
||||
if (user.isPresent()) {
|
||||
if (!performUserCommand(user.get(), message, chatId, false)) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package mage.server;
|
||||
|
||||
import mage.collectors.DataCollectorServices;
|
||||
import mage.game.Game;
|
||||
import mage.game.Table;
|
||||
import mage.game.tournament.Tournament;
|
||||
import mage.interfaces.callback.ClientCallback;
|
||||
import mage.interfaces.callback.ClientCallbackMethod;
|
||||
import mage.server.managers.ManagerFactory;
|
||||
|
|
@ -27,6 +30,13 @@ public class ChatSession {
|
|||
private final ManagerFactory managerFactory;
|
||||
private final ReadWriteLock lock = new ReentrantReadWriteLock(); // TODO: no needs due ConcurrentHashMap usage?
|
||||
|
||||
// only 1 field must be filled per chat type
|
||||
// TODO: rework chat sessions to share logic (one server room/lobby + one table/subtable + one games/match)
|
||||
private UUID roomId = null;
|
||||
private UUID tourneyId = null;
|
||||
private UUID tableId = null;
|
||||
private UUID gameId = null;
|
||||
|
||||
private final ConcurrentMap<UUID, String> users = new ConcurrentHashMap<>(); // active users
|
||||
private final Set<UUID> usersHistory = new HashSet<>(); // all users that was here (need for system messages like connection problem)
|
||||
private final UUID chatId;
|
||||
|
|
@ -40,6 +50,26 @@ public class ChatSession {
|
|||
this.info = info;
|
||||
}
|
||||
|
||||
public ChatSession withRoom(UUID roomId) {
|
||||
this.roomId = roomId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChatSession withTourney(Tournament tournament) {
|
||||
this.tourneyId = tournament.getId();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChatSession withTable(Table table) {
|
||||
this.tableId = table.getId();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ChatSession withGame(Game game) {
|
||||
this.gameId = game.getId();
|
||||
return this;
|
||||
}
|
||||
|
||||
public void join(UUID userId) {
|
||||
managerFactory.userManager().getUser(userId).ifPresent(user -> {
|
||||
if (!users.containsKey(userId)) {
|
||||
|
|
@ -112,9 +142,36 @@ public class ChatSession {
|
|||
// TODO: is it freeze on someone's connection fail/freeze with play multiple games/chats/lobby?
|
||||
// TODO: send messages in another thread?!
|
||||
if (!message.isEmpty()) {
|
||||
ChatMessage chatMessage = new ChatMessage(userName, message, (withTime ? new Date() : null), game, color, messageType, soundToPlay);
|
||||
|
||||
switch (messageType) {
|
||||
case USER_INFO:
|
||||
case STATUS:
|
||||
case TALK:
|
||||
if (this.roomId != null) {
|
||||
DataCollectorServices.getInstance().onChatRoom(this.roomId, userName, message);
|
||||
} else if (this.tourneyId != null) {
|
||||
DataCollectorServices.getInstance().onChatTourney(this.tourneyId, userName, message);
|
||||
} else if (this.tableId != null) {
|
||||
DataCollectorServices.getInstance().onChatTable(this.tableId, userName, message);
|
||||
} else if (this.gameId != null) {
|
||||
DataCollectorServices.getInstance().onChatGame(this.gameId, userName, message);
|
||||
}
|
||||
break;
|
||||
case GAME:
|
||||
// game logs processing in other place
|
||||
break;
|
||||
case WHISPER_FROM:
|
||||
case WHISPER_TO:
|
||||
// ignore private messages
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported message type " + messageType);
|
||||
}
|
||||
|
||||
// TODO: wtf, remove all that locks/tries and make it simpler
|
||||
Set<UUID> clientsToRemove = new HashSet<>();
|
||||
ClientCallback clientCallback = new ClientCallback(ClientCallbackMethod.CHATMESSAGE, chatId,
|
||||
new ChatMessage(userName, message, (withTime ? new Date() : null), game, color, messageType, soundToPlay));
|
||||
ClientCallback clientCallback = new ClientCallback(ClientCallbackMethod.CHATMESSAGE, chatId, chatMessage);
|
||||
List<UUID> chatUserIds = new ArrayList<>();
|
||||
final Lock r = lock.readLock();
|
||||
r.lock();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import mage.cards.decks.DeckCardLists;
|
|||
import mage.cards.decks.DeckValidatorFactory;
|
||||
import mage.cards.repository.CardRepository;
|
||||
import mage.cards.repository.ExpansionRepository;
|
||||
import mage.collectors.DataCollectorServices;
|
||||
import mage.constants.Constants;
|
||||
import mage.constants.ManaType;
|
||||
import mage.constants.PlayerAction;
|
||||
|
|
@ -29,6 +30,7 @@ import mage.server.managers.ManagerFactory;
|
|||
import mage.server.services.impl.FeedbackServiceImpl;
|
||||
import mage.server.tournament.TournamentFactory;
|
||||
import mage.server.util.ServerMessagesUtil;
|
||||
import mage.util.DebugUtil;
|
||||
import mage.utils.*;
|
||||
import mage.view.*;
|
||||
import mage.view.ChatMessage.MessageColor;
|
||||
|
|
@ -68,6 +70,13 @@ public class MageServerImpl implements MageServer {
|
|||
this.detailsMode = detailsMode;
|
||||
this.callExecutor = managerFactory.threadExecutor().getCallExecutor();
|
||||
ServerMessagesUtil.instance.getMessages();
|
||||
|
||||
// additional logs
|
||||
DataCollectorServices.init(
|
||||
DebugUtil.SERVER_DATA_COLLECTORS_ENABLE_PRINT_GAME_LOGS,
|
||||
DebugUtil.SERVER_DATA_COLLECTORS_ENABLE_SAVE_GAME_HISTORY
|
||||
);
|
||||
DataCollectorServices.getInstance().onServerStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public abstract class RoomImpl implements Room {
|
|||
|
||||
public RoomImpl(ChatManager chatManager) {
|
||||
roomId = UUID.randomUUID();
|
||||
chatId = chatManager.createChatSession("Room " + roomId);
|
||||
chatId = chatManager.createRoomChatSession(roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import mage.util.ThreadUtils;
|
|||
import org.apache.log4j.Logger;
|
||||
import org.jboss.remoting.callback.InvokerCallbackHandler;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
|
@ -29,7 +28,7 @@ public class SessionManagerImpl implements SessionManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Optional<Session> getSession(@Nonnull String sessionId) {
|
||||
public Optional<Session> getSession(String sessionId) {
|
||||
return Optional.ofNullable(sessions.getOrDefault(sessionId, null));
|
||||
}
|
||||
|
||||
|
|
@ -180,12 +179,12 @@ public class SessionManagerImpl implements SessionManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidSession(@Nonnull String sessionId) {
|
||||
public boolean isValidSession(String sessionId) {
|
||||
return sessions.containsKey(sessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<User> getUser(@Nonnull String sessionId) {
|
||||
public Optional<User> getUser(String sessionId) {
|
||||
Session session = sessions.get(sessionId);
|
||||
if (session != null) {
|
||||
return managerFactory.userManager().getUser(sessions.get(sessionId).getUserId());
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public class TableController {
|
|||
}
|
||||
this.table = new Table(roomId, options.getGameType(), options.getName(), controllerName, DeckValidatorFactory.instance.createDeckValidator(options.getDeckType()),
|
||||
options.getPlayerTypes(), new TableRecorderImpl(managerFactory.userManager()), match, options.getBannedUsers(), options.isPlaneChase());
|
||||
this.chatId = managerFactory.chatManager().createChatSession("Match Table " + table.getId());
|
||||
this.chatId = managerFactory.chatManager().createTableChatSession(table);
|
||||
init();
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ public class TableController {
|
|||
}
|
||||
table = new Table(roomId, options.getTournamentType(), options.getName(), controllerName, DeckValidatorFactory.instance.createDeckValidator(options.getMatchOptions().getDeckType()),
|
||||
options.getPlayerTypes(), new TableRecorderImpl(managerFactory.userManager()), tournament, options.getMatchOptions().getBannedUsers(), options.isPlaneChase());
|
||||
chatId = managerFactory.chatManager().createChatSession("Tourney table " + table.getId());
|
||||
chatId = managerFactory.chatManager().createTableChatSession(table);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
package mage.server.challenge;
|
||||
|
||||
import mage.constants.Zone;
|
||||
import mage.game.match.Match;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* C U R R E N T L Y U N U S E D
|
||||
*
|
||||
* Loads challenges from scenarios.
|
||||
* Configure games by initializing starting game board.
|
||||
*/
|
||||
public enum ChallengeManager {
|
||||
|
||||
instance;
|
||||
|
||||
public void prepareChallenge(UUID playerId, Match match) {
|
||||
Map<Zone, String> commands = new HashMap<>();
|
||||
commands.put(Zone.OUTSIDE, "life:3");
|
||||
match.getGame().cheat(playerId, commands);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package mage.server.exceptions;
|
||||
|
||||
/**
|
||||
* Created by igoudt on 14-1-2017.
|
||||
*/
|
||||
public class UserNotFoundException extends Exception {
|
||||
}
|
||||
|
|
@ -85,11 +85,11 @@ public class GameController implements GameCallback {
|
|||
|
||||
public GameController(ManagerFactory managerFactory, Game game, ConcurrentMap<UUID, UUID> userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions) {
|
||||
this.managerFactory = managerFactory;
|
||||
gameExecutor = managerFactory.threadExecutor().getGameExecutor();
|
||||
responseIdleTimeoutExecutor = managerFactory.threadExecutor().getTimeoutIdleExecutor();
|
||||
gameSessionId = UUID.randomUUID();
|
||||
this.gameExecutor = managerFactory.threadExecutor().getGameExecutor();
|
||||
this.responseIdleTimeoutExecutor = managerFactory.threadExecutor().getTimeoutIdleExecutor();
|
||||
this.gameSessionId = UUID.randomUUID();
|
||||
this.userPlayerMap = userPlayerMap;
|
||||
chatId = managerFactory.chatManager().createChatSession("Game " + game.getId());
|
||||
this.chatId = managerFactory.chatManager().createGameChatSession(game);
|
||||
this.userRequestingRollback = null;
|
||||
this.game = game;
|
||||
this.game.setSaveGame(managerFactory.configSettings().isSaveGameActivated());
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package mage.server.managers;
|
||||
|
||||
import mage.game.Game;
|
||||
import mage.game.Table;
|
||||
import mage.game.tournament.Tournament;
|
||||
import mage.server.ChatSession;
|
||||
import mage.server.DisconnectReason;
|
||||
import mage.server.exceptions.UserNotFoundException;
|
||||
import mage.view.ChatMessage;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -11,7 +12,13 @@ import java.util.UUID;
|
|||
|
||||
public interface ChatManager {
|
||||
|
||||
UUID createChatSession(String info);
|
||||
UUID createRoomChatSession(UUID roomId);
|
||||
|
||||
UUID createTourneyChatSession(Tournament tournament);
|
||||
|
||||
UUID createTableChatSession(Table table);
|
||||
|
||||
UUID createGameChatSession(Game game);
|
||||
|
||||
void joinChat(UUID chatId, UUID userId);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@ import mage.server.Session;
|
|||
import mage.server.User;
|
||||
import org.jboss.remoting.callback.InvokerCallbackHandler;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface SessionManager {
|
||||
|
||||
Optional<Session> getSession(@Nonnull String sessionId);
|
||||
Optional<Session> getSession(String sessionId);
|
||||
|
||||
void createSession(String sessionId, InvokerCallbackHandler callbackHandler);
|
||||
|
||||
|
|
@ -37,9 +36,9 @@ public interface SessionManager {
|
|||
|
||||
boolean checkAdminAccess(String sessionId);
|
||||
|
||||
boolean isValidSession(@Nonnull String sessionId);
|
||||
boolean isValidSession(String sessionId);
|
||||
|
||||
Optional<User> getUser(@Nonnull String sessionId);
|
||||
Optional<User> getUser(String sessionId);
|
||||
|
||||
boolean extendUserSession(String sessionId, String pingInfo);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package mage.server.services;
|
||||
|
||||
/**
|
||||
* Responsible for gathering logs and storing them in DB.
|
||||
*
|
||||
* @author noxx
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LogService {
|
||||
|
||||
/**
|
||||
* Logs any information
|
||||
*
|
||||
* @param key Log key. Should be the same for the same types of logs.
|
||||
* @param args Any parameters in string representation.
|
||||
*/
|
||||
void log(String key, String... args);
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package mage.server.services;
|
||||
|
||||
/**
|
||||
* Common interface for all services.
|
||||
*
|
||||
* @author noxx
|
||||
*/
|
||||
public interface MageService {
|
||||
/**
|
||||
* Restores data on startup.
|
||||
*/
|
||||
void initService();
|
||||
|
||||
/**
|
||||
* Dumps data to DB.
|
||||
*/
|
||||
void saveData();
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ public class TournamentController {
|
|||
public TournamentController(ManagerFactory managerFactory, Tournament tournament, ConcurrentMap<UUID, UUID> userPlayerMap, UUID tableId) {
|
||||
this.managerFactory = managerFactory;
|
||||
this.userPlayerMap = userPlayerMap;
|
||||
chatId = managerFactory.chatManager().createChatSession("Tournament " + tournament.getId());
|
||||
this.chatId = managerFactory.chatManager().createTourneyChatSession(tournament);
|
||||
this.tournament = tournament;
|
||||
this.tableId = tableId;
|
||||
init();
|
||||
|
|
@ -261,9 +261,7 @@ public class TournamentController {
|
|||
table.setState(TableState.STARTING);
|
||||
tableManager.startTournamentSubMatch(null, table.getId());
|
||||
tableManager.getMatch(table.getId()).ifPresent(match -> {
|
||||
match.setTableId(tableId);
|
||||
pair.setMatch(match);
|
||||
pair.setTableId(table.getId());
|
||||
pair.setMatchAndTable(match, table.getId());
|
||||
player1.setState(TournamentPlayerState.DUELING);
|
||||
player2.setState(TournamentPlayerState.DUELING);
|
||||
});
|
||||
|
|
@ -291,9 +289,7 @@ public class TournamentController {
|
|||
table.setState(TableState.STARTING);
|
||||
tableManager.startTournamentSubMatch(null, table.getId());
|
||||
tableManager.getMatch(table.getId()).ifPresent(match -> {
|
||||
match.setTableId(tableId);
|
||||
round.setMatch(match);
|
||||
round.setTableId(table.getId());
|
||||
round.setMatchAndTable(match, table.getId());
|
||||
for (TournamentPlayer player : round.getAllPlayers()) {
|
||||
player.setState(TournamentPlayerState.DUELING);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import mage.constants.Duration;
|
|||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetAnyTarget;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ public final class AbunaAcolyte extends CardImpl {
|
|||
Ability ability1 = new SimpleActivatedAbility(new PreventDamageToTargetEffect(Duration.EndOfTurn, 1), new TapSourceCost());
|
||||
ability1.addTarget(new TargetAnyTarget());
|
||||
Ability ability2 = new SimpleActivatedAbility(new PreventDamageToTargetEffect(Duration.EndOfTurn, 2), new TapSourceCost());
|
||||
ability2.addTarget(new TargetCreaturePermanent(filter));
|
||||
ability2.addTarget(new TargetPermanent(filter));
|
||||
this.addAbility(ability1);
|
||||
this.addAbility(ability2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import mage.constants.CardType;
|
|||
import mage.constants.ComparisonType;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.mageobject.PowerPredicate;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.target.common.TargetCreaturePermanentAmount;
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ public final class AbzanCharm extends CardImpl {
|
|||
|
||||
// Choose one -
|
||||
// *Exile target creature with power 3 or greater
|
||||
this.getSpellAbility().addTarget(new TargetCreaturePermanent(FILTER));
|
||||
this.getSpellAbility().addTarget(new TargetPermanent(FILTER));
|
||||
this.getSpellAbility().addEffect(new ExileTargetEffect());
|
||||
|
||||
// *You draw two cards and you lose 2 life
|
||||
|
|
|
|||
|
|
@ -16,10 +16,13 @@ import mage.constants.SubType;
|
|||
import mage.constants.Zone;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE;
|
||||
|
||||
/**
|
||||
* @author JRHerlehy
|
||||
*/
|
||||
|
|
@ -47,7 +50,7 @@ public final class AcademyJourneymage extends CardImpl {
|
|||
|
||||
// When Academy Journeymage enters the battlefield, return target creature an opponent controls to its owner's hand.
|
||||
ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandTargetEffect());
|
||||
ability.addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE));
|
||||
ability.addTarget(new TargetPermanent(FILTER_OPPONENTS_PERMANENT_CREATURE));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.dynamicvalue.common.CardsInAllGraveyardsCount;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
|
|
@ -11,8 +10,9 @@ import mage.constants.CardType;
|
|||
import mage.filter.FilterCard;
|
||||
import mage.filter.predicate.mageobject.NamePredicate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public final class AccumulatedKnowledge extends CardImpl {
|
||||
|
|
@ -24,13 +24,13 @@ public final class AccumulatedKnowledge extends CardImpl {
|
|||
}
|
||||
|
||||
public AccumulatedKnowledge(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{1}{U}");
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
|
||||
|
||||
|
||||
// Draw a card, then draw cards equal to the number of cards named Accumulated Knowledge in all graveyards.
|
||||
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1));
|
||||
Effect effect = new DrawCardSourceControllerEffect(new CardsInAllGraveyardsCount(filter));
|
||||
effect.setText(", then draw cards equal to the number of cards named {this} in all graveyards");
|
||||
effect.setText(", then draw cards equal to the number of cards named Accumulated Knowledge in all graveyards");
|
||||
this.getSpellAbility().addEffect(effect);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,9 +44,7 @@ public final class AcererakTheArchlich extends CardImpl {
|
|||
Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnToHandSourceEffect(true))
|
||||
.withInterveningIf(AcererakTheArchlichCondition.instance);
|
||||
ability.addEffect(new VentureIntoTheDungeonEffect().concatBy("and"));
|
||||
ability.addHint(CurrentDungeonHint.instance);
|
||||
ability.addHint(CompletedDungeonCondition.getHint());
|
||||
this.addAbility(ability, new CompletedDungeonWatcher());
|
||||
this.addAbility(ability.addHint(CurrentDungeonHint.instance).addHint(CompletedDungeonCondition.getHint()), new CompletedDungeonWatcher());
|
||||
|
||||
// Whenever Acererak the Archlich attacks, for each opponent, you create a 2/2 black Zombie creature token unless that player sacrifices a creature.
|
||||
this.addAbility(new AttacksTriggeredAbility(new AcererakTheArchlichEffect()));
|
||||
|
|
@ -83,7 +81,7 @@ class AcererakTheArchlichEffect extends OneShotEffect {
|
|||
AcererakTheArchlichEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "for each opponent, you create a 2/2 black Zombie creature " +
|
||||
"token unless that player sacrifices a creature";
|
||||
"token unless that player sacrifices a creature of their choice";
|
||||
}
|
||||
|
||||
private AcererakTheArchlichEffect(final AcererakTheArchlichEffect effect) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import mage.cards.CardImpl;
|
|||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ public final class AcesBaseballBat extends CardImpl {
|
|||
// Equip legendary creature (1)
|
||||
this.addAbility(new EquipAbility(
|
||||
Outcome.AddAbility, new GenericManaCost(1),
|
||||
new TargetControlledCreaturePermanent(filterLegendary), false
|
||||
new TargetPermanent(filterLegendary), false
|
||||
));
|
||||
|
||||
// Equip {3}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import mage.abilities.DelayedTriggeredAbility;
|
|||
import mage.abilities.condition.common.BeforeBlockersAreDeclaredCondition;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.abilities.decorator.ConditionalActivatedAbility;
|
||||
import mage.abilities.common.ActivateIfConditionActivatedAbility;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
|
||||
import mage.abilities.effects.common.DestroyTargetEffect;
|
||||
|
|
@ -39,7 +39,7 @@ public final class AcidicDagger extends CardImpl {
|
|||
// {4}, {tap}: Whenever target creature deals combat damage to a non-Wall creature this turn,
|
||||
// destroy that non-Wall creature. When the targeted creature leaves the battlefield this turn,
|
||||
// sacrifice Acidic Dagger. Activate this ability only before blockers are declared.
|
||||
Ability ability = new ConditionalActivatedAbility(
|
||||
Ability ability = new ActivateIfConditionActivatedAbility(
|
||||
new CreateDelayedTriggeredAbilityEffect(new AcidicDaggerDestroyNonWallAbility()),
|
||||
new GenericManaCost(4),
|
||||
BeforeBlockersAreDeclaredCondition.instance);
|
||||
|
|
|
|||
|
|
@ -11,8 +11,11 @@ import mage.cards.CardSetInfo;
|
|||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import static mage.filter.StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author North
|
||||
|
|
@ -21,7 +24,7 @@ public final class ActOfAggression extends CardImpl {
|
|||
|
||||
public ActOfAggression(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{R/P}{R/P}");
|
||||
this.getSpellAbility().addTarget(new TargetCreaturePermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE));
|
||||
this.getSpellAbility().addTarget(new TargetPermanent(FILTER_OPPONENTS_PERMANENT_CREATURE));
|
||||
this.getSpellAbility().addEffect(new GainControlTargetEffect(Duration.EndOfTurn));
|
||||
this.getSpellAbility().addEffect(new UntapTargetEffect().setText("Untap that creature"));
|
||||
this.getSpellAbility().addEffect(new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn).setText("It gains haste until end of turn."));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.abilities.common.AttacksAttachedTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
|
|
@ -9,26 +7,26 @@ import mage.abilities.dynamicvalue.DynamicValue;
|
|||
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
|
||||
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
|
||||
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.hint.ValueHint;
|
||||
import mage.abilities.hint.common.ArtifactYouControlHint;
|
||||
import mage.abilities.keyword.EquipAbility;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.PutCards;
|
||||
import mage.constants.SubType;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.PutCards;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterControlledArtifactPermanent;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sobiech
|
||||
*/
|
||||
public final class AdaptiveOmnitool extends CardImpl {
|
||||
|
||||
private final static DynamicValue artifactYouControlCount = new PermanentsOnBattlefieldCount(new FilterControlledArtifactPermanent());
|
||||
private final static Hint hint = new ValueHint("Artifacts you control", artifactYouControlCount);
|
||||
|
||||
public AdaptiveOmnitool(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}");
|
||||
|
|
@ -37,7 +35,7 @@ public final class AdaptiveOmnitool extends CardImpl {
|
|||
|
||||
// Equipped creature gets +1/+1 for each artifact you control.
|
||||
this.addAbility(
|
||||
new SimpleStaticAbility(new BoostEquippedEffect(artifactYouControlCount, artifactYouControlCount)).addHint(hint)
|
||||
new SimpleStaticAbility(new BoostEquippedEffect(artifactYouControlCount, artifactYouControlCount)).addHint(ArtifactYouControlHint.instance)
|
||||
);
|
||||
|
||||
// Whenever equipped creature attacks, look at the top six cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public final class AdaptiveTrainingPost extends CardImpl {
|
|||
this.addAbility(new SpellCastControllerTriggeredAbility(
|
||||
new AddCountersSourceEffect(CounterType.CHARGE.createInstance()),
|
||||
StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false
|
||||
).withInterveningIf(condition));
|
||||
).withInterveningIf(condition).withRuleTextReplacement(true));
|
||||
|
||||
// Remove three charge counters from this artifact: When you next cast an instant or sorcery spell this turn, copy it and you may choose new targets for the copy.
|
||||
this.addAbility(new SimpleActivatedAbility(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.TriggeredAbility;
|
||||
|
|
@ -20,13 +19,15 @@ import mage.filter.predicate.Predicates;
|
|||
import mage.filter.predicate.mageobject.AnotherPredicate;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jeffwadsworth
|
||||
*/
|
||||
public final class AdmonitionAngel extends CardImpl {
|
||||
|
||||
private static final FilterPermanent filter = new FilterPermanent("nonland permanent other than Admonition Angel");
|
||||
private static final FilterPermanent filter = new FilterPermanent("nonland permanent other than {this}");
|
||||
|
||||
static {
|
||||
filter.add(AnotherPredicate.instance);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import mage.constants.TargetController;
|
|||
import mage.counters.CounterType;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.target.Target;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
/**
|
||||
|
|
@ -38,7 +39,7 @@ public final class AdvocateOfTheBeast extends CardImpl {
|
|||
|
||||
// At the beginning of your end step, put a +1/+1 counter on target Beast creature you control.
|
||||
Ability ability = new BeginningOfEndStepTriggeredAbility(new AddCountersTargetEffect(CounterType.P1P1.createInstance()));
|
||||
Target target = new TargetCreaturePermanent(filter);
|
||||
Target target = new TargetPermanent(filter);
|
||||
ability.addTarget(target);
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
|
|
@ -10,12 +9,12 @@ import mage.cards.CardImpl;
|
|||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author fireshoes
|
||||
*/
|
||||
public final class AegisAutomaton extends CardImpl {
|
||||
|
|
@ -29,7 +28,7 @@ public final class AegisAutomaton extends CardImpl {
|
|||
|
||||
// {4}{W}: Return another target creature you control to its owner's hand.
|
||||
Ability ability = new SimpleActivatedAbility(new ReturnToHandTargetEffect(), new ManaCostsImpl<>("{4}{W}"));
|
||||
ability.addTarget(new TargetControlledCreaturePermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL));
|
||||
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_ANOTHER_TARGET_CREATURE_YOU_CONTROL));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import mage.constants.Zone;
|
|||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.mageobject.PowerPredicate;
|
||||
import mage.filter.predicate.mageobject.ToughnessPredicate;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
/**
|
||||
|
|
@ -37,7 +38,7 @@ public final class AegisOfTheMeek extends CardImpl {
|
|||
// {1}, {T}: Target 1/1 creature gets +1/+2 until end of turn.
|
||||
Ability ability = new SimpleActivatedAbility(new BoostTargetEffect(1, 2, Duration.EndOfTurn), new ManaCostsImpl<>("{1}"));
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addTarget(new TargetCreaturePermanent(filter));
|
||||
ability.addTarget(new TargetPermanent(filter));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import mage.cards.CardSetInfo;
|
|||
import mage.constants.CardType;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.mageobject.AbilityPredicate;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
/**
|
||||
|
|
@ -31,7 +32,7 @@ public final class AerialPredation extends CardImpl {
|
|||
|
||||
|
||||
// Destroy target creature with flying. You gain 2 life.
|
||||
this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter));
|
||||
this.getSpellAbility().addTarget(new TargetPermanent(filter));
|
||||
this.getSpellAbility().addEffect(new DestroyTargetEffect());
|
||||
this.getSpellAbility().addEffect(new GainLifeEffect(2));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ enum AerialSurveyorCondition implements Condition {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "";
|
||||
return "defending player controls more lands than you";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import mage.constants.SubType;
|
|||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.mageobject.AbilityPredicate;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -42,7 +43,7 @@ public final class AerieOuphes extends CardImpl {
|
|||
// Sacrifice Aerie Ouphes: Aerie Ouphes deals damage equal to its power to target creature with flying.
|
||||
Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE)
|
||||
.setText("it deals damage equal to its power to target creature with flying"), new SacrificeSourceCost());
|
||||
ability.addTarget(new TargetCreaturePermanent(filter));
|
||||
ability.addTarget(new TargetPermanent(filter));
|
||||
this.addAbility(ability);
|
||||
|
||||
// Persist
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class AetherBarrierEffect extends SacrificeEffect {
|
|||
|
||||
AetherBarrierEffect() {
|
||||
super(new FilterPermanent("permanent to sacrifice"), 1, "that player");
|
||||
this.staticText = "that player sacrifices a permanent unless they pay {1}";
|
||||
this.staticText = "that player sacrifices a permanent of their choice unless they pay {1}";
|
||||
}
|
||||
|
||||
private AetherBarrierEffect(final AetherBarrierEffect effect) {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.Mana;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.common.SpellCastControllerTriggeredAbility;
|
||||
import mage.abilities.costs.common.PayEnergyCost;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Controllable;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.players.Player;
|
||||
|
|
@ -34,8 +37,8 @@ public final class AetherfluxConduit extends CardImpl {
|
|||
this.addAbility(new SpellCastControllerTriggeredAbility(new AetherfluxConduitManaEffect(), false));
|
||||
|
||||
// {T}, Pay fifty {E}: Draw seven cards. You may cast any number of spells from your hand without paying their mana costs.
|
||||
final Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(7), new TapSourceCost());
|
||||
ability.addCost(new PayEnergyCost(50).setText("Pay fifty {E}"));
|
||||
Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(7), new TapSourceCost());
|
||||
ability.addCost(new PayEnergyCost(50).setText("pay fifty {E}"));
|
||||
ability.addEffect(new AetherfluxConduitCastEffect());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
|
@ -68,10 +71,21 @@ class AetherfluxConduitManaEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Optional.ofNullable(this.getValue("spellCast"))
|
||||
int amount = Optional
|
||||
.ofNullable(this.getValue("spellCast"))
|
||||
.map(Spell.class::cast)
|
||||
.ifPresent(spell -> new GetEnergyCountersControllerEffect(spell.getManaValue()).apply(game, source));
|
||||
return true;
|
||||
.map(Spell::getStackAbility)
|
||||
.map(Ability::getManaCostsToPay)
|
||||
.map(ManaCost::getUsedManaToPay)
|
||||
.map(Mana::count)
|
||||
.orElse(0);
|
||||
return amount > 0
|
||||
&& Optional
|
||||
.ofNullable(source)
|
||||
.map(Controllable::getControllerId)
|
||||
.map(game::getPlayer)
|
||||
.filter(player -> player.addCounters(CounterType.ENERGY.createInstance(amount), player.getId(), source, game))
|
||||
.isPresent();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
|
||||
import mage.abilities.effects.common.continuous.BoostTargetEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.keyword.IndestructibleAbility;
|
||||
import mage.constants.SubType;
|
||||
import mage.abilities.triggers.BeginningOfCombatTriggeredAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
import mage.target.TargetPermanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class AethershieldArtificer extends CardImpl {
|
||||
|
|
@ -45,7 +45,7 @@ public final class AethershieldArtificer extends CardImpl {
|
|||
IndestructibleAbility.getInstance(),
|
||||
Duration.EndOfTurn
|
||||
).setText("and gains indestructible until end of turn"));
|
||||
ability.addTarget(new TargetControlledCreaturePermanent(filter));
|
||||
ability.addTarget(new TargetPermanent(filter));
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import mage.filter.common.FilterAttackingOrBlockingCreature;
|
|||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
|
||||
/**
|
||||
|
|
@ -28,7 +29,7 @@ public final class Aethertow extends CardImpl {
|
|||
|
||||
// Put target attacking or blocking creature on top of its owner's library.
|
||||
this.getSpellAbility().addEffect(new AethertowEffect());
|
||||
this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter));
|
||||
this.getSpellAbility().addTarget(new TargetPermanent(filter));
|
||||
|
||||
// Conspire
|
||||
this.addAbility(new ConspireAbility(ConspireAbility.ConspireTargets.ONE));
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue