forked from External/mage
Merge branch 'External-master'
All checks were successful
/ build_release (push) Successful in 10m38s
All checks were successful
/ build_release (push) Successful in 10m38s
This commit is contained in:
commit
3062dd066d
838 changed files with 24448 additions and 4173 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -12,6 +12,7 @@ syntax: glob
|
||||||
*.log.*
|
*.log.*
|
||||||
*/gamelogs
|
*/gamelogs
|
||||||
*/gamelogsJson
|
*/gamelogsJson
|
||||||
|
*/gamesHistory
|
||||||
|
|
||||||
# Mage.Client
|
# Mage.Client
|
||||||
Mage.Client/plugins/images
|
Mage.Client/plugins/images
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ git:
|
||||||
before_install:
|
before_install:
|
||||||
- echo "MAVEN_OPTS='-Xmx2g'" > ~/.mavenrc
|
- echo "MAVEN_OPTS='-Xmx2g'" > ~/.mavenrc
|
||||||
script:
|
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:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.m2
|
- $HOME/.m2
|
||||||
|
|
@ -87,14 +87,6 @@
|
||||||
<artifactId>jetlang</artifactId>
|
<artifactId>jetlang</artifactId>
|
||||||
<version>0.2.23</version>
|
<version>0.2.23</version>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<!-- GUI lib TODO: unused and can be deleted? -->
|
<!-- GUI lib TODO: unused and can be deleted? -->
|
||||||
<groupId>com.jgoodies</groupId>
|
<groupId>com.jgoodies</groupId>
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,9 @@ public final class Constants {
|
||||||
public static final String RESOURCE_SYMBOL_FOLDER_SVG = "svg";
|
public static final String RESOURCE_SYMBOL_FOLDER_SVG = "svg";
|
||||||
public static final String RESOURCE_SYMBOL_FOLDER_PNG = "png";
|
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 {
|
public enum ResourceSymbolSize {
|
||||||
SMALL, // TODO: delete SMALL, MEDIUM and LARGE as outdated (svg or generated png works fine)
|
SMALL, // TODO: delete SMALL, MEDIUM and LARGE as outdated (svg or generated png works fine)
|
||||||
MEDIUM,
|
MEDIUM,
|
||||||
|
|
@ -133,12 +136,12 @@ public final class Constants {
|
||||||
// resources - sets
|
// resources - sets
|
||||||
public static final String RESOURCE_PATH_SETS = File.separator + "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_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 static final String RESOURCE_SET_FOLDER_SVG = "svg";
|
||||||
|
|
||||||
public enum ResourceSetSize {
|
public enum ResourceSetSize {
|
||||||
SMALL,
|
SMALL,
|
||||||
MEDIUM,
|
LARGE,
|
||||||
SVG
|
SVG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ public class DeckGeneratorPool {
|
||||||
// List of cards so far in the deck
|
// List of cards so far in the deck
|
||||||
private final List<Card> deckCards = new ArrayList<>();
|
private final List<Card> deckCards = new ArrayList<>();
|
||||||
// List of reserve cards found to fix up undersized decks
|
// 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;
|
private final Deck deck;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -170,12 +170,13 @@ public class DeckGeneratorPool {
|
||||||
* @param card the card to add.
|
* @param card the card to add.
|
||||||
*/
|
*/
|
||||||
public void addCard(Card card) {
|
public void addCard(Card card) {
|
||||||
Object cnt = cardCounts.get((card.getName()));
|
int count = cardCounts.getOrDefault(card.getName(), 0);
|
||||||
if (cnt == null)
|
cardCounts.put(card.getName(), count + 1);
|
||||||
cardCounts.put(card.getName(), 0);
|
|
||||||
int existingCount = cardCounts.get((card.getName()));
|
|
||||||
cardCounts.put(card.getName(), existingCount + 1);
|
|
||||||
deckCards.add(card);
|
deckCards.add(card);
|
||||||
|
|
||||||
|
if (deckCards.stream().distinct().collect(Collectors.toList()).size() != deckCards.size()) {
|
||||||
|
System.out.println("wtf " + card.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearCards(boolean isClearReserve) {
|
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
|
// 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
|
// can be added to our reserve pool as not to overwhelm the curve
|
||||||
// with high CMC cards and duplicates.
|
// with high CMC cards and duplicates.
|
||||||
if (cardCMC < 7 && getCardCount(card.getName()) == 0) {
|
if (cardCMC < 7 && getCardCount(card.getName()) == 0 && !this.reserveSpells.containsKey(card.getName())) {
|
||||||
this.reserveSpells.add(card);
|
this.reserveSpells.put(card.getName(), card);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -416,48 +417,38 @@ public class DeckGeneratorPool {
|
||||||
* @return a fixed list of cards for this deck.
|
* @return a fixed list of cards for this deck.
|
||||||
*/
|
*/
|
||||||
private List<Card> getFixedSpells() {
|
private List<Card> getFixedSpells() {
|
||||||
int spellSize = deckCards.size();
|
int spellsSize = deckCards.size();
|
||||||
int nonLandSize = (deckSize - landCount);
|
int nonLandSize = (deckSize - landCount);
|
||||||
|
|
||||||
// Less spells than needed
|
// fewer spells than needed - add
|
||||||
if (spellSize < nonLandSize) {
|
if (spellsSize < nonLandSize) {
|
||||||
|
int needExtraSpells = nonLandSize - spellsSize;
|
||||||
int spellsNeeded = nonLandSize - spellSize;
|
List<Card> possibleSpells = new ArrayList<>(reserveSpells.values());
|
||||||
|
while (needExtraSpells > 0) {
|
||||||
// If we haven't got enough spells in reserve to fulfil the amount we need, skip adding any.
|
Card card = RandomUtil.randomFromCollection(possibleSpells);
|
||||||
if (reserveSpells.size() >= spellsNeeded) {
|
if (card == null) {
|
||||||
|
break;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Add randomly selected spells needed
|
if (isValidSpellCard(card)) {
|
||||||
deckCards.addAll(spellsToAdd);
|
needExtraSpells--;
|
||||||
|
deckCards.add(card);
|
||||||
|
}
|
||||||
|
possibleSpells.remove(card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// More spells than needed
|
// more spells than needed - remove
|
||||||
else if (spellSize > (deckSize - landCount)) {
|
if (spellsSize > nonLandSize) {
|
||||||
int spellsRemoved = (spellSize) - (deckSize - landCount);
|
int removeCount = spellsSize - nonLandSize;
|
||||||
for (int i = 0; i < spellsRemoved; ++i) {
|
for (int i = 0; i < removeCount; ++i) {
|
||||||
deckCards.remove(RandomUtil.nextInt(deckCards.size()));
|
deckCards.remove(RandomUtil.randomFromCollection(deckCards));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check we have exactly the right amount of cards for a deck.
|
|
||||||
if (deckCards.size() != nonLandSize) {
|
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;
|
return deckCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -619,48 +610,75 @@ public class DeckGeneratorPool {
|
||||||
if (needCommandersCount > 0 && !genPool.cardCounts.isEmpty()) {
|
if (needCommandersCount > 0 && !genPool.cardCounts.isEmpty()) {
|
||||||
throw new IllegalArgumentException("Wrong code usage: generateSpells with creatures and commanders must be called as first");
|
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);
|
List<DeckGeneratorCMC.CMC> deckCMCs = genPool.getCMCsForSpellCount(needCardsCount);
|
||||||
int count = 0;
|
int usedCardsCount = 0;
|
||||||
int validCommanders = 0;
|
int validCommanders = 0;
|
||||||
int reservesAdded = 0;
|
int reservesAdded = 0;
|
||||||
if (cardPool.size() > 0 && cardPool.size() >= needCardsCount) {
|
if (cardsPool.size() > 0 && cardsPool.size() >= needCardsCount) {
|
||||||
int tries = 0;
|
int tries = 0;
|
||||||
|
List<Card> possibleCards = new ArrayList<>(cardsPool);
|
||||||
|
List<Card> possibleCommanders = new ArrayList<>(commandersPool);
|
||||||
while (true) {
|
while (true) {
|
||||||
tries++;
|
tries++;
|
||||||
|
|
||||||
// can't finish deck, stop and use reserved cards later
|
// can't finish deck, stop and use reserved cards later
|
||||||
if (tries > DeckGenerator.MAX_TRIES) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// can finish deck - but make sure it has commander
|
// can finish deck - but make sure it has commander
|
||||||
if (count >= needCardsCount) {
|
if (usedCardsCount >= needCardsCount) {
|
||||||
if (validCommanders < needCommandersCount) {
|
if (validCommanders < needCommandersCount) {
|
||||||
// reset deck search from scratch (except reserved cards)
|
// reset deck search from scratch (except reserved cards)
|
||||||
count = 0;
|
usedCardsCount = 0;
|
||||||
validCommanders = 0;
|
validCommanders = 0;
|
||||||
deckCMCs = genPool.getCMCsForSpellCount(needCardsCount);
|
deckCMCs = genPool.getCMCsForSpellCount(needCardsCount);
|
||||||
genPool.clearCards(false);
|
genPool.clearCards(true);
|
||||||
|
possibleCards = new ArrayList<>(cardsPool);
|
||||||
|
possibleCommanders = new ArrayList<>(commandersPool);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
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)) {
|
if (!genPool.isValidSpellCard(card)) {
|
||||||
|
possibleCards.remove(card);
|
||||||
|
possibleCommanders.remove(card);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cardCMC = card.getManaValue();
|
int cardCMC = card.getManaValue();
|
||||||
for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) {
|
for (DeckGeneratorCMC.CMC deckCMC : deckCMCs) {
|
||||||
if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) {
|
if (cardCMC >= deckCMC.min && cardCMC <= deckCMC.max) {
|
||||||
int currentAmount = deckCMC.getAmount();
|
int needAmount = deckCMC.getAmount();
|
||||||
if (currentAmount > 0) {
|
if (needAmount > 0) {
|
||||||
deckCMC.setAmount(currentAmount - 1);
|
deckCMC.setAmount(needAmount - 1);
|
||||||
genPool.addCard(card.copy());
|
genPool.addCard(card.copy());
|
||||||
count++;
|
usedCardsCount++;
|
||||||
// make sure it has compatible commanders
|
// make sure it has compatible commanders
|
||||||
if (genPool.isValidCommander(card)) {
|
if (genPool.isValidCommander(card)) {
|
||||||
validCommanders++;
|
validCommanders++;
|
||||||
|
|
@ -674,7 +692,7 @@ public class DeckGeneratorPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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.dialog.PreferencesDialog;
|
||||||
import mage.client.plugins.impl.Plugins;
|
import mage.client.plugins.impl.Plugins;
|
||||||
import mage.client.util.Event;
|
import mage.client.util.Event;
|
||||||
import mage.client.util.GUISizeHelper;
|
|
||||||
import mage.client.util.Listener;
|
import mage.client.util.Listener;
|
||||||
import mage.client.util.audio.AudioManager;
|
import mage.client.util.audio.AudioManager;
|
||||||
import mage.components.CardInfoPane;
|
import mage.components.CardInfoPane;
|
||||||
|
|
@ -31,7 +30,6 @@ import mage.util.XmageThreadFactory;
|
||||||
import mage.view.CardView;
|
import mage.view.CardView;
|
||||||
import mage.view.SimpleCardView;
|
import mage.view.SimpleCardView;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.mage.card.arcane.ManaSymbols;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.Border;
|
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
|
// auto-import dropped files from OS
|
||||||
if (mode == DeckEditorMode.FREE_BUILDING) {
|
if (mode == DeckEditorMode.FREE_BUILDING) {
|
||||||
|
|
@ -673,20 +672,28 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
|
|
||||||
private void refreshDeck() {
|
private void refreshDeck() {
|
||||||
if (this.isVisible()) { // TODO: test auto-close deck with active lands dialog, e.g. on timeout
|
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 {
|
try {
|
||||||
setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
MageFrame.getDesktop().setCursor(new Cursor(Cursor.WAIT_CURSOR));
|
||||||
this.txtDeckName.setText(deck.getName());
|
this.txtDeckName.setText(deck.getName());
|
||||||
deckArea.loadDeck(deck, useLayout, bigCard);
|
deckArea.loadDeck(deck, useLayout, bigCard);
|
||||||
|
if (useDeckValidation) {
|
||||||
|
validateDeck();
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateDeck() {
|
||||||
|
this.deckLegalityDisplay.setVisible(true);
|
||||||
|
this.deckLegalityDisplay.validateDeck(deck);
|
||||||
|
}
|
||||||
|
|
||||||
private void setTimeout(int s) {
|
private void setTimeout(int s) {
|
||||||
int minute = s / 60;
|
int minute = s / 60;
|
||||||
int second = s - (minute * 60);
|
int second = s - (minute * 60);
|
||||||
|
|
@ -762,7 +769,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
|
|
||||||
if (newDeck != null) {
|
if (newDeck != null) {
|
||||||
deck = newDeck;
|
deck = newDeck;
|
||||||
refreshDeck();
|
refreshDeck(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// save last deck import folder
|
// 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);
|
Deck deckToAppend = Deck.load(DeckImporter.importDeckFromFile(tempDeckPath, errorMessages, false), true, true);
|
||||||
processAndShowImportErrors(errorMessages);
|
processAndShowImportErrors(errorMessages);
|
||||||
this.deck = Deck.append(deckToAppend, this.deck);
|
this.deck = Deck.append(deckToAppend, this.deck);
|
||||||
refreshDeck();
|
refreshDeck(false, true);
|
||||||
} catch (GameException e1) {
|
} catch (GameException e1) {
|
||||||
JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE);
|
JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -922,7 +929,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
|
|
||||||
if (newDeck != null) {
|
if (newDeck != null) {
|
||||||
deck = newDeck;
|
deck = newDeck;
|
||||||
refreshDeck();
|
refreshDeck(false, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1441,7 +1448,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
|
|
||||||
if (newDeck != null) {
|
if (newDeck != null) {
|
||||||
deck = newDeck;
|
deck = newDeck;
|
||||||
refreshDeck(true);
|
refreshDeck(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// save last deck history
|
// save last deck history
|
||||||
|
|
@ -1474,7 +1481,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
// in deck editor mode - clear all cards
|
// in deck editor mode - clear all cards
|
||||||
deck = new Deck();
|
deck = new Deck();
|
||||||
}
|
}
|
||||||
refreshDeck();
|
refreshDeck(false, true);
|
||||||
}//GEN-LAST:event_btnNewActionPerformed
|
}//GEN-LAST:event_btnNewActionPerformed
|
||||||
|
|
||||||
private void btnExitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExitActionPerformed
|
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
|
private void btnAddLandActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAddLandActionPerformed
|
||||||
AddLandDialog dialog = new AddLandDialog();
|
AddLandDialog dialog = new AddLandDialog();
|
||||||
dialog.showDialog(deck, mode, this::refreshDeck);
|
dialog.showDialog(deck, mode, () -> {
|
||||||
|
this.refreshDeck(false, true);
|
||||||
|
});
|
||||||
}//GEN-LAST:event_btnAddLandActionPerformed
|
}//GEN-LAST:event_btnAddLandActionPerformed
|
||||||
|
|
||||||
private void btnGenDeckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnGenDeckActionPerformed
|
private void btnGenDeckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnGenDeckActionPerformed
|
||||||
|
|
@ -1501,7 +1510,7 @@ public class DeckEditorPanel extends javax.swing.JPanel {
|
||||||
} finally {
|
} finally {
|
||||||
MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
MageFrame.getDesktop().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||||
}
|
}
|
||||||
refreshDeck();
|
refreshDeck(false, true);
|
||||||
}//GEN-LAST:event_btnGenDeckActionPerformed
|
}//GEN-LAST:event_btnGenDeckActionPerformed
|
||||||
|
|
||||||
private void btnSubmitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSubmitActionPerformed
|
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
|
}//GEN-LAST:event_btnExportActionPerformed
|
||||||
|
|
||||||
private void btnLegalityActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLegalityActionPerformed
|
private void btnLegalityActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLegalityActionPerformed
|
||||||
this.deckLegalityDisplay.setVisible(true);
|
validateDeck();
|
||||||
this.deckLegalityDisplay.validateDeck(deck);
|
|
||||||
}//GEN-LAST:event_btnLegalityActionPerformed
|
}//GEN-LAST:event_btnLegalityActionPerformed
|
||||||
|
|
||||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ public class DraftPickLogger {
|
||||||
private void appendToDraftLog(String data) {
|
private void appendToDraftLog(String data) {
|
||||||
if (logging) {
|
if (logging) {
|
||||||
try {
|
try {
|
||||||
Files.write(logPath, data.getBytes(), StandardOpenOption.APPEND);
|
Files.write(logPath, data.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOGGER.error(null, ex);
|
LOGGER.error(null, ex);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -257,10 +257,7 @@ public class CallbackClientImpl implements CallbackClient {
|
||||||
if (panel != null) {
|
if (panel != null) {
|
||||||
Session session = SessionHandler.getSession();
|
Session session = SessionHandler.getSession();
|
||||||
if (session.isJsonLogActive()) {
|
if (session.isJsonLogActive()) {
|
||||||
UUID gameId = callback.getObjectId();
|
|
||||||
appendJsonEvent("GAME_OVER", callback.getObjectId(), message);
|
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());
|
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));
|
formatFilterList.add(RowFilter.regexFilter("^Limited", TablesTableModel.COLUMN_DECK_TYPE));
|
||||||
}
|
}
|
||||||
if (btnFormatOther.isSelected()) {
|
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
|
// skill
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,6 @@ public final class ManaSymbols {
|
||||||
public static void loadImages() {
|
public static void loadImages() {
|
||||||
logger.info("Symbols: loading...");
|
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?
|
// TODO: remove medium sets files to "medium" folder like symbols above?
|
||||||
|
|
||||||
// prepare svg's css settings
|
// prepare svg's css settings
|
||||||
|
|
@ -145,9 +140,9 @@ public final class ManaSymbols {
|
||||||
Map<Rarity, Image> rarityImages = new EnumMap<>(Rarity.class);
|
Map<Rarity, Image> rarityImages = new EnumMap<>(Rarity.class);
|
||||||
setImages.put(set, rarityImages);
|
setImages.put(set, rarityImages);
|
||||||
|
|
||||||
// load medium size
|
// load large size
|
||||||
for (Rarity rarityCode : codes) {
|
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 {
|
try {
|
||||||
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
|
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
|
||||||
int width = image.getWidth(null);
|
int width = image.getWidth(null);
|
||||||
|
|
@ -167,7 +162,7 @@ public final class ManaSymbols {
|
||||||
|
|
||||||
// generate small size
|
// generate small size
|
||||||
try {
|
try {
|
||||||
File file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM));
|
File file = new File(getResourceSetsPath(ResourceSetSize.LARGE));
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
file.mkdirs();
|
file.mkdirs();
|
||||||
}
|
}
|
||||||
|
|
@ -175,11 +170,11 @@ public final class ManaSymbols {
|
||||||
for (Rarity code : codes) {
|
for (Rarity code : codes) {
|
||||||
File newFile = new File(pathRoot + '-' + code + ".png");
|
File newFile = new File(pathRoot + '-' + code + ".png");
|
||||||
if (!(MageFrame.isSkipSmallSymbolGenerationForExisting() && newFile.exists())) {// skip if option enabled and file already exists
|
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()) {
|
if (file.exists()) {
|
||||||
continue;
|
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();
|
Image image = UI.getImageIcon(file.getAbsolutePath()).getImage();
|
||||||
try {
|
try {
|
||||||
int width = image.getWidth(null);
|
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;
|
ResourceSymbolSize needSize = null;
|
||||||
if (size <= 15) {
|
if (size <= 15) {
|
||||||
|
|
@ -250,15 +245,15 @@ public final class ManaSymbols {
|
||||||
needSize = ResourceSymbolSize.LARGE;
|
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) {
|
private static BufferedImage loadSymbolAsPNG(String symbol, int resizeToWidth, int resizeToHeight) {
|
||||||
File file = getSymbolFileNameAsGIF(symbol, resizeToWidth);
|
File file = getSymbolFileNameAsPNG(symbol, resizeToWidth);
|
||||||
return loadSymbolAsGIF(file, resizeToWidth, resizeToHeight);
|
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;
|
BufferedImage image = null;
|
||||||
|
|
||||||
|
|
@ -315,9 +310,9 @@ public final class ManaSymbols {
|
||||||
|
|
||||||
// gif (if svg fails)
|
// gif (if svg fails)
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
file = getSymbolFileNameAsGIF(symbol, size);
|
file = getSymbolFileNameAsPNG(symbol, size);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
image = loadSymbolAsGIF(file, size, size);
|
image = loadSymbolAsPNG(file, size, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
|
|
@ -352,29 +347,6 @@ public final class ManaSymbols {
|
||||||
return errorInfo.isEmpty();
|
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) {
|
private static String getResourceSymbolsPath(ResourceSymbolSize needSize) {
|
||||||
// return real path to symbols (default or user defined)
|
// return real path to symbols (default or user defined)
|
||||||
|
|
||||||
|
|
@ -420,8 +392,8 @@ public final class ManaSymbols {
|
||||||
case SMALL:
|
case SMALL:
|
||||||
path = path + Constants.RESOURCE_SET_FOLDER_SMALL;
|
path = path + Constants.RESOURCE_SET_FOLDER_SMALL;
|
||||||
break;
|
break;
|
||||||
case MEDIUM:
|
case LARGE:
|
||||||
path = path + Constants.RESOURCE_SET_FOLDER_MEDIUM;
|
path = path + Constants.RESOURCE_SET_FOLDER_LARGE;
|
||||||
break;
|
break;
|
||||||
case SVG:
|
case SVG:
|
||||||
path = path + Constants.RESOURCE_SET_FOLDER_SVG;
|
path = path + Constants.RESOURCE_SET_FOLDER_SVG;
|
||||||
|
|
|
||||||
|
|
@ -663,7 +663,9 @@ public class CardPluginImpl implements CardPlugin {
|
||||||
// mana symbols (low quality)
|
// mana symbols (low quality)
|
||||||
jobs = new GathererSymbols();
|
jobs = new GathererSymbols();
|
||||||
for (DownloadJob job : jobs) {
|
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)
|
// set code symbols (low quality)
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,10 @@ public class Downloader extends AbstractLaternaBean {
|
||||||
|
|
||||||
public Downloader() {
|
public Downloader() {
|
||||||
// prepare 10 threads and start to waiting new download jobs from queue
|
// 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);
|
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 fiber = f.create();
|
||||||
fiber.start();
|
fiber.start();
|
||||||
fibers.add(fiber);
|
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
|
* Download: set code symbols download from wizards web size
|
||||||
* <p>
|
* <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> {
|
public class GathererSets implements Iterable<DownloadJob> {
|
||||||
|
|
||||||
|
|
@ -41,9 +43,10 @@ public class GathererSets implements Iterable<DownloadJob> {
|
||||||
|
|
||||||
private static File outDir;
|
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);
|
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",
|
private static final String[] symbolsBasic = {"10E", "9ED", "8ED", "7ED", "6ED", "5ED", "4ED", "3ED", "2ED", "LEB", "LEA",
|
||||||
"HOP",
|
"HOP",
|
||||||
"ARN", "ATQ", "LEG", "DRK", "FEM", "HML",
|
"ARN", "ATQ", "LEG", "DRK", "FEM", "HML",
|
||||||
|
|
@ -61,14 +64,16 @@ public class GathererSets implements Iterable<DownloadJob> {
|
||||||
"TSP", "TSB", "PLC", "FUT",
|
"TSP", "TSB", "PLC", "FUT",
|
||||||
"LRW", "MOR",
|
"LRW", "MOR",
|
||||||
"SHM", "EVE",
|
"SHM", "EVE",
|
||||||
"MED", "ME2", "ME3", "ME4",
|
"ME2", "ME3", "ME4",
|
||||||
"POR", "P02", "PTK",
|
"POR", "P02", "PTK",
|
||||||
"ARC", "DD3EVG",
|
"ARC", "DD3EVG",
|
||||||
"W16", "W17",
|
"W16", "W17",
|
||||||
// "PALP" -- Gatherer does not have the set Asia Pacific Land Program
|
// "PALP" -- Gatherer does not have the set Asia Pacific Land Program
|
||||||
// "ATH" -- has cards from many sets, symbol does not exist on gatherer
|
// "ATH" -- has cards from many sets, symbol does not exist on gatherer
|
||||||
// "CP", "DPA", "PELP", "PGPX", "PGRU", "H17", "JR", "SWS", // need to fix
|
// "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
|
// current testing
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -98,21 +103,25 @@ public class GathererSets implements Iterable<DownloadJob> {
|
||||||
"GNT", "UMA", "GRN",
|
"GNT", "UMA", "GRN",
|
||||||
"RNA", "WAR", "MH1",
|
"RNA", "WAR", "MH1",
|
||||||
"M20",
|
"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",
|
"JMP", "2XM", "ZNR", "KLR", "CMR", "KHC", "KHM", "TSR", "STX", "STA",
|
||||||
"C21", "MH2", "AFR", "AFC", "J21", "MID", "MIC", "VOW", "VOC", "YMID",
|
"C21", "MH2", "AFR", "AFC", "J21", "MID", "MIC", "VOW", "VOC", "YMID",
|
||||||
"NEC", "YNEO", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "YDMU", "40K", "GN3",
|
"NEC", "YNEO", "NEO", "SNC", "NCC", "CLB", "2X2", "DMU", "DMC", "40K", "GN3",
|
||||||
"UNF", "BRO", "BRC", "BOT", "30A", "J22", "SCD", "DMR", "ONE", "ONC",
|
"UNF", "BRO", "BRC", "BOT", "J22", "DMR", "ONE", "ONC",
|
||||||
"MOM", "MOC", "MUL", "MAT", "LTR", "CMM", "WOE", "WHO", "RVR", "WOT",
|
"MOM", "MOC", "MUL", "MAT", "LTR", "CMM", "WOE", "WHO", "RVR", "WOT",
|
||||||
"WOC", "SPG", "LCI", "LCC", "REX", "PIP", "MKM", "MKC", "CLU", "OTJ",
|
"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", "EOS"
|
||||||
// "HHO", "ANA" -- do not exist on gatherer
|
// "HHO", "ANA" -- do not exist on gatherer
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String[] symbolsOnlyMyth = {
|
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
|
// "HTR16" does not exist
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String[] symbolsOnlySpecial = {
|
private static final String[] symbolsOnlySpecial = {
|
||||||
"MPS", "MP2"
|
"MPS", "MP2"
|
||||||
};
|
};
|
||||||
|
|
@ -130,6 +139,7 @@ public class GathererSets implements Iterable<DownloadJob> {
|
||||||
codeReplacements.put("APC", "AP");
|
codeReplacements.put("APC", "AP");
|
||||||
codeReplacements.put("ARN", "AN");
|
codeReplacements.put("ARN", "AN");
|
||||||
codeReplacements.put("ATQ", "AQ");
|
codeReplacements.put("ATQ", "AQ");
|
||||||
|
codeReplacements.put("BTD", "BD");
|
||||||
codeReplacements.put("CMA", "CM1");
|
codeReplacements.put("CMA", "CM1");
|
||||||
codeReplacements.put("CHR", "CH");
|
codeReplacements.put("CHR", "CH");
|
||||||
codeReplacements.put("DVD", "DD3_DVD");
|
codeReplacements.put("DVD", "DD3_DVD");
|
||||||
|
|
@ -165,14 +175,16 @@ public class GathererSets implements Iterable<DownloadJob> {
|
||||||
codeReplacements.put("UGIN", "FRF_UGIN");
|
codeReplacements.put("UGIN", "FRF_UGIN");
|
||||||
codeReplacements.put("UGL", "UG");
|
codeReplacements.put("UGL", "UG");
|
||||||
codeReplacements.put("ULG", "GU");
|
codeReplacements.put("ULG", "GU");
|
||||||
|
codeReplacements.put("UNF", "UNFS");
|
||||||
codeReplacements.put("USG", "UZ");
|
codeReplacements.put("USG", "UZ");
|
||||||
codeReplacements.put("VIS", "VI");
|
codeReplacements.put("VIS", "VI");
|
||||||
codeReplacements.put("WTH", "WL");
|
codeReplacements.put("WTH", "WL");
|
||||||
|
codeReplacements.put("YMID", "Y22");
|
||||||
|
codeReplacements.put("YNEO", "Y22NEO");
|
||||||
}
|
}
|
||||||
|
|
||||||
public GathererSets() {
|
public GathererSets() {
|
||||||
|
outDir = new File(getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS_RARITY_DEFAULT_PATH);
|
||||||
outDir = new File(getImagesDir() + Constants.RESOURCE_PATH_SYMBOLS);
|
|
||||||
|
|
||||||
if (!outDir.exists()) {
|
if (!outDir.exists()) {
|
||||||
outDir.mkdirs();
|
outDir.mkdirs();
|
||||||
|
|
@ -287,9 +299,9 @@ public class GathererSets implements Iterable<DownloadJob> {
|
||||||
canDownload = false;
|
canDownload = false;
|
||||||
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
||||||
canDownload = true;
|
canDownload = true;
|
||||||
jobs.add(generateDownloadJob(symbol, "C", "C"));
|
jobs.add(generateDownloadJob(symbol, "C", "common"));
|
||||||
jobs.add(generateDownloadJob(symbol, "U", "U"));
|
jobs.add(generateDownloadJob(symbol, "U", "uncommon"));
|
||||||
jobs.add(generateDownloadJob(symbol, "R", "R"));
|
jobs.add(generateDownloadJob(symbol, "R", "rare"));
|
||||||
}
|
}
|
||||||
CheckSearchResult(symbol, exp, canDownload, true, true, true, false);
|
CheckSearchResult(symbol, exp, canDownload, true, true, true, false);
|
||||||
}
|
}
|
||||||
|
|
@ -299,10 +311,10 @@ public class GathererSets implements Iterable<DownloadJob> {
|
||||||
canDownload = false;
|
canDownload = false;
|
||||||
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
||||||
canDownload = true;
|
canDownload = true;
|
||||||
jobs.add(generateDownloadJob(symbol, "C", "C"));
|
jobs.add(generateDownloadJob(symbol, "C", "common"));
|
||||||
jobs.add(generateDownloadJob(symbol, "U", "U"));
|
jobs.add(generateDownloadJob(symbol, "U", "uncommon"));
|
||||||
jobs.add(generateDownloadJob(symbol, "R", "R"));
|
jobs.add(generateDownloadJob(symbol, "R", "rare"));
|
||||||
jobs.add(generateDownloadJob(symbol, "M", "M"));
|
jobs.add(generateDownloadJob(symbol, "M", "mythic"));
|
||||||
}
|
}
|
||||||
CheckSearchResult(symbol, exp, canDownload, true, true, true, true);
|
CheckSearchResult(symbol, exp, canDownload, true, true, true, true);
|
||||||
}
|
}
|
||||||
|
|
@ -312,7 +324,7 @@ public class GathererSets implements Iterable<DownloadJob> {
|
||||||
canDownload = false;
|
canDownload = false;
|
||||||
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
||||||
canDownload = true;
|
canDownload = true;
|
||||||
jobs.add(generateDownloadJob(symbol, "M", "M"));
|
jobs.add(generateDownloadJob(symbol, "M", "mythic"));
|
||||||
}
|
}
|
||||||
CheckSearchResult(symbol, exp, canDownload, false, false, false, true);
|
CheckSearchResult(symbol, exp, canDownload, false, false, false, true);
|
||||||
}
|
}
|
||||||
|
|
@ -322,7 +334,7 @@ public class GathererSets implements Iterable<DownloadJob> {
|
||||||
canDownload = false;
|
canDownload = false;
|
||||||
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
if (exp != null && exp.getReleaseDate().before(compareDate)) {
|
||||||
canDownload = true;
|
canDownload = true;
|
||||||
jobs.add(generateDownloadJob(symbol, "M", "S"));
|
jobs.add(generateDownloadJob(symbol, "M", "special"));
|
||||||
}
|
}
|
||||||
CheckSearchResult(symbol, exp, canDownload, false, false, false, true);
|
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) {
|
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)) {
|
if (codeReplacements.containsKey(set)) {
|
||||||
set = codeReplacements.get(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);
|
return new DownloadJob(set + '-' + rarity, url, toFile(dst), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ public class GathererSymbols implements Iterable<DownloadJob> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String symbol = sym.replaceAll("/", "");
|
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)
|
// 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
|
// TODO: comment and try download without workaround, keep fix for symbols with "Resource not found" error
|
||||||
|
|
|
||||||
|
|
@ -478,8 +478,8 @@ public enum GrabbagImageSource implements CardImageSource {
|
||||||
|
|
||||||
// Emblems
|
// Emblems
|
||||||
singleLinks.put("SWS/Emblem Obi-Wan Kenobi", "Qyc10aT.png");
|
singleLinks.put("SWS/Emblem Obi-Wan Kenobi", "Qyc10aT.png");
|
||||||
singleLinks.put("SWS/Aurra Sing", "BLWbVJC.png");
|
singleLinks.put("SWS/Emblem Aurra Sing", "BLWbVJC.png");
|
||||||
singleLinks.put("SWS/Yoda", "zH0sYxg.png");
|
singleLinks.put("SWS/Emblem Yoda", "zH0sYxg.png");
|
||||||
singleLinks.put("SWS/Emblem Luke Skywalker", "kHELZDJ.jpeg");
|
singleLinks.put("SWS/Emblem Luke Skywalker", "kHELZDJ.jpeg");
|
||||||
|
|
||||||
// Tokens
|
// Tokens
|
||||||
|
|
|
||||||
|
|
@ -590,6 +590,7 @@ public class ScryfallImageSupportCards {
|
||||||
add("FCA"); // Final Fantasy: Through the Ages
|
add("FCA"); // Final Fantasy: Through the Ages
|
||||||
add("EOE"); // Edge of Eternities
|
add("EOE"); // Edge of Eternities
|
||||||
add("EOC"); // Edge of Eternities Commander
|
add("EOC"); // Edge of Eternities Commander
|
||||||
|
add("EOS"); // Edge of Eternities: Stellar Sights
|
||||||
add("SPE"); // Marvel's Spider-Man Eternal
|
add("SPE"); // Marvel's Spider-Man Eternal
|
||||||
add("TLA"); // Avatar: The Last Airbender
|
add("TLA"); // Avatar: The Last Airbender
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -832,12 +832,18 @@ public class ScryfallImageSupportTokens {
|
||||||
put("SLD/Food/3", "https://api.scryfall.com/cards/sld/2011?format=image");
|
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/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/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/Goblin", "https://api.scryfall.com/cards/sld/219?format=image");
|
||||||
put("SLD/Hydra", "https://api.scryfall.com/cards/sld/1334?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/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/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/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/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/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/1", "https://api.scryfall.com/cards/sld/1341?format=image");
|
||||||
put("SLD/Spirit/2", "https://api.scryfall.com/cards/sld/1852?format=image");
|
put("SLD/Spirit/2", "https://api.scryfall.com/cards/sld/1852?format=image");
|
||||||
|
|
@ -846,6 +852,8 @@ public class ScryfallImageSupportTokens {
|
||||||
put("SLD/Treasure/2", "https://api.scryfall.com/cards/sld/1736/en?format=image");
|
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/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/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/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/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");
|
put("SLD/Walker/3", "https://api.scryfall.com/cards/sld/150/en?format=image");
|
||||||
|
|
@ -2172,23 +2180,16 @@ public class ScryfallImageSupportTokens {
|
||||||
put("WOE/Young Hero", "https://api.scryfall.com/cards/twoe/16/en?format=image");
|
put("WOE/Young Hero", "https://api.scryfall.com/cards/twoe/16/en?format=image");
|
||||||
|
|
||||||
// WOC
|
// 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", "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/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/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/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/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/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/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/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/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/Virtuous", "https://api.scryfall.com/cards/twoc/2/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");
|
|
||||||
|
|
||||||
// WHO
|
// WHO
|
||||||
put("WHO/Alien", "https://api.scryfall.com/cards/twho/2?format=image");
|
put("WHO/Alien", "https://api.scryfall.com/cards/twho/2?format=image");
|
||||||
|
|
@ -2553,7 +2554,8 @@ public class ScryfallImageSupportTokens {
|
||||||
put("DSK/Primo, the Indivisible", "https://api.scryfall.com/cards/tdsk/14?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/Shard", "https://api.scryfall.com/cards/tdsk/2?format=image");
|
||||||
put("DSK/Spider", "https://api.scryfall.com/cards/tdsk/12?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");
|
put("DSK/Treasure", "https://api.scryfall.com/cards/tdsk/15?format=image");
|
||||||
|
|
||||||
// DSC
|
// DSC
|
||||||
|
|
@ -2783,6 +2785,36 @@ public class ScryfallImageSupportTokens {
|
||||||
put("FIC/The Blackjack", "https://api.scryfall.com/cards/tfic/8/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");
|
put("FIC/Clue", "https://api.scryfall.com/cards/tfic/9/en?format=image");
|
||||||
|
|
||||||
|
// EOE
|
||||||
|
put("EOE/Drone", "https://api.scryfall.com/cards/teoe/3?format=image");
|
||||||
|
put("EOE/Human Soldier", "https://api.scryfall.com/cards/teoe/2?format=image");
|
||||||
|
put("EOE/Lander/1", "https://api.scryfall.com/cards/teoe/4?format=image");
|
||||||
|
put("EOE/Lander/2", "https://api.scryfall.com/cards/teoe/5?format=image");
|
||||||
|
put("EOE/Lander/3", "https://api.scryfall.com/cards/teoe/6?format=image");
|
||||||
|
put("EOE/Lander/4", "https://api.scryfall.com/cards/teoe/7?format=image");
|
||||||
|
put("EOE/Lander/5", "https://api.scryfall.com/cards/teoe/8?format=image");
|
||||||
|
put("EOE/Munitions", "https://api.scryfall.com/cards/teoe/9?format=image");
|
||||||
|
put("EOE/Robot", "https://api.scryfall.com/cards/teoe/10?format=image");
|
||||||
|
put("EOE/Sliver", "https://api.scryfall.com/cards/teoe/1?format=image");
|
||||||
|
|
||||||
|
// EOC
|
||||||
|
put("EOC/Beast/1", "https://api.scryfall.com/cards/teoc/5/en?format=image");
|
||||||
|
put("EOC/Beast/2", "https://api.scryfall.com/cards/teoc/6/en?format=image");
|
||||||
|
put("EOC/Bird", "https://api.scryfall.com/cards/teoc/3/en?format=image");
|
||||||
|
put("EOC/Clue", "https://api.scryfall.com/cards/teoc/10/en?format=image");
|
||||||
|
put("EOC/Elemental/1", "https://api.scryfall.com/cards/teoc/7/en?format=image");
|
||||||
|
put("EOC/Elemental/2", "https://api.scryfall.com/cards/teoc/8/en?format=image");
|
||||||
|
put("EOC/Gnome", "https://api.scryfall.com/cards/teoc/11/en?format=image");
|
||||||
|
put("EOC/Golem/1", "https://api.scryfall.com/cards/teoc/12/en?format=image");
|
||||||
|
put("EOC/Golem/2", "https://api.scryfall.com/cards/teoc/13/en?format=image");
|
||||||
|
put("EOC/Golem/3", "https://api.scryfall.com/cards/teoc/14/en?format=image");
|
||||||
|
put("EOC/Incubator", "https://api.scryfall.com/cards/teoc/15/en?format=image&face=front");
|
||||||
|
put("EOC/Insect", "https://api.scryfall.com/cards/teoc/4/en?format=image");
|
||||||
|
put("EOC/Pest", "https://api.scryfall.com/cards/teoc/9/en?format=image");
|
||||||
|
put("EOC/Phyrexian", "https://api.scryfall.com/cards/teoc/15/en?format=image&face=back");
|
||||||
|
put("EOC/Shapeshifter", "https://api.scryfall.com/cards/teoc/2/en?format=image");
|
||||||
|
put("EOC/Thopter", "https://api.scryfall.com/cards/teoc/16/en?format=image");
|
||||||
|
|
||||||
// JVC
|
// JVC
|
||||||
put("JVC/Elemental Shaman", "https://api.scryfall.com/cards/tjvc/4?format=image");
|
put("JVC/Elemental Shaman", "https://api.scryfall.com/cards/tjvc/4?format=image");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNeedCancel() {
|
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) {
|
private void setNeedCancel(boolean needCancel) {
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,6 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -723,7 +723,7 @@ public class SessionImpl implements Session {
|
||||||
public Optional<UUID> getRoomChatId(UUID roomId) {
|
public Optional<UUID> getRoomChatId(UUID roomId) {
|
||||||
try {
|
try {
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
return Optional.of(server.chatFindByRoom(roomId));
|
return Optional.ofNullable(server.chatFindByRoom(roomId));
|
||||||
}
|
}
|
||||||
} catch (MageException ex) {
|
} catch (MageException ex) {
|
||||||
handleMageException(ex);
|
handleMageException(ex);
|
||||||
|
|
@ -735,7 +735,7 @@ public class SessionImpl implements Session {
|
||||||
public Optional<UUID> getTableChatId(UUID tableId) {
|
public Optional<UUID> getTableChatId(UUID tableId) {
|
||||||
try {
|
try {
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
return Optional.of(server.chatFindByTable(tableId));
|
return Optional.ofNullable(server.chatFindByTable(tableId));
|
||||||
}
|
}
|
||||||
} catch (MageException ex) {
|
} catch (MageException ex) {
|
||||||
handleMageException(ex);
|
handleMageException(ex);
|
||||||
|
|
@ -747,7 +747,7 @@ public class SessionImpl implements Session {
|
||||||
public Optional<UUID> getGameChatId(UUID gameId) {
|
public Optional<UUID> getGameChatId(UUID gameId) {
|
||||||
try {
|
try {
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
return Optional.of(server.chatFindByGame(gameId));
|
return Optional.ofNullable(server.chatFindByGame(gameId));
|
||||||
}
|
}
|
||||||
} catch (MageException ex) {
|
} catch (MageException ex) {
|
||||||
handleMageException(ex);
|
handleMageException(ex);
|
||||||
|
|
@ -761,7 +761,7 @@ public class SessionImpl implements Session {
|
||||||
public Optional<TableView> getTable(UUID roomId, UUID tableId) {
|
public Optional<TableView> getTable(UUID roomId, UUID tableId) {
|
||||||
try {
|
try {
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
return Optional.of(server.roomGetTableById(roomId, tableId));
|
return Optional.ofNullable(server.roomGetTableById(roomId, tableId));
|
||||||
}
|
}
|
||||||
} catch (MageException ex) {
|
} catch (MageException ex) {
|
||||||
handleMageException(ex);
|
handleMageException(ex);
|
||||||
|
|
@ -905,7 +905,7 @@ public class SessionImpl implements Session {
|
||||||
public Optional<UUID> getTournamentChatId(UUID tournamentId) {
|
public Optional<UUID> getTournamentChatId(UUID tournamentId) {
|
||||||
try {
|
try {
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
return Optional.of(server.chatFindByTournament(tournamentId));
|
return Optional.ofNullable(server.chatFindByTournament(tournamentId));
|
||||||
}
|
}
|
||||||
} catch (MageException ex) {
|
} catch (MageException ex) {
|
||||||
handleMageException(ex);
|
handleMageException(ex);
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,11 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public class AmountTestableResult extends BaseTestableResult {
|
public class AmountTestableResult extends BaseTestableResult {
|
||||||
|
|
||||||
int amount = 0;
|
Integer amount = null;
|
||||||
|
|
||||||
|
boolean aiAssertEnabled = false;
|
||||||
|
int aiAssertMinAmount = 0;
|
||||||
|
int aiAssertMaxAmount = 0;
|
||||||
|
|
||||||
public void onFinish(String resDebugSource, boolean status, List<String> info, int amount) {
|
public void onFinish(String resDebugSource, boolean status, List<String> info, int amount) {
|
||||||
this.onFinish(resDebugSource, status, info);
|
this.onFinish(resDebugSource, status, info);
|
||||||
|
|
@ -18,12 +22,38 @@ public class AmountTestableResult extends BaseTestableResult {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getResAssert() {
|
public String getResAssert() {
|
||||||
return null; // TODO: implement
|
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
|
@Override
|
||||||
public void onClear() {
|
public void onClear() {
|
||||||
super.onClear();
|
super.onClear();
|
||||||
this.amount = 0;
|
this.amount = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,15 @@ class AnnounceXTestableDialog extends BaseTestableDialog {
|
||||||
this.max = max;
|
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
|
@Override
|
||||||
public void 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;
|
Player choosingPlayer = this.isYou ? player : opponent;
|
||||||
|
|
@ -51,17 +60,17 @@ class AnnounceXTestableDialog extends BaseTestableDialog {
|
||||||
List<Boolean> isManas = Arrays.asList(false, true);
|
List<Boolean> isManas = Arrays.asList(false, true);
|
||||||
for (boolean isYou : isYous) {
|
for (boolean isYou : isYous) {
|
||||||
for (boolean isMana : isManas) {
|
for (boolean isMana : isManas) {
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 0));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 0).aiMustChoose(0, 0));
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 1));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 1).aiMustChoose(0, 1));
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 3));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 3).aiMustChoose(0, 3));
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 50));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 50).aiMustChoose(0, 50));
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 500));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 0, 500).aiMustChoose(0, 500));
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 1));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 1).aiMustChoose(1, 1));
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 3));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 3).aiMustChoose(1, 3));
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 50));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 1, 50).aiMustChoose(1, 50));
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 3));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 3).aiMustChoose(3, 3));
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 10));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 3, 10).aiMustChoose(3, 10));
|
||||||
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 10, 10));
|
runner.registerDialog(new AnnounceXTestableDialog(isYou, isMana, 10, 10).aiMustChoose(10, 10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,15 @@ class GetAmountTestableDialog extends BaseTestableDialog {
|
||||||
this.max = max;
|
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
|
@Override
|
||||||
public void 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;
|
Player choosingPlayer = this.isYou ? player : opponent;
|
||||||
|
|
@ -51,17 +60,20 @@ class GetAmountTestableDialog extends BaseTestableDialog {
|
||||||
static public void register(TestableDialogsRunner runner) {
|
static public void register(TestableDialogsRunner runner) {
|
||||||
List<Boolean> isYous = Arrays.asList(false, true);
|
List<Boolean> isYous = Arrays.asList(false, true);
|
||||||
for (boolean isYou : isYous) {
|
for (boolean isYou : isYous) {
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 0));
|
// TODO: add good and bad effects:
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 1));
|
// - on good: choose random big value
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 3));
|
// - on bad: choose lower value
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 50));
|
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 0).aiMustChoose(0, 0));
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 500));
|
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 1).aiMustChoose(0, 1));
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 1));
|
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 3).aiMustChoose(0, 3));
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 3));
|
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 50).aiMustChoose(0, 50));
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 50));
|
runner.registerDialog(new GetAmountTestableDialog(isYou, 0, 500).aiMustChoose(0, 500));
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 3));
|
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 1).aiMustChoose(1, 1));
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 3, 10));
|
runner.registerDialog(new GetAmountTestableDialog(isYou, 1, 3).aiMustChoose(1, 3));
|
||||||
runner.registerDialog(new GetAmountTestableDialog(isYou, 10, 10));
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,9 @@ class GetMultiAmountTestableDialog extends BaseTestableDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
private GetMultiAmountTestableDialog aiMustChoose(Integer... needValues) {
|
private GetMultiAmountTestableDialog aiMustChoose(Integer... needValues) {
|
||||||
// TODO: AI use default distribution (min possible values), improve someday
|
// TODO: AI use default distribution:
|
||||||
|
// - bad effect: min possible values
|
||||||
|
// - good effect: max possible and distributed values
|
||||||
MultiAmountTestableResult res = ((MultiAmountTestableResult) this.getResult());
|
MultiAmountTestableResult res = ((MultiAmountTestableResult) this.getResult());
|
||||||
res.aiAssertEnabled = true;
|
res.aiAssertEnabled = true;
|
||||||
res.aiAssertValues = Arrays.stream(needValues).collect(Collectors.toList());
|
res.aiAssertValues = Arrays.stream(needValues).collect(Collectors.toList());
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,12 @@ public class ChatMessage implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MessageType {
|
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 {
|
public enum SoundToPlay {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ import mage.abilities.Ability;
|
||||||
import mage.abilities.common.CanBeYourCommanderAbility;
|
import mage.abilities.common.CanBeYourCommanderAbility;
|
||||||
import mage.abilities.common.CommanderChooseColorAbility;
|
import mage.abilities.common.CommanderChooseColorAbility;
|
||||||
import mage.abilities.keyword.CompanionAbility;
|
import mage.abilities.keyword.CompanionAbility;
|
||||||
|
import mage.abilities.keyword.StationLevelAbility;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.decks.Constructed;
|
import mage.cards.decks.Constructed;
|
||||||
import mage.cards.decks.Deck;
|
import mage.cards.decks.Deck;
|
||||||
import mage.cards.decks.DeckValidatorErrorType;
|
import mage.cards.decks.DeckValidatorErrorType;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
import mage.filter.FilterMana;
|
import mage.filter.FilterMana;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
import mage.util.ManaUtil;
|
import mage.util.ManaUtil;
|
||||||
|
|
@ -51,9 +53,19 @@ public abstract class AbstractCommander extends Constructed {
|
||||||
protected abstract boolean checkBanned(Map<String, Integer> counts);
|
protected abstract boolean checkBanned(Map<String, Integer> counts);
|
||||||
|
|
||||||
protected boolean checkCommander(Card commander, Set<Card> commanders) {
|
protected boolean checkCommander(Card commander, Set<Card> commanders) {
|
||||||
return commander.hasCardTypeForDeckbuilding(CardType.CREATURE) && commander.isLegendary()
|
if (commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance())) {
|
||||||
|| commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance())
|
return true;
|
||||||
|| (validators.stream().anyMatch(validator -> validator.specialCheck(commander)) && commanders.size() == 2);
|
}
|
||||||
|
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) {
|
protected boolean checkPartners(Set<Card> commanders) {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ public class CanadianHighlander extends Constructed {
|
||||||
static {
|
static {
|
||||||
pointMap.put("Ancestral Recall", 8);
|
pointMap.put("Ancestral Recall", 8);
|
||||||
pointMap.put("Ancient Tomb", 1);
|
pointMap.put("Ancient Tomb", 1);
|
||||||
|
pointMap.put("Balance", 1);
|
||||||
pointMap.put("Black Lotus", 7);
|
pointMap.put("Black Lotus", 7);
|
||||||
pointMap.put("Demonic Tutor", 3);
|
pointMap.put("Demonic Tutor", 3);
|
||||||
pointMap.put("Dig Through Time", 1);
|
pointMap.put("Dig Through Time", 1);
|
||||||
|
|
@ -30,19 +31,22 @@ public class CanadianHighlander extends Constructed {
|
||||||
pointMap.put("Mana Crypt", 5);
|
pointMap.put("Mana Crypt", 5);
|
||||||
pointMap.put("Mana Drain", 1);
|
pointMap.put("Mana Drain", 1);
|
||||||
pointMap.put("Mana Vault", 1);
|
pointMap.put("Mana Vault", 1);
|
||||||
|
pointMap.put("Merchant Scroll", 1);
|
||||||
|
pointMap.put("Minsc & Boo, Timeless Heroes", 1);
|
||||||
pointMap.put("Mox Emerald", 3);
|
pointMap.put("Mox Emerald", 3);
|
||||||
pointMap.put("Mox Jet", 3);
|
pointMap.put("Mox Jet", 3);
|
||||||
pointMap.put("Mox Pearl", 3);
|
pointMap.put("Mox Pearl", 3);
|
||||||
pointMap.put("Mox Ruby", 3);
|
pointMap.put("Mox Ruby", 3);
|
||||||
pointMap.put("Mox Sapphire", 3);
|
pointMap.put("Mox Sapphire", 3);
|
||||||
pointMap.put("Mystical Tutor", 1);
|
pointMap.put("Mystical Tutor", 1);
|
||||||
|
pointMap.put("Nadu, Winged Wisdom", 1);
|
||||||
pointMap.put("Natural Order", 1);
|
pointMap.put("Natural Order", 1);
|
||||||
pointMap.put("Sol Ring", 4);
|
pointMap.put("Psychic Frog", 1);
|
||||||
pointMap.put("Spellseeker", 1);
|
pointMap.put("Reanimate", 1);
|
||||||
pointMap.put("Strip Mine", 2);
|
pointMap.put("Sol Ring", 3);
|
||||||
pointMap.put("Survival of the Fittest", 1);
|
pointMap.put("Strip Mine", 1);
|
||||||
pointMap.put("Tainted Pact", 1);
|
pointMap.put("Tainted Pact", 1);
|
||||||
pointMap.put("Thassa's Oracle", 7);
|
pointMap.put("Thassa's Oracle", 6);
|
||||||
pointMap.put("Time Vault", 7);
|
pointMap.put("Time Vault", 7);
|
||||||
pointMap.put("Time Walk", 6);
|
pointMap.put("Time Walk", 6);
|
||||||
pointMap.put("Tinker", 3);
|
pointMap.put("Tinker", 3);
|
||||||
|
|
@ -50,8 +54,11 @@ public class CanadianHighlander extends Constructed {
|
||||||
pointMap.put("Treasure Cruise", 1);
|
pointMap.put("Treasure Cruise", 1);
|
||||||
pointMap.put("True-Name Nemesis", 1);
|
pointMap.put("True-Name Nemesis", 1);
|
||||||
pointMap.put("Underworld Breach", 3);
|
pointMap.put("Underworld Breach", 3);
|
||||||
|
pointMap.put("Urza's Saga", 1);
|
||||||
pointMap.put("Vampiric Tutor", 2);
|
pointMap.put("Vampiric Tutor", 2);
|
||||||
|
pointMap.put("White Plume Adventurer", 1);
|
||||||
pointMap.put("Wishclaw Talisman", 1);
|
pointMap.put("Wishclaw Talisman", 1);
|
||||||
|
pointMap.put("Wrenn and Six", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CanadianHighlander() {
|
public CanadianHighlander() {
|
||||||
|
|
|
||||||
|
|
@ -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("Agent of Treachery");
|
||||||
banned.add("Brainstorm");
|
banned.add("Brainstorm");
|
||||||
banned.add("Channel");
|
banned.add("Channel");
|
||||||
banned.add("Counterspell");
|
|
||||||
banned.add("Dark Ritual");
|
banned.add("Dark Ritual");
|
||||||
banned.add("Demonic Tutor");
|
banned.add("Demonic Tutor");
|
||||||
banned.add("Fires of Invention");
|
banned.add("Fires of Invention");
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,14 @@ public class Standard extends Constructed {
|
||||||
super("Constructed - Standard");
|
super("Constructed - Standard");
|
||||||
|
|
||||||
setCodes.addAll(makeLegalSets());
|
setCodes.addAll(makeLegalSets());
|
||||||
|
|
||||||
|
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() {
|
static List<String> makeLegalSets() {
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,13 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
||||||
this.actionCache = player.actionCache;
|
this.actionCache = player.actionCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change simulation timeout - used for AI stability tests only
|
||||||
|
*/
|
||||||
|
public void setMaxThinkTimeSecs(int maxThinkTimeSecs) {
|
||||||
|
this.maxThinkTimeSecs = maxThinkTimeSecs;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComputerPlayer6 copy() {
|
public ComputerPlayer6 copy() {
|
||||||
return new ComputerPlayer6(this);
|
return new ComputerPlayer6(this);
|
||||||
|
|
@ -206,10 +213,8 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
||||||
logger.trace("Add Action [" + depth + "] " + node.getAbilities().toString() + " a: " + alpha + " b: " + beta);
|
logger.trace("Add Action [" + depth + "] " + node.getAbilities().toString() + " a: " + alpha + " b: " + beta);
|
||||||
}
|
}
|
||||||
Game game = node.getGame();
|
Game game = node.getGame();
|
||||||
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS
|
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) {
|
||||||
&& Thread.interrupted()) {
|
logger.debug("AI game sim interrupted by timeout");
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
logger.debug("interrupted");
|
|
||||||
return GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
|
return GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
|
||||||
}
|
}
|
||||||
// Condition to stop deeper simulation
|
// Condition to stop deeper simulation
|
||||||
|
|
@ -431,6 +436,8 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
protected Integer addActionsTimed() {
|
protected Integer addActionsTimed() {
|
||||||
|
// TODO: all actions added and calculated one by one,
|
||||||
|
// multithreading do not supported here
|
||||||
// run new game simulation in parallel thread
|
// run new game simulation in parallel thread
|
||||||
FutureTask<Integer> task = new FutureTask<>(() -> addActions(root, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE));
|
FutureTask<Integer> task = new FutureTask<>(() -> addActions(root, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE));
|
||||||
threadPoolSimulations.execute(task);
|
threadPoolSimulations.execute(task);
|
||||||
|
|
@ -446,15 +453,23 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
||||||
}
|
}
|
||||||
} catch (TimeoutException | InterruptedException e) {
|
} catch (TimeoutException | InterruptedException e) {
|
||||||
// AI thinks too long
|
// 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);
|
task.cancel(true);
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
// game error
|
// 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);
|
task.cancel(true);
|
||||||
// real games: must catch and log
|
// real games: must catch and log
|
||||||
// unit tests: must raise again for fast fail
|
// 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);
|
throw new IllegalStateException("One of the simulated games raise the error: " + e, e);
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
@ -466,11 +481,33 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
||||||
return 0;
|
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) {
|
protected int simulatePriority(SimulationNode2 node, Game game, int depth, int alpha, int beta) {
|
||||||
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS
|
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) {
|
||||||
&& Thread.interrupted()) {
|
logger.debug("AI game sim interrupted by timeout");
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
logger.info("interrupted");
|
|
||||||
return GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
|
return GameStateEvaluator2.evaluate(playerId, game).getTotalScore();
|
||||||
}
|
}
|
||||||
node.setGameValue(game.getState().getValue(true).hashCode());
|
node.setGameValue(game.getState().getValue(true).hashCode());
|
||||||
|
|
@ -498,9 +535,7 @@ public class ComputerPlayer6 extends ComputerPlayer {
|
||||||
int bestValSubNodes = Integer.MIN_VALUE;
|
int bestValSubNodes = Integer.MIN_VALUE;
|
||||||
for (Ability action : allActions) {
|
for (Ability action : allActions) {
|
||||||
actionNumber++;
|
actionNumber++;
|
||||||
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS
|
if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) {
|
||||||
&& Thread.interrupted()) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
logger.info("Sim Prio [" + depth + "] -- interrupted");
|
logger.info("Sim Prio [" + depth + "] -- interrupted");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,8 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Next Action exists!");
|
logger.debug("Next Action exists!");
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
protected static final int PASSIVITY_PENALTY = 5; // Penalty value for doing nothing if some actions are available
|
protected static final int PASSIVITY_PENALTY = 5; // Penalty value for doing nothing if some actions are available
|
||||||
|
|
||||||
// debug only: set TRUE to debug simulation's code/games (on false sim thread will be stopped after few secs by timeout)
|
// debug only: set TRUE to debug simulation's code/games (on false sim thread will be stopped after few secs by timeout)
|
||||||
protected static final boolean COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = true; // DebugUtil.AI_ENABLE_DEBUG_MODE;
|
public static final boolean COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS = false; // DebugUtil.AI_ENABLE_DEBUG_MODE;
|
||||||
|
|
||||||
// AI agents uses game simulation thread for all calcs and it's high CPU consumption
|
// AI agents uses game simulation thread for all calcs and it's high CPU consumption
|
||||||
// More AI threads - more parallel AI games can be calculate
|
// More AI threads - more parallel AI games can be calculate
|
||||||
|
|
@ -64,7 +64,7 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
// * use yours CPU cores for best performance
|
// * use yours CPU cores for best performance
|
||||||
// TODO: add server config to control max AI threads (with CPU cores by default)
|
// TODO: add server config to control max AI threads (with CPU cores by default)
|
||||||
// TODO: rework AI implementation to use multiple sims calculation instead one by one
|
// TODO: rework AI implementation to use multiple sims calculation instead one by one
|
||||||
final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 1;//DebugUtil.AI_ENABLE_DEBUG_MODE ? 1 : 5;
|
final static int COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 5;//DebugUtil.AI_ENABLE_DEBUG_MODE ? 1 : 5;
|
||||||
|
|
||||||
|
|
||||||
// remember picked cards for better draft choices
|
// remember picked cards for better draft choices
|
||||||
|
|
@ -104,7 +104,7 @@ public class ComputerPlayer extends PlayerImpl {
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseMulligan(Game game) {
|
public boolean chooseMulligan(Game game) {
|
||||||
if (hand.size() < 6
|
if (hand.size() < 6
|
||||||
|| isTestsMode() // ignore mulligan in tests
|
|| isTestMode() // ignore mulligan in tests
|
||||||
|| game.getClass().getName().contains("Momir") // ignore mulligan in Momir games
|
|| game.getClass().getName().contains("Momir") // ignore mulligan in Momir games
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,8 @@ public class PossibleTargetsSelector {
|
||||||
|
|
||||||
public void findNewTargets(Set<UUID> fromTargetsList) {
|
public void findNewTargets(Set<UUID> fromTargetsList) {
|
||||||
// collect new valid targets
|
// collect new valid targets
|
||||||
List<MageItem> found = target.possibleTargets(abilityControllerId, source, game).stream()
|
List<MageItem> found = target.possibleTargets(abilityControllerId, source, game, fromTargetsList).stream()
|
||||||
.filter(id -> !target.contains(id))
|
.filter(id -> !target.contains(id))
|
||||||
.filter(id -> fromTargetsList == null || fromTargetsList.contains(id))
|
|
||||||
.filter(id -> target.canTarget(abilityControllerId, id, source, game))
|
.filter(id -> target.canTarget(abilityControllerId, id, source, game))
|
||||||
.map(id -> {
|
.map(id -> {
|
||||||
Player player = game.getPlayer(id);
|
Player player = game.getPlayer(id);
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ public class ComputerPlayerMCTS extends ComputerPlayer {
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
// real games: must catch and log
|
// real games: must catch and log
|
||||||
// unit tests: must raise again for fast fail
|
// 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);
|
throw new IllegalStateException("One of the simulated games raise the error: " + e, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,7 @@
|
||||||
<deckType name="Constructed - Old School 93/94 - EC Rules" jar="mage-deck-constructed.jar" className="mage.deck.OldSchool9394EC"/>
|
<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 - 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" 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 - 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 - 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"/>
|
<deckType name="Variant Magic - MTGO 1v1 Commander" jar="mage-deck-constructed.jar" className="mage.deck.MTGO1v1Commander"/>
|
||||||
|
|
|
||||||
|
|
@ -192,11 +192,6 @@
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.commons</groupId>
|
|
||||||
<artifactId>commons-lang3</artifactId>
|
|
||||||
<version>3.11</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.xml.bind</groupId>
|
<groupId>javax.xml.bind</groupId>
|
||||||
<artifactId>jaxb-api</artifactId>
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
|
@ -213,12 +208,12 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-compress</artifactId>
|
<artifactId>commons-compress</artifactId>
|
||||||
<version>[1.19,)</version>
|
<version>1.27.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.shiro</groupId>
|
<groupId>org.apache.shiro</groupId>
|
||||||
<artifactId>shiro-core</artifactId>
|
<artifactId>shiro-core</artifactId>
|
||||||
<version>1.8.0</version>
|
<version>1.13.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.mail</groupId>
|
<groupId>javax.mail</groupId>
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,7 @@
|
||||||
<deckType name="Constructed - Old School 93/94 - EC Rules" jar="mage-deck-constructed-${project.version}.jar" className="mage.deck.OldSchool9394EC"/>
|
<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 - 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" 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 - 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 - 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"/>
|
<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.cards.repository.CardRepository;
|
||||||
import mage.constants.Constants;
|
import mage.constants.Constants;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.game.Table;
|
||||||
|
import mage.game.tournament.Tournament;
|
||||||
import mage.server.game.GameController;
|
import mage.server.game.GameController;
|
||||||
import mage.server.managers.ChatManager;
|
import mage.server.managers.ChatManager;
|
||||||
import mage.server.managers.ManagerFactory;
|
import mage.server.managers.ManagerFactory;
|
||||||
|
|
@ -40,11 +42,39 @@ public class ChatManagerImpl implements ChatManager {
|
||||||
this.managerFactory = managerFactory;
|
this.managerFactory = managerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@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);
|
ChatSession chatSession = new ChatSession(managerFactory, info);
|
||||||
chatSessions.put(chatSession.getChatId(), chatSession);
|
chatSessions.put(chatSession.getChatId(), chatSession);
|
||||||
return chatSession.getChatId();
|
return chatSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -94,7 +124,7 @@ public class ChatManagerImpl implements ChatManager {
|
||||||
ChatSession chatSession = chatSessions.get(chatId);
|
ChatSession chatSession = chatSessions.get(chatId);
|
||||||
Optional<User> user = managerFactory.userManager().getUserByName(userName);
|
Optional<User> user = managerFactory.userManager().getUserByName(userName);
|
||||||
if (chatSession != null) {
|
if (chatSession != null) {
|
||||||
// special commads
|
// special commands
|
||||||
if (message.startsWith("\\") || message.startsWith("/")) {
|
if (message.startsWith("\\") || message.startsWith("/")) {
|
||||||
if (user.isPresent()) {
|
if (user.isPresent()) {
|
||||||
if (!performUserCommand(user.get(), message, chatId, false)) {
|
if (!performUserCommand(user.get(), message, chatId, false)) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package mage.server;
|
package mage.server;
|
||||||
|
|
||||||
|
import mage.collectors.DataCollectorServices;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.game.Table;
|
||||||
|
import mage.game.tournament.Tournament;
|
||||||
import mage.interfaces.callback.ClientCallback;
|
import mage.interfaces.callback.ClientCallback;
|
||||||
import mage.interfaces.callback.ClientCallbackMethod;
|
import mage.interfaces.callback.ClientCallbackMethod;
|
||||||
import mage.server.managers.ManagerFactory;
|
import mage.server.managers.ManagerFactory;
|
||||||
|
|
@ -27,6 +30,13 @@ public class ChatSession {
|
||||||
private final ManagerFactory managerFactory;
|
private final ManagerFactory managerFactory;
|
||||||
private final ReadWriteLock lock = new ReentrantReadWriteLock(); // TODO: no needs due ConcurrentHashMap usage?
|
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 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 Set<UUID> usersHistory = new HashSet<>(); // all users that was here (need for system messages like connection problem)
|
||||||
private final UUID chatId;
|
private final UUID chatId;
|
||||||
|
|
@ -40,6 +50,26 @@ public class ChatSession {
|
||||||
this.info = info;
|
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) {
|
public void join(UUID userId) {
|
||||||
managerFactory.userManager().getUser(userId).ifPresent(user -> {
|
managerFactory.userManager().getUser(userId).ifPresent(user -> {
|
||||||
if (!users.containsKey(userId)) {
|
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: is it freeze on someone's connection fail/freeze with play multiple games/chats/lobby?
|
||||||
// TODO: send messages in another thread?!
|
// TODO: send messages in another thread?!
|
||||||
if (!message.isEmpty()) {
|
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<>();
|
Set<UUID> clientsToRemove = new HashSet<>();
|
||||||
ClientCallback clientCallback = new ClientCallback(ClientCallbackMethod.CHATMESSAGE, chatId,
|
ClientCallback clientCallback = new ClientCallback(ClientCallbackMethod.CHATMESSAGE, chatId, chatMessage);
|
||||||
new ChatMessage(userName, message, (withTime ? new Date() : null), game, color, messageType, soundToPlay));
|
|
||||||
List<UUID> chatUserIds = new ArrayList<>();
|
List<UUID> chatUserIds = new ArrayList<>();
|
||||||
final Lock r = lock.readLock();
|
final Lock r = lock.readLock();
|
||||||
r.lock();
|
r.lock();
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import mage.cards.decks.DeckCardLists;
|
||||||
import mage.cards.decks.DeckValidatorFactory;
|
import mage.cards.decks.DeckValidatorFactory;
|
||||||
import mage.cards.repository.CardRepository;
|
import mage.cards.repository.CardRepository;
|
||||||
import mage.cards.repository.ExpansionRepository;
|
import mage.cards.repository.ExpansionRepository;
|
||||||
|
import mage.collectors.DataCollectorServices;
|
||||||
import mage.constants.Constants;
|
import mage.constants.Constants;
|
||||||
import mage.constants.ManaType;
|
import mage.constants.ManaType;
|
||||||
import mage.constants.PlayerAction;
|
import mage.constants.PlayerAction;
|
||||||
|
|
@ -29,6 +30,7 @@ import mage.server.managers.ManagerFactory;
|
||||||
import mage.server.services.impl.FeedbackServiceImpl;
|
import mage.server.services.impl.FeedbackServiceImpl;
|
||||||
import mage.server.tournament.TournamentFactory;
|
import mage.server.tournament.TournamentFactory;
|
||||||
import mage.server.util.ServerMessagesUtil;
|
import mage.server.util.ServerMessagesUtil;
|
||||||
|
import mage.util.DebugUtil;
|
||||||
import mage.utils.*;
|
import mage.utils.*;
|
||||||
import mage.view.*;
|
import mage.view.*;
|
||||||
import mage.view.ChatMessage.MessageColor;
|
import mage.view.ChatMessage.MessageColor;
|
||||||
|
|
@ -68,6 +70,13 @@ public class MageServerImpl implements MageServer {
|
||||||
this.detailsMode = detailsMode;
|
this.detailsMode = detailsMode;
|
||||||
this.callExecutor = managerFactory.threadExecutor().getCallExecutor();
|
this.callExecutor = managerFactory.threadExecutor().getCallExecutor();
|
||||||
ServerMessagesUtil.instance.getMessages();
|
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
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public abstract class RoomImpl implements Room {
|
||||||
|
|
||||||
public RoomImpl(ChatManager chatManager) {
|
public RoomImpl(ChatManager chatManager) {
|
||||||
roomId = UUID.randomUUID();
|
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.apache.log4j.Logger;
|
||||||
import org.jboss.remoting.callback.InvokerCallbackHandler;
|
import org.jboss.remoting.callback.InvokerCallbackHandler;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
|
@ -29,7 +28,7 @@ public class SessionManagerImpl implements SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Session> getSession(@Nonnull String sessionId) {
|
public Optional<Session> getSession(String sessionId) {
|
||||||
return Optional.ofNullable(sessions.getOrDefault(sessionId, null));
|
return Optional.ofNullable(sessions.getOrDefault(sessionId, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,12 +179,12 @@ public class SessionManagerImpl implements SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValidSession(@Nonnull String sessionId) {
|
public boolean isValidSession(String sessionId) {
|
||||||
return sessions.containsKey(sessionId);
|
return sessions.containsKey(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<User> getUser(@Nonnull String sessionId) {
|
public Optional<User> getUser(String sessionId) {
|
||||||
Session session = sessions.get(sessionId);
|
Session session = sessions.get(sessionId);
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
return managerFactory.userManager().getUser(sessions.get(sessionId).getUserId());
|
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()),
|
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());
|
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();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ public class TableController {
|
||||||
}
|
}
|
||||||
table = new Table(roomId, options.getTournamentType(), options.getName(), controllerName, DeckValidatorFactory.instance.createDeckValidator(options.getMatchOptions().getDeckType()),
|
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());
|
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() {
|
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) {
|
public GameController(ManagerFactory managerFactory, Game game, ConcurrentMap<UUID, UUID> userPlayerMap, UUID tableId, UUID choosingPlayerId, GameOptions gameOptions) {
|
||||||
this.managerFactory = managerFactory;
|
this.managerFactory = managerFactory;
|
||||||
gameExecutor = managerFactory.threadExecutor().getGameExecutor();
|
this.gameExecutor = managerFactory.threadExecutor().getGameExecutor();
|
||||||
responseIdleTimeoutExecutor = managerFactory.threadExecutor().getTimeoutIdleExecutor();
|
this.responseIdleTimeoutExecutor = managerFactory.threadExecutor().getTimeoutIdleExecutor();
|
||||||
gameSessionId = UUID.randomUUID();
|
this.gameSessionId = UUID.randomUUID();
|
||||||
this.userPlayerMap = userPlayerMap;
|
this.userPlayerMap = userPlayerMap;
|
||||||
chatId = managerFactory.chatManager().createChatSession("Game " + game.getId());
|
this.chatId = managerFactory.chatManager().createGameChatSession(game);
|
||||||
this.userRequestingRollback = null;
|
this.userRequestingRollback = null;
|
||||||
this.game = game;
|
this.game = game;
|
||||||
this.game.setSaveGame(managerFactory.configSettings().isSaveGameActivated());
|
this.game.setSaveGame(managerFactory.configSettings().isSaveGameActivated());
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package mage.server.managers;
|
package mage.server.managers;
|
||||||
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.game.Table;
|
||||||
|
import mage.game.tournament.Tournament;
|
||||||
import mage.server.ChatSession;
|
import mage.server.ChatSession;
|
||||||
import mage.server.DisconnectReason;
|
import mage.server.DisconnectReason;
|
||||||
import mage.server.exceptions.UserNotFoundException;
|
|
||||||
import mage.view.ChatMessage;
|
import mage.view.ChatMessage;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -11,7 +12,13 @@ import java.util.UUID;
|
||||||
|
|
||||||
public interface ChatManager {
|
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);
|
void joinChat(UUID chatId, UUID userId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,11 @@ import mage.server.Session;
|
||||||
import mage.server.User;
|
import mage.server.User;
|
||||||
import org.jboss.remoting.callback.InvokerCallbackHandler;
|
import org.jboss.remoting.callback.InvokerCallbackHandler;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface SessionManager {
|
public interface SessionManager {
|
||||||
|
|
||||||
Optional<Session> getSession(@Nonnull String sessionId);
|
Optional<Session> getSession(String sessionId);
|
||||||
|
|
||||||
void createSession(String sessionId, InvokerCallbackHandler callbackHandler);
|
void createSession(String sessionId, InvokerCallbackHandler callbackHandler);
|
||||||
|
|
||||||
|
|
@ -37,9 +36,9 @@ public interface SessionManager {
|
||||||
|
|
||||||
boolean checkAdminAccess(String sessionId);
|
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);
|
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) {
|
public TournamentController(ManagerFactory managerFactory, Tournament tournament, ConcurrentMap<UUID, UUID> userPlayerMap, UUID tableId) {
|
||||||
this.managerFactory = managerFactory;
|
this.managerFactory = managerFactory;
|
||||||
this.userPlayerMap = userPlayerMap;
|
this.userPlayerMap = userPlayerMap;
|
||||||
chatId = managerFactory.chatManager().createChatSession("Tournament " + tournament.getId());
|
this.chatId = managerFactory.chatManager().createTourneyChatSession(tournament);
|
||||||
this.tournament = tournament;
|
this.tournament = tournament;
|
||||||
this.tableId = tableId;
|
this.tableId = tableId;
|
||||||
init();
|
init();
|
||||||
|
|
@ -261,9 +261,7 @@ public class TournamentController {
|
||||||
table.setState(TableState.STARTING);
|
table.setState(TableState.STARTING);
|
||||||
tableManager.startTournamentSubMatch(null, table.getId());
|
tableManager.startTournamentSubMatch(null, table.getId());
|
||||||
tableManager.getMatch(table.getId()).ifPresent(match -> {
|
tableManager.getMatch(table.getId()).ifPresent(match -> {
|
||||||
match.setTableId(tableId);
|
pair.setMatchAndTable(match, table.getId());
|
||||||
pair.setMatch(match);
|
|
||||||
pair.setTableId(table.getId());
|
|
||||||
player1.setState(TournamentPlayerState.DUELING);
|
player1.setState(TournamentPlayerState.DUELING);
|
||||||
player2.setState(TournamentPlayerState.DUELING);
|
player2.setState(TournamentPlayerState.DUELING);
|
||||||
});
|
});
|
||||||
|
|
@ -291,9 +289,7 @@ public class TournamentController {
|
||||||
table.setState(TableState.STARTING);
|
table.setState(TableState.STARTING);
|
||||||
tableManager.startTournamentSubMatch(null, table.getId());
|
tableManager.startTournamentSubMatch(null, table.getId());
|
||||||
tableManager.getMatch(table.getId()).ifPresent(match -> {
|
tableManager.getMatch(table.getId()).ifPresent(match -> {
|
||||||
match.setTableId(tableId);
|
round.setMatchAndTable(match, table.getId());
|
||||||
round.setMatch(match);
|
|
||||||
round.setTableId(table.getId());
|
|
||||||
for (TournamentPlayer player : round.getAllPlayers()) {
|
for (TournamentPlayer player : round.getAllPlayers()) {
|
||||||
player.setState(TournamentPlayerState.DUELING);
|
player.setState(TournamentPlayerState.DUELING);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ class AbsoluteVirtueAbility extends ProtectionAbility {
|
||||||
.ofNullable(source)
|
.ofNullable(source)
|
||||||
.map(MageItem::getId)
|
.map(MageItem::getId)
|
||||||
.map(game::getControllerId)
|
.map(game::getControllerId)
|
||||||
.map(uuid -> !game.getOpponents(this.getControllerId()).contains(uuid))
|
.map(uuid -> !game.getOpponents(this.getSourceId()).contains(uuid))
|
||||||
.orElse(true);
|
.orElse(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
Mage.Sets/src/mage/cards/a/AdagiaWindsweptBastion.java
Normal file
62
Mage.Sets/src/mage/cards/a/AdagiaWindsweptBastion.java
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTappedAbility;
|
||||||
|
import mage.abilities.costs.common.TapSourceCost;
|
||||||
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
|
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
|
||||||
|
import mage.abilities.keyword.StationAbility;
|
||||||
|
import mage.abilities.keyword.StationLevelAbility;
|
||||||
|
import mage.abilities.mana.WhiteManaAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.constants.SuperType;
|
||||||
|
import mage.filter.StaticFilters;
|
||||||
|
import mage.target.TargetPermanent;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class AdagiaWindsweptBastion extends CardImpl {
|
||||||
|
|
||||||
|
public AdagiaWindsweptBastion(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.PLANET);
|
||||||
|
|
||||||
|
// This land enters tapped.
|
||||||
|
this.addAbility(new EntersBattlefieldTappedAbility());
|
||||||
|
|
||||||
|
// {T}: Add {W}.
|
||||||
|
this.addAbility(new WhiteManaAbility());
|
||||||
|
|
||||||
|
// Station
|
||||||
|
this.addAbility(new StationAbility());
|
||||||
|
|
||||||
|
// STATION 12+
|
||||||
|
// {3}{W}, {T}: Create a token that's a copy of target artifact or enchantment you control, except it's legendary. Activate only as a sorcery.
|
||||||
|
Ability ability = new ActivateAsSorceryActivatedAbility(
|
||||||
|
new CreateTokenCopyTargetEffect()
|
||||||
|
.setPermanentModifier(token -> token.addSuperType(SuperType.LEGENDARY))
|
||||||
|
.setText("create a token that's a copy of target artifact or enchantment you control, except it's legendary"),
|
||||||
|
new ManaCostsImpl<>("{3}{W}")
|
||||||
|
);
|
||||||
|
ability.addCost(new TapSourceCost());
|
||||||
|
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CONTROLLED_ARTIFACT_OR_ENCHANTMENT));
|
||||||
|
this.addAbility(new StationLevelAbility(12).withLevelAbility(ability));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdagiaWindsweptBastion(final AdagiaWindsweptBastion card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdagiaWindsweptBastion copy() {
|
||||||
|
return new AdagiaWindsweptBastion(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ public final class AdelineResplendentCathar extends CardImpl {
|
||||||
|
|
||||||
// Adeline, Resplendent Cathar's power is equal to the number of creatures you control.
|
// Adeline, Resplendent Cathar's power is equal to the number of creatures you control.
|
||||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(
|
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(
|
||||||
CreaturesYouControlCount.instance)).addHint(CreaturesYouControlHint.instance)
|
CreaturesYouControlCount.PLURAL)).addHint(CreaturesYouControlHint.instance)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Whenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control.
|
// Whenever you attack, for each opponent, create a 1/1 white Human creature token that's tapped and attacking that player or a planeswalker they control.
|
||||||
|
|
|
||||||
|
|
@ -3,36 +3,26 @@
|
||||||
package mage.cards.a;
|
package mage.cards.a;
|
||||||
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.abilities.effects.common.DestroyTargetEffect;
|
import mage.abilities.effects.common.DestroyTargetEffect;
|
||||||
import mage.abilities.effects.common.GainLifeEffect;
|
import mage.abilities.effects.common.GainLifeEffect;
|
||||||
import mage.abilities.keyword.FlyingAbility;
|
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.filter.common.FilterCreaturePermanent;
|
import mage.filter.StaticFilters;
|
||||||
import mage.filter.predicate.mageobject.AbilityPredicate;
|
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
import mage.target.common.TargetCreaturePermanent;
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public final class AerialPredation extends CardImpl {
|
public final class AerialPredation extends CardImpl {
|
||||||
|
|
||||||
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying");
|
|
||||||
|
|
||||||
static {
|
|
||||||
filter.add(new AbilityPredicate(FlyingAbility.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AerialPredation(UUID ownerId, CardSetInfo setInfo) {
|
public AerialPredation(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{G}");
|
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}");
|
||||||
|
|
||||||
|
|
||||||
// Destroy target creature with flying. You gain 2 life.
|
// Destroy target creature with flying. You gain 2 life.
|
||||||
this.getSpellAbility().addTarget(new TargetPermanent(filter));
|
this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING));
|
||||||
this.getSpellAbility().addEffect(new DestroyTargetEffect());
|
this.getSpellAbility().addEffect(new DestroyTargetEffect());
|
||||||
this.getSpellAbility().addEffect(new GainLifeEffect(2));
|
this.getSpellAbility().addEffect(new GainLifeEffect(2));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,34 +7,23 @@ import mage.abilities.common.SimpleActivatedAbility;
|
||||||
import mage.abilities.costs.common.SacrificeSourceCost;
|
import mage.abilities.costs.common.SacrificeSourceCost;
|
||||||
import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue;
|
import mage.abilities.dynamicvalue.common.SourcePermanentPowerValue;
|
||||||
import mage.abilities.effects.common.DamageTargetEffect;
|
import mage.abilities.effects.common.DamageTargetEffect;
|
||||||
import mage.abilities.keyword.FlyingAbility;
|
|
||||||
import mage.abilities.keyword.PersistAbility;
|
import mage.abilities.keyword.PersistAbility;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.SubType;
|
import mage.constants.SubType;
|
||||||
import mage.constants.Zone;
|
import mage.filter.StaticFilters;
|
||||||
import mage.filter.common.FilterCreaturePermanent;
|
|
||||||
import mage.filter.predicate.mageobject.AbilityPredicate;
|
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
import mage.target.common.TargetCreaturePermanent;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author jeffwadsworth
|
* @author jeffwadsworth
|
||||||
*/
|
*/
|
||||||
public final class AerieOuphes extends CardImpl {
|
public final class AerieOuphes extends CardImpl {
|
||||||
|
|
||||||
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying");
|
|
||||||
|
|
||||||
static {
|
|
||||||
filter.add(new AbilityPredicate(FlyingAbility.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AerieOuphes(UUID ownerId, CardSetInfo setInfo) {
|
public AerieOuphes(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{G}");
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{G}");
|
||||||
this.subtype.add(SubType.OUPHE);
|
this.subtype.add(SubType.OUPHE);
|
||||||
|
|
||||||
this.power = new MageInt(3);
|
this.power = new MageInt(3);
|
||||||
|
|
@ -43,7 +32,7 @@ public final class AerieOuphes extends CardImpl {
|
||||||
// Sacrifice Aerie Ouphes: Aerie Ouphes deals damage equal to its power to target creature with flying.
|
// 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)
|
Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(SourcePermanentPowerValue.NOT_NEGATIVE)
|
||||||
.setText("it deals damage equal to its power to target creature with flying"), new SacrificeSourceCost());
|
.setText("it deals damage equal to its power to target creature with flying"), new SacrificeSourceCost());
|
||||||
ability.addTarget(new TargetPermanent(filter));
|
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING));
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
|
|
||||||
// Persist
|
// Persist
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
package mage.cards.a;
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.Mana;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.SimpleActivatedAbility;
|
import mage.abilities.common.SimpleActivatedAbility;
|
||||||
import mage.abilities.common.SpellCastControllerTriggeredAbility;
|
import mage.abilities.common.SpellCastControllerTriggeredAbility;
|
||||||
import mage.abilities.costs.common.PayEnergyCost;
|
import mage.abilities.costs.common.PayEnergyCost;
|
||||||
import mage.abilities.costs.common.TapSourceCost;
|
import mage.abilities.costs.common.TapSourceCost;
|
||||||
|
import mage.abilities.costs.mana.ManaCost;
|
||||||
import mage.abilities.effects.OneShotEffect;
|
import mage.abilities.effects.OneShotEffect;
|
||||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||||
import mage.abilities.effects.common.counter.GetEnergyCountersControllerEffect;
|
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.cards.CardsImpl;
|
import mage.cards.CardsImpl;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
|
import mage.counters.CounterType;
|
||||||
import mage.filter.StaticFilters;
|
import mage.filter.StaticFilters;
|
||||||
|
import mage.game.Controllable;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.stack.Spell;
|
import mage.game.stack.Spell;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
|
|
@ -34,8 +37,8 @@ public final class AetherfluxConduit extends CardImpl {
|
||||||
this.addAbility(new SpellCastControllerTriggeredAbility(new AetherfluxConduitManaEffect(), false));
|
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.
|
// {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 ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(7), new TapSourceCost());
|
||||||
ability.addCost(new PayEnergyCost(50).setText("Pay fifty {E}"));
|
ability.addCost(new PayEnergyCost(50).setText("pay fifty {E}"));
|
||||||
ability.addEffect(new AetherfluxConduitCastEffect());
|
ability.addEffect(new AetherfluxConduitCastEffect());
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
}
|
}
|
||||||
|
|
@ -68,10 +71,21 @@ class AetherfluxConduitManaEffect extends OneShotEffect {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
Optional.ofNullable(this.getValue("spellCast"))
|
int amount = Optional
|
||||||
|
.ofNullable(this.getValue("spellCast"))
|
||||||
.map(Spell.class::cast)
|
.map(Spell.class::cast)
|
||||||
.ifPresent(spell -> new GetEnergyCountersControllerEffect(spell.getManaValue()).apply(game, source));
|
.map(Spell::getStackAbility)
|
||||||
return true;
|
.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,12 +1,8 @@
|
||||||
package mage.cards.a;
|
package mage.cards.a;
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.Mode;
|
import mage.abilities.Mode;
|
||||||
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
|
import mage.abilities.common.SimpleActivatedAbility;
|
||||||
import mage.abilities.costs.common.TapSourceCost;
|
import mage.abilities.costs.common.TapSourceCost;
|
||||||
import mage.abilities.costs.mana.GenericManaCost;
|
import mage.abilities.costs.mana.GenericManaCost;
|
||||||
import mage.abilities.effects.OneShotEffect;
|
import mage.abilities.effects.OneShotEffect;
|
||||||
|
|
@ -15,6 +11,7 @@ import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
|
import mage.constants.TimingRule;
|
||||||
import mage.counters.Counter;
|
import mage.counters.Counter;
|
||||||
import mage.counters.CounterType;
|
import mage.counters.CounterType;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
@ -22,6 +19,10 @@ import mage.game.permanent.Permanent;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author sobiech
|
* @author sobiech
|
||||||
*/
|
*/
|
||||||
|
|
@ -35,8 +36,9 @@ public final class AethericAmplifier extends CardImpl {
|
||||||
|
|
||||||
// {4}, {T}: Choose one. Activate only as a sorcery.
|
// {4}, {T}: Choose one. Activate only as a sorcery.
|
||||||
// * Double the number of each kind of counter on target permanent.
|
// * Double the number of each kind of counter on target permanent.
|
||||||
final Ability ability = new ActivateAsSorceryActivatedAbility(new AethericAmplifierDoublePermanentEffect(), new GenericManaCost(4))
|
Ability ability = new SimpleActivatedAbility(
|
||||||
.withShowActivateText(false);
|
new AethericAmplifierDoublePermanentEffect(), new GenericManaCost(4)
|
||||||
|
).setTiming(TimingRule.SORCERY);
|
||||||
ability.addCost(new TapSourceCost());
|
ability.addCost(new TapSourceCost());
|
||||||
ability.addTarget(new TargetPermanent());
|
ability.addTarget(new TargetPermanent());
|
||||||
ability.getModes().setChooseText("choose one. Activate only as a sorcery.");
|
ability.getModes().setChooseText("choose one. Activate only as a sorcery.");
|
||||||
|
|
@ -145,5 +147,3 @@ class AethericAmplifierDoubleControllerEffect extends OneShotEffect {
|
||||||
return new AethericAmplifierDoubleControllerEffect(this);
|
return new AethericAmplifierDoubleControllerEffect(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ public final class AetherworksMarvel extends CardImpl {
|
||||||
this.addAbility(new PutIntoGraveFromBattlefieldAllTriggeredAbility(
|
this.addAbility(new PutIntoGraveFromBattlefieldAllTriggeredAbility(
|
||||||
new GetEnergyCountersControllerEffect(1), false,
|
new GetEnergyCountersControllerEffect(1), false,
|
||||||
StaticFilters.FILTER_CONTROLLED_A_PERMANENT, false
|
StaticFilters.FILTER_CONTROLLED_A_PERMANENT, false
|
||||||
));
|
).setTriggerPhrase("Whenever a permanent you control is put into a graveyard, "));
|
||||||
|
|
||||||
// {T}, Pay {E}{E}{E}{E}{E}{E}: Look at the top six cards of your library.
|
// {T}, Pay {E}{E}{E}{E}{E}{E}: Look at the top six cards of your library.
|
||||||
// You may cast a card from among them without paying its mana cost.
|
// You may cast a card from among them without paying its mana cost.
|
||||||
|
|
|
||||||
|
|
@ -61,18 +61,22 @@ class AettirAndPriwenEffect extends ContinuousEffectImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
Permanent permanent = Optional
|
||||||
|
.ofNullable(source.getSourcePermanentIfItStillExists(game))
|
||||||
|
.map(Permanent::getAttachedTo)
|
||||||
|
.map(game::getPermanent)
|
||||||
|
.orElse(null);
|
||||||
if (permanent == null) {
|
if (permanent == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int life = Optional
|
Optional.ofNullable(source)
|
||||||
.ofNullable(source)
|
|
||||||
.map(Controllable::getControllerId)
|
.map(Controllable::getControllerId)
|
||||||
.map(game::getPlayer)
|
.map(game::getPlayer)
|
||||||
.map(Player::getLife)
|
.map(Player::getLife)
|
||||||
.orElse(0);
|
.ifPresent(life -> {
|
||||||
permanent.getPower().setModifiedBaseValue(life);
|
permanent.getPower().setModifiedBaseValue(life);
|
||||||
permanent.getToughness().setModifiedBaseValue(life);
|
permanent.getToughness().setModifiedBaseValue(life);
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ public final class AgentOfAcquisitions extends CardImpl {
|
||||||
|
|
||||||
// Instead of drafting a card from a booster pack, you may draft each card in that booster pack, one at a time. If you do, turn Agent of Acquisitions face down and you can’t draft cards for the rest of this draft round.
|
// Instead of drafting a card from a booster pack, you may draft each card in that booster pack, one at a time. If you do, turn Agent of Acquisitions face down and you can’t draft cards for the rest of this draft round.
|
||||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Instead of drafting a card from a booster pack, "
|
this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Instead of drafting a card from a booster pack, "
|
||||||
+ "you may draft each card in that booster pack, one at a time. If you do, turn Agent of Acquisitions face down and "
|
+ "you may draft each card in that booster pack, one at a time. If you do, turn {this} face down and "
|
||||||
+ "you can't draft cards for the rest of this draft round - not implemented.")));
|
+ "you can't draft cards for the rest of this draft round - not implemented.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
package mage.cards.a;
|
package mage.cards.a;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.MageInt;
|
import mage.MageInt;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.SimpleActivatedAbility;
|
import mage.abilities.common.SimpleActivatedAbility;
|
||||||
|
|
@ -13,33 +12,27 @@ import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.SubType;
|
import mage.constants.SubType;
|
||||||
import mage.constants.Zone;
|
import mage.filter.StaticFilters;
|
||||||
import mage.filter.common.FilterCreaturePermanent;
|
|
||||||
import mage.filter.predicate.mageobject.AbilityPredicate;
|
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
import mage.target.common.TargetCreaturePermanent;
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
*/
|
*/
|
||||||
public final class AirServant extends CardImpl {
|
public final class AirServant extends CardImpl {
|
||||||
|
|
||||||
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying");
|
|
||||||
|
|
||||||
static {
|
|
||||||
filter.add(new AbilityPredicate(FlyingAbility.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AirServant(UUID ownerId, CardSetInfo setInfo) {
|
public AirServant(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{U}");
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{U}");
|
||||||
|
|
||||||
this.subtype.add(SubType.ELEMENTAL);
|
this.subtype.add(SubType.ELEMENTAL);
|
||||||
this.power = new MageInt(4);
|
this.power = new MageInt(4);
|
||||||
this.toughness = new MageInt(3);
|
this.toughness = new MageInt(3);
|
||||||
|
|
||||||
this.addAbility(FlyingAbility.getInstance());
|
this.addAbility(FlyingAbility.getInstance());
|
||||||
|
|
||||||
Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new ManaCostsImpl<>("{2}{U}"));
|
Ability ability = new SimpleActivatedAbility(new TapTargetEffect(), new ManaCostsImpl<>("{2}{U}"));
|
||||||
ability.addTarget(new TargetPermanent(filter));
|
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING));
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ class AjaniNacatlAvengerZeroEffect extends OneShotEffect {
|
||||||
}
|
}
|
||||||
|
|
||||||
ReflexiveTriggeredAbility reflexive = new ReflexiveTriggeredAbility(
|
ReflexiveTriggeredAbility reflexive = new ReflexiveTriggeredAbility(
|
||||||
new DamageTargetEffect(CreaturesYouControlCount.instance),
|
new DamageTargetEffect(CreaturesYouControlCount.PLURAL),
|
||||||
false,
|
false,
|
||||||
"When you do, if you control a red permanent other than {this}, "
|
"When you do, if you control a red permanent other than {this}, "
|
||||||
+ "he deals damage equal to the number of creatures you control to any target.",
|
+ "he deals damage equal to the number of creatures you control to any target.",
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public final class AjaniWiseCounselor extends CardImpl {
|
||||||
this.setStartingLoyalty(5);
|
this.setStartingLoyalty(5);
|
||||||
|
|
||||||
// +2: You gain 1 life for each creature you control.
|
// +2: You gain 1 life for each creature you control.
|
||||||
this.addAbility(new LoyaltyAbility(new GainLifeEffect(CreaturesYouControlCount.instance)
|
this.addAbility(new LoyaltyAbility(new GainLifeEffect(CreaturesYouControlCount.PLURAL)
|
||||||
.setText("you gain 1 life for each creature you control"), 2));
|
.setText("you gain 1 life for each creature you control"), 2));
|
||||||
|
|
||||||
// −3: Creatures you control get +2/+2 until end of turn.
|
// −3: Creatures you control get +2/+2 until end of turn.
|
||||||
|
|
|
||||||
46
Mage.Sets/src/mage/cards/a/AllFatesScroll.java
Normal file
46
Mage.Sets/src/mage/cards/a/AllFatesScroll.java
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.SimpleActivatedAbility;
|
||||||
|
import mage.abilities.costs.common.SacrificeSourceCost;
|
||||||
|
import mage.abilities.costs.common.TapSourceCost;
|
||||||
|
import mage.abilities.costs.mana.GenericManaCost;
|
||||||
|
import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount;
|
||||||
|
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||||
|
import mage.abilities.mana.AnyColorManaAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.filter.StaticFilters;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class AllFatesScroll extends CardImpl {
|
||||||
|
|
||||||
|
private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS);
|
||||||
|
|
||||||
|
public AllFatesScroll(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
|
||||||
|
|
||||||
|
// {T}: Add one mana of any color.
|
||||||
|
this.addAbility(new AnyColorManaAbility());
|
||||||
|
|
||||||
|
// {7}, {T}, Sacrifice this artifact: Draw X cards, where X is the number of differently named lands you control.
|
||||||
|
Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(xValue), new GenericManaCost(7));
|
||||||
|
ability.addCost(new TapSourceCost());
|
||||||
|
ability.addCost(new SacrificeSourceCost());
|
||||||
|
this.addAbility(ability.addHint(xValue.getHint()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AllFatesScroll(final AllFatesScroll card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AllFatesScroll copy() {
|
||||||
|
return new AllFatesScroll(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
55
Mage.Sets/src/mage/cards/a/AllFatesStalker.java
Normal file
55
Mage.Sets/src/mage/cards/a/AllFatesStalker.java
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.effects.common.ExileUntilSourceLeavesEffect;
|
||||||
|
import mage.abilities.keyword.WarpAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.filter.FilterPermanent;
|
||||||
|
import mage.filter.common.FilterCreaturePermanent;
|
||||||
|
import mage.filter.predicate.Predicates;
|
||||||
|
import mage.target.TargetPermanent;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class AllFatesStalker extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterPermanent filter = new FilterCreaturePermanent("non-Assassin creature");
|
||||||
|
|
||||||
|
static {
|
||||||
|
filter.add(Predicates.not(SubType.ASSASSIN.getPredicate()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AllFatesStalker(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.DRIX);
|
||||||
|
this.subtype.add(SubType.ASSASSIN);
|
||||||
|
this.power = new MageInt(2);
|
||||||
|
this.toughness = new MageInt(3);
|
||||||
|
|
||||||
|
// When this creature enters, exile up to one target non-Assassin creature until this creature leaves the battlefield.
|
||||||
|
Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect());
|
||||||
|
ability.addTarget(new TargetPermanent(0, 1, filter));
|
||||||
|
this.addAbility(ability);
|
||||||
|
|
||||||
|
// Warp {1}{W}
|
||||||
|
this.addAbility(new WarpAbility(this, "{1}{W}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AllFatesStalker(final AllFatesStalker card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AllFatesStalker copy() {
|
||||||
|
return new AllFatesStalker(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Mage.Sets/src/mage/cards/a/AlpharaelStonechosen.java
Normal file
49
Mage.Sets/src/mage/cards/a/AlpharaelStonechosen.java
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.common.AttacksTriggeredAbility;
|
||||||
|
import mage.abilities.condition.common.VoidCondition;
|
||||||
|
import mage.abilities.costs.common.DiscardCardCost;
|
||||||
|
import mage.abilities.effects.common.LoseHalfLifeTargetEffect;
|
||||||
|
import mage.abilities.keyword.WardAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.*;
|
||||||
|
import mage.watchers.common.VoidWatcher;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class AlpharaelStonechosen extends CardImpl {
|
||||||
|
|
||||||
|
public AlpharaelStonechosen(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}");
|
||||||
|
|
||||||
|
this.supertype.add(SuperType.LEGENDARY);
|
||||||
|
this.subtype.add(SubType.HUMAN);
|
||||||
|
this.subtype.add(SubType.CLERIC);
|
||||||
|
this.power = new MageInt(3);
|
||||||
|
this.toughness = new MageInt(3);
|
||||||
|
|
||||||
|
// Ward--Discard a card at random.
|
||||||
|
this.addAbility(new WardAbility(new DiscardCardCost(true)));
|
||||||
|
|
||||||
|
// Void -- Whenever Alpharael attacks, if a nonland permanent left the battlefield this turn or a spell was warped this turn, defending player loses half their life, rounded up.
|
||||||
|
this.addAbility(new AttacksTriggeredAbility(
|
||||||
|
new LoseHalfLifeTargetEffect()
|
||||||
|
.setText("defending player loses half their life, rounded up"),
|
||||||
|
false, null, SetTargetPointer.PLAYER
|
||||||
|
).withInterveningIf(VoidCondition.instance).setAbilityWord(AbilityWord.VOID).addHint(VoidCondition.getHint()), new VoidWatcher());
|
||||||
|
}
|
||||||
|
|
||||||
|
private AlpharaelStonechosen(final AlpharaelStonechosen card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AlpharaelStonechosen copy() {
|
||||||
|
return new AlpharaelStonechosen(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,7 +27,7 @@ public final class AngelOfRenewal extends CardImpl {
|
||||||
// Flying
|
// Flying
|
||||||
this.addAbility(FlyingAbility.getInstance());
|
this.addAbility(FlyingAbility.getInstance());
|
||||||
// When Angel of Renewal enters the battlefield, you gain 1 life for each creature you control.
|
// When Angel of Renewal enters the battlefield, you gain 1 life for each creature you control.
|
||||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(CreaturesYouControlCount.instance).setText("you gain 1 life for each creature you control")));
|
this.addAbility(new EntersBattlefieldTriggeredAbility(new GainLifeEffect(CreaturesYouControlCount.PLURAL).setText("you gain 1 life for each creature you control")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private AngelOfRenewal(final AngelOfRenewal card) {
|
private AngelOfRenewal(final AngelOfRenewal card) {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public final class AngelicExaltation extends CardImpl {
|
||||||
|
|
||||||
// Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of creatures you control.
|
// Whenever a creature you control attacks alone, it gets +X/+X until end of turn, where X is the number of creatures you control.
|
||||||
this.addAbility(new AttacksAloneControlledTriggeredAbility(
|
this.addAbility(new AttacksAloneControlledTriggeredAbility(
|
||||||
new BoostTargetEffect(CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, Duration.EndOfTurn),
|
new BoostTargetEffect(CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn),
|
||||||
true, false).addHint(CreaturesYouControlHint.instance));
|
true, false).addHint(CreaturesYouControlHint.instance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
71
Mage.Sets/src/mage/cards/a/AnticausalVestige.java
Normal file
71
Mage.Sets/src/mage/cards/a/AnticausalVestige.java
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.LeavesBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.dynamicvalue.common.LandsYouControlCount;
|
||||||
|
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||||
|
import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect;
|
||||||
|
import mage.abilities.hint.common.LandsYouControlHint;
|
||||||
|
import mage.abilities.keyword.WarpAbility;
|
||||||
|
import mage.cards.Card;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.filter.FilterCard;
|
||||||
|
import mage.filter.common.FilterPermanentCard;
|
||||||
|
import mage.filter.predicate.ObjectSourcePlayer;
|
||||||
|
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||||
|
import mage.game.Game;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class AnticausalVestige extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterCard filter = new FilterPermanentCard(
|
||||||
|
"a permanent card with mana value less than or equal to the number of lands you control"
|
||||||
|
);
|
||||||
|
|
||||||
|
static {
|
||||||
|
filter.add(AnticausalVestigePredicate.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnticausalVestige(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.ELDRAZI);
|
||||||
|
this.power = new MageInt(7);
|
||||||
|
this.toughness = new MageInt(5);
|
||||||
|
|
||||||
|
// When this creature leaves the battlefield, draw a card, then you may put a permanent card with mana value less than or equal to the number of lands you control from your hand onto the battlefield tapped.
|
||||||
|
Ability ability = new LeavesBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(1));
|
||||||
|
ability.addEffect(new PutCardFromHandOntoBattlefieldEffect(filter, false, true).concatBy(", then"));
|
||||||
|
this.addAbility(ability.addHint(LandsYouControlHint.instance));
|
||||||
|
|
||||||
|
// Warp {4}
|
||||||
|
this.addAbility(new WarpAbility(this, "{4}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AnticausalVestige(final AnticausalVestige card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnticausalVestige copy() {
|
||||||
|
return new AnticausalVestige(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AnticausalVestigePredicate implements ObjectSourcePlayerPredicate<Card> {
|
||||||
|
instance;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(ObjectSourcePlayer<Card> input, Game game) {
|
||||||
|
return input.getObject().getManaValue()
|
||||||
|
<= LandsYouControlCount.instance.calculate(game, input.getSource(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ public final class AppealAuthority extends SplitCard {
|
||||||
// Until end of turn, target creature gains trample and gets +X/+X, where X is the number of creatures you control.
|
// Until end of turn, target creature gains trample and gets +X/+X, where X is the number of creatures you control.
|
||||||
getLeftHalfCard().getSpellAbility().addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn)
|
getLeftHalfCard().getSpellAbility().addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance(), Duration.EndOfTurn)
|
||||||
.setText("Until end of turn, target creature gains trample"));
|
.setText("Until end of turn, target creature gains trample"));
|
||||||
getLeftHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(CreaturesYouControlCount.instance, CreaturesYouControlCount.instance, Duration.EndOfTurn)
|
getLeftHalfCard().getSpellAbility().addEffect(new BoostTargetEffect(CreaturesYouControlCount.PLURAL, CreaturesYouControlCount.PLURAL, Duration.EndOfTurn)
|
||||||
.setText("and gets +X/+X, where X is the number of creatures you control"));
|
.setText("and gets +X/+X, where X is the number of creatures you control"));
|
||||||
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent());
|
getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent());
|
||||||
getLeftHalfCard().getSpellAbility().addHint(CreaturesYouControlHint.instance);
|
getLeftHalfCard().getSpellAbility().addHint(CreaturesYouControlHint.instance);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
|
|
||||||
package mage.cards.a;
|
package mage.cards.a;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.MageInt;
|
import mage.MageInt;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.SimpleActivatedAbility;
|
import mage.abilities.common.SimpleActivatedAbility;
|
||||||
|
|
@ -11,31 +10,23 @@ import mage.abilities.dynamicvalue.common.GetXValue;
|
||||||
import mage.abilities.effects.common.DamageAllEffect;
|
import mage.abilities.effects.common.DamageAllEffect;
|
||||||
import mage.abilities.effects.common.DamageTargetEffect;
|
import mage.abilities.effects.common.DamageTargetEffect;
|
||||||
import mage.abilities.keyword.ChannelAbility;
|
import mage.abilities.keyword.ChannelAbility;
|
||||||
import mage.abilities.keyword.FlyingAbility;
|
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.SubType;
|
import mage.constants.SubType;
|
||||||
import mage.constants.SuperType;
|
import mage.constants.SuperType;
|
||||||
import mage.constants.Zone;
|
import mage.filter.StaticFilters;
|
||||||
import mage.filter.common.FilterCreaturePermanent;
|
|
||||||
import mage.filter.predicate.mageobject.AbilityPredicate;
|
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public final class ArashiTheSkyAsunder extends CardImpl {
|
public final class ArashiTheSkyAsunder extends CardImpl {
|
||||||
|
|
||||||
static final private FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying");
|
|
||||||
|
|
||||||
static {
|
|
||||||
filter.add(new AbilityPredicate(FlyingAbility.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArashiTheSkyAsunder(UUID ownerId, CardSetInfo setInfo) {
|
public ArashiTheSkyAsunder(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{G}{G}");
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}");
|
||||||
this.supertype.add(SuperType.LEGENDARY);
|
this.supertype.add(SuperType.LEGENDARY);
|
||||||
this.subtype.add(SubType.SPIRIT);
|
this.subtype.add(SubType.SPIRIT);
|
||||||
|
|
||||||
|
|
@ -45,11 +36,11 @@ public final class ArashiTheSkyAsunder extends CardImpl {
|
||||||
// {X}{G}, {tap}: Arashi, the Sky Asunder deals X damage to target creature with flying.
|
// {X}{G}, {tap}: Arashi, the Sky Asunder deals X damage to target creature with flying.
|
||||||
Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(GetXValue.instance), new ManaCostsImpl<>("{X}{G}"));
|
Ability ability = new SimpleActivatedAbility(new DamageTargetEffect(GetXValue.instance), new ManaCostsImpl<>("{X}{G}"));
|
||||||
ability.addCost(new TapSourceCost());
|
ability.addCost(new TapSourceCost());
|
||||||
ability.addTarget(new TargetPermanent(filter));
|
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING));
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
|
|
||||||
// Channel - {X}{G}{G}, Discard Arashi: Arashi deals X damage to each creature with flying.
|
// Channel - {X}{G}{G}, Discard Arashi: Arashi deals X damage to each creature with flying.
|
||||||
this.addAbility(new ChannelAbility("{X}{G}{G}", new DamageAllEffect(GetXValue.instance, filter)));
|
this.addAbility(new ChannelAbility("{X}{G}{G}", new DamageAllEffect(GetXValue.instance, StaticFilters.FILTER_CREATURE_FLYING)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArashiTheSkyAsunder(final ArashiTheSkyAsunder card) {
|
private ArashiTheSkyAsunder(final ArashiTheSkyAsunder card) {
|
||||||
|
|
|
||||||
62
Mage.Sets/src/mage/cards/a/ArchenemysCharm.java
Normal file
62
Mage.Sets/src/mage/cards/a/ArchenemysCharm.java
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.abilities.Mode;
|
||||||
|
import mage.abilities.effects.common.ExileTargetEffect;
|
||||||
|
import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||||
|
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||||
|
import mage.abilities.keyword.LifelinkAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import mage.filter.FilterCard;
|
||||||
|
import mage.filter.predicate.Predicates;
|
||||||
|
import mage.target.common.TargetCardInYourGraveyard;
|
||||||
|
import mage.target.common.TargetControlledCreaturePermanent;
|
||||||
|
import mage.target.common.TargetCreatureOrPlaneswalker;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class ArchenemysCharm extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterCard filter = new FilterCard("creature and/or planeswalker cards from your graveyard");
|
||||||
|
|
||||||
|
static {
|
||||||
|
filter.add(Predicates.or(
|
||||||
|
CardType.CREATURE.getPredicate(),
|
||||||
|
CardType.PLANESWALKER.getPredicate()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArchenemysCharm(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{B}{B}{B}");
|
||||||
|
|
||||||
|
// Choose one --
|
||||||
|
// * Exile target creature or planeswalker.
|
||||||
|
this.getSpellAbility().addEffect(new ExileTargetEffect());
|
||||||
|
this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker());
|
||||||
|
|
||||||
|
// * Return one or two target creature and/or planeswalker cards from your graveyard to your hand.
|
||||||
|
this.getSpellAbility().addMode(new Mode(new ReturnFromGraveyardToHandTargetEffect())
|
||||||
|
.addTarget(new TargetCardInYourGraveyard(1, 2, filter)));
|
||||||
|
|
||||||
|
// * Put two +1/+1 counters on target creature you control. It gains lifelink until end of turn.
|
||||||
|
this.getSpellAbility().addMode(new Mode(new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)))
|
||||||
|
.addEffect(new GainAbilityTargetEffect(LifelinkAbility.getInstance())
|
||||||
|
.setText("It gains lifelink until end of turn"))
|
||||||
|
.addTarget(new TargetControlledCreaturePermanent()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArchenemysCharm(final ArchenemysCharm card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArchenemysCharm copy() {
|
||||||
|
return new ArchenemysCharm(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,6 @@ import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.filter.StaticFilters;
|
import mage.filter.StaticFilters;
|
||||||
import mage.target.common.TargetControlledCreaturePermanent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -26,7 +25,7 @@ public final class AshnodsAltar extends CardImpl {
|
||||||
// Sacrifice a creature: Add {C}{C}.
|
// Sacrifice a creature: Add {C}{C}.
|
||||||
SacrificeTargetCost cost = new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE);
|
SacrificeTargetCost cost = new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_CREATURE);
|
||||||
this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD,
|
this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD,
|
||||||
new BasicManaEffect(Mana.ColorlessMana(2), CreaturesYouControlCount.instance),
|
new BasicManaEffect(Mana.ColorlessMana(2), CreaturesYouControlCount.PLURAL),
|
||||||
cost));
|
cost));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
|
|
||||||
package mage.cards.a;
|
package mage.cards.a;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.abilities.effects.Effect;
|
import mage.abilities.effects.Effect;
|
||||||
import mage.abilities.effects.common.CreateTokenEffect;
|
import mage.abilities.effects.common.CreateTokenEffect;
|
||||||
import mage.abilities.effects.common.DamageTargetEffect;
|
import mage.abilities.effects.common.DamageTargetEffect;
|
||||||
|
|
@ -12,6 +11,8 @@ import mage.constants.SpellAbilityType;
|
||||||
import mage.game.permanent.token.ElephantToken;
|
import mage.game.permanent.token.ElephantToken;
|
||||||
import mage.target.common.TargetAnyTarget;
|
import mage.target.common.TargetAnyTarget;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public final class AssaultBattery extends SplitCard {
|
public final class AssaultBattery extends SplitCard {
|
||||||
|
|
||||||
public AssaultBattery(UUID ownerId, CardSetInfo setInfo) {
|
public AssaultBattery(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
|
@ -20,7 +21,7 @@ public final class AssaultBattery extends SplitCard {
|
||||||
// Assault
|
// Assault
|
||||||
// Assault deals 2 damage to any target.
|
// Assault deals 2 damage to any target.
|
||||||
Effect effect = new DamageTargetEffect(2);
|
Effect effect = new DamageTargetEffect(2);
|
||||||
effect.setText("Assault deals 2 damage to any target");
|
effect.setText("{this} deals 2 damage to any target");
|
||||||
getLeftHalfCard().getSpellAbility().addEffect(effect);
|
getLeftHalfCard().getSpellAbility().addEffect(effect);
|
||||||
getLeftHalfCard().getSpellAbility().addTarget(new TargetAnyTarget());
|
getLeftHalfCard().getSpellAbility().addTarget(new TargetAnyTarget());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,13 @@
|
||||||
package mage.cards.a;
|
package mage.cards.a;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
|
||||||
import mage.abilities.costs.Cost;
|
|
||||||
import mage.abilities.costs.mana.GenericManaCost;
|
import mage.abilities.costs.mana.GenericManaCost;
|
||||||
import mage.abilities.effects.OneShotEffect;
|
import mage.abilities.effects.common.CounterUnlessPaysEffect;
|
||||||
import mage.abilities.effects.keyword.IncubateEffect;
|
import mage.abilities.effects.keyword.IncubateEffect;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.Outcome;
|
|
||||||
import mage.filter.FilterSpell;
|
import mage.filter.FilterSpell;
|
||||||
import mage.filter.predicate.Predicates;
|
import mage.filter.predicate.Predicates;
|
||||||
import mage.game.Game;
|
|
||||||
import mage.players.Player;
|
|
||||||
import mage.target.TargetSpell;
|
import mage.target.TargetSpell;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -35,7 +30,10 @@ public final class AssimilateEssence extends CardImpl {
|
||||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
|
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
|
||||||
|
|
||||||
// Counter target creature or battle spell unless its controller pays {4}. If they do, you incubate 2.
|
// Counter target creature or battle spell unless its controller pays {4}. If they do, you incubate 2.
|
||||||
this.getSpellAbility().addEffect(new AssimilateEssenceEffect());
|
this.getSpellAbility().addEffect(
|
||||||
|
new CounterUnlessPaysEffect(new GenericManaCost(4))
|
||||||
|
.withIfTheyDo(new IncubateEffect(2).setText("you incubate 2"))
|
||||||
|
);
|
||||||
this.getSpellAbility().addTarget(new TargetSpell(filter));
|
this.getSpellAbility().addTarget(new TargetSpell(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,35 +46,3 @@ public final class AssimilateEssence extends CardImpl {
|
||||||
return new AssimilateEssence(this);
|
return new AssimilateEssence(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AssimilateEssenceEffect extends OneShotEffect {
|
|
||||||
|
|
||||||
AssimilateEssenceEffect() {
|
|
||||||
super(Outcome.Benefit);
|
|
||||||
staticText = "counter target creature or battle spell unless its controller pays {4}. If they do, you incubate 2";
|
|
||||||
}
|
|
||||||
|
|
||||||
private AssimilateEssenceEffect(final AssimilateEssenceEffect effect) {
|
|
||||||
super(effect);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AssimilateEssenceEffect copy() {
|
|
||||||
return new AssimilateEssenceEffect(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean apply(Game game, Ability source) {
|
|
||||||
UUID targetId = getTargetPointer().getFirst(game, source);
|
|
||||||
Player player = game.getPlayer(game.getControllerId(targetId));
|
|
||||||
Cost cost = new GenericManaCost(4);
|
|
||||||
if (player == null
|
|
||||||
|| !cost.canPay(source, source, player.getId(), game)
|
|
||||||
|| !player.chooseUse(outcome, "Pay {4}?", source, game)
|
|
||||||
|| !cost.pay(source, game, source, player.getId(), false)) {
|
|
||||||
game.getStack().counter(targetId, source, game);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return IncubateEffect.doIncubate(2, source.getControllerId(), game, source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
80
Mage.Sets/src/mage/cards/a/AstelliReclaimer.java
Normal file
80
Mage.Sets/src/mage/cards/a/AstelliReclaimer.java
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect;
|
||||||
|
import mage.abilities.keyword.FlyingAbility;
|
||||||
|
import mage.abilities.keyword.WarpAbility;
|
||||||
|
import mage.cards.Card;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.filter.FilterCard;
|
||||||
|
import mage.filter.predicate.ObjectSourcePlayer;
|
||||||
|
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||||
|
import mage.filter.predicate.Predicates;
|
||||||
|
import mage.filter.predicate.mageobject.PermanentPredicate;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.target.common.TargetCardInYourGraveyard;
|
||||||
|
import mage.watchers.common.ManaPaidSourceWatcher;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class AstelliReclaimer extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterCard filter = new FilterCard(
|
||||||
|
"noncreature, nonland permanent card with mana value X or less"
|
||||||
|
);
|
||||||
|
|
||||||
|
static {
|
||||||
|
filter.add(Predicates.not(CardType.CREATURE.getPredicate()));
|
||||||
|
filter.add(Predicates.not(CardType.LAND.getPredicate()));
|
||||||
|
filter.add(PermanentPredicate.instance);
|
||||||
|
filter.add(AstelliReclaimerPredicate.instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AstelliReclaimer(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.ANGEL);
|
||||||
|
this.subtype.add(SubType.WARRIOR);
|
||||||
|
this.power = new MageInt(5);
|
||||||
|
this.toughness = new MageInt(4);
|
||||||
|
|
||||||
|
// Flying
|
||||||
|
this.addAbility(FlyingAbility.getInstance());
|
||||||
|
|
||||||
|
// When this creature enters, return target noncreature, nonland permanent card with mana value X or less from your graveyard to the battlefield, where X is the amount of mana spent to cast this creature.
|
||||||
|
Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()
|
||||||
|
.setText("return target noncreature, nonland permanent card with mana value X or less from " +
|
||||||
|
"your graveyard to the battlefield, where X is the amount of mana spent to cast this creature"));
|
||||||
|
ability.addTarget(new TargetCardInYourGraveyard(filter));
|
||||||
|
this.addAbility(ability);
|
||||||
|
|
||||||
|
// Warp {2}{W}
|
||||||
|
this.addAbility(new WarpAbility(this, "{2}{W}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AstelliReclaimer(final AstelliReclaimer card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AstelliReclaimer copy() {
|
||||||
|
return new AstelliReclaimer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AstelliReclaimerPredicate implements ObjectSourcePlayerPredicate<Card> {
|
||||||
|
instance;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(ObjectSourcePlayer<Card> input, Game game) {
|
||||||
|
return input.getObject().getManaValue() <= ManaPaidSourceWatcher.getTotalPaid(input.getSourceId(), game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,8 +9,7 @@ import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.SubType;
|
import mage.constants.SubType;
|
||||||
import mage.filter.FilterPermanent;
|
import mage.filter.StaticFilters;
|
||||||
import mage.filter.predicate.Predicates;
|
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -20,12 +19,6 @@ import java.util.UUID;
|
||||||
*/
|
*/
|
||||||
public final class AstralDragon extends CardImpl {
|
public final class AstralDragon extends CardImpl {
|
||||||
|
|
||||||
private static final FilterPermanent filter = new FilterPermanent("noncreature permanent");
|
|
||||||
|
|
||||||
static {
|
|
||||||
filter.add(Predicates.not(CardType.CREATURE.getPredicate()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AstralDragon(UUID ownerId, CardSetInfo setInfo) {
|
public AstralDragon(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{U}{U}");
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{6}{U}{U}");
|
||||||
|
|
||||||
|
|
@ -37,14 +30,13 @@ public final class AstralDragon extends CardImpl {
|
||||||
this.addAbility(FlyingAbility.getInstance());
|
this.addAbility(FlyingAbility.getInstance());
|
||||||
|
|
||||||
// Project Image — When Astral Dragon enters the battlefield, create two tokens that are copies of target noncreature permanent, except they're 3/3 Dragon creatures in addition to their other types, and they have flying.
|
// Project Image — When Astral Dragon enters the battlefield, create two tokens that are copies of target noncreature permanent, except they're 3/3 Dragon creatures in addition to their other types, and they have flying.
|
||||||
CreateTokenCopyTargetEffect effect = new CreateTokenCopyTargetEffect(
|
Ability ability = new EntersBattlefieldTriggeredAbility(new CreateTokenCopyTargetEffect(
|
||||||
null, CardType.CREATURE, false, 2, false,
|
null, CardType.CREATURE, false, 2, false,
|
||||||
false, null, 3, 3, true);
|
false, null, 3, 3, true
|
||||||
effect.setText("create two tokens that are copies of target noncreature permanent, " +
|
).withAdditionalSubType(SubType.DRAGON)
|
||||||
"except they're 3/3 Dragon creatures in addition to their other types, and they have flying");
|
.setText("create two tokens that are copies of target noncreature permanent, " +
|
||||||
effect.withAdditionalSubType(SubType.DRAGON);
|
"except they're 3/3 Dragon creatures in addition to their other types, and they have flying"));
|
||||||
Ability ability = new EntersBattlefieldTriggeredAbility(effect);
|
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_NON_CREATURE));
|
||||||
ability.addTarget(new TargetPermanent(filter));
|
|
||||||
this.addAbility(ability.withFlavorWord("Project Image"));
|
this.addAbility(ability.withFlavorWord("Project Image"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
54
Mage.Sets/src/mage/cards/a/AtmosphericGreenhouse.java
Normal file
54
Mage.Sets/src/mage/cards/a/AtmosphericGreenhouse.java
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.effects.common.counter.AddCountersAllEffect;
|
||||||
|
import mage.abilities.keyword.FlyingAbility;
|
||||||
|
import mage.abilities.keyword.StationAbility;
|
||||||
|
import mage.abilities.keyword.StationLevelAbility;
|
||||||
|
import mage.abilities.keyword.TrampleAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import mage.filter.StaticFilters;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class AtmosphericGreenhouse extends CardImpl {
|
||||||
|
|
||||||
|
public AtmosphericGreenhouse(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{G}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.SPACECRAFT);
|
||||||
|
|
||||||
|
// When this Spacecraft enters, put a +1/+1 counter on each creature you control.
|
||||||
|
this.addAbility(new EntersBattlefieldTriggeredAbility(new AddCountersAllEffect(
|
||||||
|
CounterType.P1P1.createInstance(), StaticFilters.FILTER_CONTROLLED_CREATURE
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Station
|
||||||
|
this.addAbility(new StationAbility());
|
||||||
|
|
||||||
|
// STATION 8+
|
||||||
|
// Flying
|
||||||
|
// Trample
|
||||||
|
// 5/4
|
||||||
|
this.addAbility(new StationLevelAbility(8)
|
||||||
|
.withLevelAbility(FlyingAbility.getInstance())
|
||||||
|
.withLevelAbility(TrampleAbility.getInstance())
|
||||||
|
.withPT(5, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtmosphericGreenhouse(final AtmosphericGreenhouse card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AtmosphericGreenhouse copy() {
|
||||||
|
return new AtmosphericGreenhouse(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Mage.Sets/src/mage/cards/a/AtomicMicrosizer.java
Normal file
52
Mage.Sets/src/mage/cards/a/AtomicMicrosizer.java
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.AttacksAttachedTriggeredAbility;
|
||||||
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect;
|
||||||
|
import mage.abilities.keyword.EquipAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.Duration;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.target.common.TargetCreaturePermanent;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class AtomicMicrosizer extends CardImpl {
|
||||||
|
|
||||||
|
public AtomicMicrosizer(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{U}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.EQUIPMENT);
|
||||||
|
|
||||||
|
// Equipped creature gets +1/+0.
|
||||||
|
this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(1, 0)));
|
||||||
|
|
||||||
|
// Whenever equipped creature attacks, choose up to one target creature. That creature can't be blocked this turn and has base power and toughness 1/1 until end of turn.
|
||||||
|
Ability ability = new AttacksAttachedTriggeredAbility(new CantBeBlockedTargetEffect()
|
||||||
|
.setText("choose up to one target creature. That creature can't be blocked this turn"));
|
||||||
|
ability.addEffect(new SetBasePowerToughnessTargetEffect(1, 1, Duration.EndOfTurn)
|
||||||
|
.setText("and has base power and toughness 1/1 until end of turn"));
|
||||||
|
ability.addTarget(new TargetCreaturePermanent(0, 1));
|
||||||
|
this.addAbility(ability);
|
||||||
|
|
||||||
|
// Equip {2}
|
||||||
|
this.addAbility(new EquipAbility(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AtomicMicrosizer(final AtomicMicrosizer card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AtomicMicrosizer copy() {
|
||||||
|
return new AtomicMicrosizer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
package mage.cards.a;
|
package mage.cards.a;
|
||||||
|
|
||||||
import mage.MageObject;
|
import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount;
|
||||||
import mage.abilities.Ability;
|
|
||||||
import mage.abilities.dynamicvalue.DynamicValue;
|
|
||||||
import mage.abilities.effects.Effect;
|
|
||||||
import mage.abilities.effects.common.CreateTokenEffect;
|
import mage.abilities.effects.common.CreateTokenEffect;
|
||||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||||
import mage.abilities.hint.Hint;
|
|
||||||
import mage.abilities.hint.ValueHint;
|
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.filter.StaticFilters;
|
import mage.filter.FilterPermanent;
|
||||||
import mage.game.Game;
|
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||||
import mage.game.permanent.PermanentToken;
|
import mage.filter.predicate.permanent.TokenPredicate;
|
||||||
import mage.game.permanent.token.PlantToken;
|
import mage.game.permanent.token.PlantToken;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -23,14 +18,22 @@ import java.util.UUID;
|
||||||
*/
|
*/
|
||||||
public final class AudienceWithTrostani extends CardImpl {
|
public final class AudienceWithTrostani extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterPermanent filter = new FilterControlledCreaturePermanent("creature tokens you control");
|
||||||
|
|
||||||
|
static {
|
||||||
|
filter.add(TokenPredicate.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(filter);
|
||||||
|
|
||||||
public AudienceWithTrostani(UUID ownerId, CardSetInfo setInfo) {
|
public AudienceWithTrostani(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}");
|
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{G}");
|
||||||
|
|
||||||
// Create a 0/1 green Plant creature token, then draw cards equal to the number of differently named creature tokens you control.
|
// Create a 0/1 green Plant creature token, then draw cards equal to the number of differently named creature tokens you control.
|
||||||
this.getSpellAbility().addEffect(new CreateTokenEffect(new PlantToken()));
|
this.getSpellAbility().addEffect(new CreateTokenEffect(new PlantToken()));
|
||||||
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(AudienceWithTrostaniValue.instance)
|
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(xValue)
|
||||||
.setText(", then draw cards equal to the number of differently named creature tokens you control"));
|
.setText(", then draw cards equal to the number of differently named creature tokens you control"));
|
||||||
this.getSpellAbility().addHint(AudienceWithTrostaniValue.getHint());
|
this.getSpellAbility().addHint(xValue.getHint());
|
||||||
}
|
}
|
||||||
|
|
||||||
private AudienceWithTrostani(final AudienceWithTrostani card) {
|
private AudienceWithTrostani(final AudienceWithTrostani card) {
|
||||||
|
|
@ -42,46 +45,3 @@ public final class AudienceWithTrostani extends CardImpl {
|
||||||
return new AudienceWithTrostani(this);
|
return new AudienceWithTrostani(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AudienceWithTrostaniValue implements DynamicValue {
|
|
||||||
instance;
|
|
||||||
private static final Hint hint = new ValueHint(
|
|
||||||
"Different names among creature tokens you control", instance
|
|
||||||
);
|
|
||||||
|
|
||||||
public static Hint getHint() {
|
|
||||||
return hint;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
|
||||||
return game
|
|
||||||
.getBattlefield()
|
|
||||||
.getActivePermanents(
|
|
||||||
StaticFilters.FILTER_CONTROLLED_CREATURE,
|
|
||||||
sourceAbility.getControllerId(), sourceAbility, game
|
|
||||||
)
|
|
||||||
.stream()
|
|
||||||
.filter(PermanentToken.class::isInstance)
|
|
||||||
.map(MageObject::getName)
|
|
||||||
.filter(s -> !s.isEmpty())
|
|
||||||
.distinct()
|
|
||||||
.mapToInt(x -> 1)
|
|
||||||
.sum();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AudienceWithTrostaniValue copy() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage() {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "1";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ public final class AurraSingBaneOfJedi extends CardImpl {
|
||||||
ability.addTarget(new TargetCreaturePermanent());
|
ability.addTarget(new TargetCreaturePermanent());
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
|
|
||||||
// -4: Target player gets an emblem wiht "Whenever a nontoken creature you control leave the battlefied, discard a card.".
|
// -4: Target player gets an emblem with "Whenever a nontoken creature you control leave the battlefied, discard a card.".
|
||||||
ability = new LoyaltyAbility(new GetEmblemTargetPlayerEffect(new AurraSingBaneOfJediEmblem()), -4);
|
ability = new LoyaltyAbility(new GetEmblemTargetPlayerEffect(new AurraSingBaneOfJediEmblem()), -4);
|
||||||
ability.addTarget(new TargetPlayer());
|
ability.addTarget(new TargetPlayer());
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
|
|
|
||||||
52
Mage.Sets/src/mage/cards/a/AuxiliaryBoosters.java
Normal file
52
Mage.Sets/src/mage/cards/a/AuxiliaryBoosters.java
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
import mage.abilities.effects.common.CreateTokenAttachSourceEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
|
||||||
|
import mage.abilities.keyword.EquipAbility;
|
||||||
|
import mage.abilities.keyword.FlyingAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.AttachmentType;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.game.permanent.token.RobotToken;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class AuxiliaryBoosters extends CardImpl {
|
||||||
|
|
||||||
|
public AuxiliaryBoosters(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}{W}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.EQUIPMENT);
|
||||||
|
|
||||||
|
// When this Equipment enters, create a 2/2 colorless Robot artifact creature token and attach this Equipment to it.
|
||||||
|
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenAttachSourceEffect(new RobotToken())));
|
||||||
|
|
||||||
|
// Equipped creature gets +1/+2 and has flying.
|
||||||
|
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 2));
|
||||||
|
ability.addEffect(new GainAbilityAttachedEffect(
|
||||||
|
FlyingAbility.getInstance(), AttachmentType.EQUIPMENT
|
||||||
|
).setText("and has flying"));
|
||||||
|
this.addAbility(ability);
|
||||||
|
|
||||||
|
// Equip {3}
|
||||||
|
this.addAbility(new EquipAbility(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuxiliaryBoosters(final AuxiliaryBoosters card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuxiliaryBoosters copy() {
|
||||||
|
return new AuxiliaryBoosters(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,29 +1,25 @@
|
||||||
|
|
||||||
package mage.cards.a;
|
package mage.cards.a;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import mage.MageInt;
|
import mage.MageInt;
|
||||||
import mage.abilities.Ability;
|
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
import mage.abilities.dynamicvalue.DynamicValue;
|
import mage.abilities.dynamicvalue.common.DifferentlyNamedPermanentCount;
|
||||||
import mage.abilities.effects.Effect;
|
|
||||||
import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect;
|
import mage.abilities.effects.common.continuous.SetBasePowerToughnessSourceEffect;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.SubType;
|
import mage.constants.SubType;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.Game;
|
import mage.filter.StaticFilters;
|
||||||
import mage.game.permanent.Permanent;
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public final class AwakenedAmalgam extends CardImpl {
|
public final class AwakenedAmalgam extends CardImpl {
|
||||||
|
|
||||||
|
private static final DifferentlyNamedPermanentCount xValue = new DifferentlyNamedPermanentCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS);
|
||||||
|
|
||||||
public AwakenedAmalgam(UUID ownerId, CardSetInfo setInfo) {
|
public AwakenedAmalgam(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}");
|
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}");
|
||||||
|
|
||||||
|
|
@ -32,8 +28,9 @@ public final class AwakenedAmalgam extends CardImpl {
|
||||||
this.toughness = new MageInt(0);
|
this.toughness = new MageInt(0);
|
||||||
|
|
||||||
// Awakened Amalgam's power and toughness are each equal to the number of differently named lands you control.
|
// Awakened Amalgam's power and toughness are each equal to the number of differently named lands you control.
|
||||||
DynamicValue value = (new AwakenedAmalgamLandNamesCount());
|
this.addAbility(new SimpleStaticAbility(
|
||||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(value)));
|
Zone.ALL, new SetBasePowerToughnessSourceEffect(xValue)
|
||||||
|
).addHint(xValue.getHint()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private AwakenedAmalgam(final AwakenedAmalgam card) {
|
private AwakenedAmalgam(final AwakenedAmalgam card) {
|
||||||
|
|
@ -45,35 +42,3 @@ public final class AwakenedAmalgam extends CardImpl {
|
||||||
return new AwakenedAmalgam(this);
|
return new AwakenedAmalgam(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AwakenedAmalgamLandNamesCount implements DynamicValue {
|
|
||||||
|
|
||||||
public AwakenedAmalgamLandNamesCount() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
|
||||||
Set<String> landNames = new HashSet<>();
|
|
||||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(sourceAbility.getControllerId())) {
|
|
||||||
if (permanent.isLand(game)) {
|
|
||||||
landNames.add(permanent.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return landNames.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AwakenedAmalgamLandNamesCount copy() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "1";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage() {
|
|
||||||
return "differently named lands you control";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
66
Mage.Sets/src/mage/cards/b/BalothPrime.java
Normal file
66
Mage.Sets/src/mage/cards/b/BalothPrime.java
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
package mage.cards.b;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.EntersBattlefieldAbility;
|
||||||
|
import mage.abilities.common.SacrificePermanentTriggeredAbility;
|
||||||
|
import mage.abilities.common.SimpleActivatedAbility;
|
||||||
|
import mage.abilities.costs.common.SacrificeTargetCost;
|
||||||
|
import mage.abilities.costs.mana.GenericManaCost;
|
||||||
|
import mage.abilities.effects.common.CreateTokenEffect;
|
||||||
|
import mage.abilities.effects.common.GainLifeEffect;
|
||||||
|
import mage.abilities.effects.common.TapSourceEffect;
|
||||||
|
import mage.abilities.effects.common.UntapSourceEffect;
|
||||||
|
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import mage.filter.StaticFilters;
|
||||||
|
import mage.game.permanent.token.BeastToken2;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Susucr
|
||||||
|
*/
|
||||||
|
public final class BalothPrime extends CardImpl {
|
||||||
|
|
||||||
|
public BalothPrime(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.BEAST);
|
||||||
|
this.power = new MageInt(10);
|
||||||
|
this.toughness = new MageInt(10);
|
||||||
|
|
||||||
|
// This creature enters tapped with six stun counters on it. (If a permanent with a stun counter would become untapped, remove one from it instead.)
|
||||||
|
Ability ability = new EntersBattlefieldAbility(
|
||||||
|
new TapSourceEffect(true), "tapped with six stun counters on it. "
|
||||||
|
+ "<i>(If a permanent with a stun counter would become untapped, remove one from it instead.)</i>"
|
||||||
|
);
|
||||||
|
ability.addEffect(new AddCountersSourceEffect(CounterType.STUN.createInstance(6)));
|
||||||
|
this.addAbility(ability);
|
||||||
|
|
||||||
|
// Whenever you sacrifice a land, create a tapped 4/4 green Beast creature token and untap this creature.
|
||||||
|
ability = new SacrificePermanentTriggeredAbility(
|
||||||
|
new CreateTokenEffect(new BeastToken2(), 1, true), StaticFilters.FILTER_LAND
|
||||||
|
);
|
||||||
|
ability.addEffect(new UntapSourceEffect().concatBy("and"));
|
||||||
|
this.addAbility(ability);
|
||||||
|
|
||||||
|
// {4}, Sacrifice a land: You gain 2 life.
|
||||||
|
ability = new SimpleActivatedAbility(new GainLifeEffect(2), new GenericManaCost(4));
|
||||||
|
ability.addCost(new SacrificeTargetCost(StaticFilters.FILTER_LAND));
|
||||||
|
this.addAbility(ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BalothPrime(final BalothPrime card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BalothPrime copy() {
|
||||||
|
return new BalothPrime(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,14 +5,12 @@ import mage.abilities.Ability;
|
||||||
import mage.abilities.effects.common.DestroyTargetEffect;
|
import mage.abilities.effects.common.DestroyTargetEffect;
|
||||||
import mage.abilities.keyword.ChannelAbility;
|
import mage.abilities.keyword.ChannelAbility;
|
||||||
import mage.abilities.keyword.DefenderAbility;
|
import mage.abilities.keyword.DefenderAbility;
|
||||||
import mage.abilities.keyword.FlyingAbility;
|
|
||||||
import mage.abilities.keyword.ReachAbility;
|
import mage.abilities.keyword.ReachAbility;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.SubType;
|
import mage.constants.SubType;
|
||||||
import mage.filter.common.FilterCreaturePermanent;
|
import mage.filter.StaticFilters;
|
||||||
import mage.filter.predicate.mageobject.AbilityPredicate;
|
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -22,12 +20,6 @@ import java.util.UUID;
|
||||||
*/
|
*/
|
||||||
public final class BambooGroveArcher extends CardImpl {
|
public final class BambooGroveArcher extends CardImpl {
|
||||||
|
|
||||||
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature with flying");
|
|
||||||
|
|
||||||
static {
|
|
||||||
filter.add(new AbilityPredicate(FlyingAbility.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
public BambooGroveArcher(UUID ownerId, CardSetInfo setInfo) {
|
public BambooGroveArcher(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{1}{G}");
|
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{1}{G}");
|
||||||
|
|
||||||
|
|
@ -44,7 +36,7 @@ public final class BambooGroveArcher extends CardImpl {
|
||||||
|
|
||||||
// Channel — {4}{G}, Discard Bamboo Grove Archer: Destroy target creature with flying.
|
// Channel — {4}{G}, Discard Bamboo Grove Archer: Destroy target creature with flying.
|
||||||
Ability ability = new ChannelAbility("{4}{G}", new DestroyTargetEffect());
|
Ability ability = new ChannelAbility("{4}{G}", new DestroyTargetEffect());
|
||||||
ability.addTarget(new TargetPermanent(filter));
|
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CREATURE_FLYING));
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public final class BattleHymn extends CardImpl {
|
||||||
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}");
|
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{R}");
|
||||||
|
|
||||||
// Add {R} for each creature you control.
|
// Add {R} for each creature you control.
|
||||||
this.getSpellAbility().addEffect(new DynamicManaEffect(Mana.RedMana(1), CreaturesYouControlCount.instance));
|
this.getSpellAbility().addEffect(new DynamicManaEffect(Mana.RedMana(1), CreaturesYouControlCount.SINGULAR));
|
||||||
}
|
}
|
||||||
|
|
||||||
private BattleHymn(final BattleHymn card) {
|
private BattleHymn(final BattleHymn card) {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ public final class BattleSquadron extends CardImpl {
|
||||||
this.addAbility(FlyingAbility.getInstance());
|
this.addAbility(FlyingAbility.getInstance());
|
||||||
|
|
||||||
// Battle Squadron's power and toughness are each equal to the number of creatures you control.
|
// Battle Squadron's power and toughness are each equal to the number of creatures you control.
|
||||||
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.instance))
|
this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerToughnessSourceEffect(CreaturesYouControlCount.PLURAL))
|
||||||
.addHint(CreaturesYouControlHint.instance));
|
.addHint(CreaturesYouControlHint.instance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
39
Mage.Sets/src/mage/cards/b/BeamsawProspector.java
Normal file
39
Mage.Sets/src/mage/cards/b/BeamsawProspector.java
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package mage.cards.b;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.common.DiesSourceTriggeredAbility;
|
||||||
|
import mage.abilities.effects.common.CreateTokenEffect;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.game.permanent.token.LanderToken;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class BeamsawProspector extends CardImpl {
|
||||||
|
|
||||||
|
public BeamsawProspector(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.HUMAN);
|
||||||
|
this.subtype.add(SubType.ARTIFICER);
|
||||||
|
this.power = new MageInt(2);
|
||||||
|
this.toughness = new MageInt(1);
|
||||||
|
|
||||||
|
// When this creature dies, create a Lander token.
|
||||||
|
this.addAbility(new DiesSourceTriggeredAbility(new CreateTokenEffect(new LanderToken())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeamsawProspector(final BeamsawProspector card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BeamsawProspector copy() {
|
||||||
|
return new BeamsawProspector(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Mage.Sets/src/mage/cards/b/BeyondTheQuiet.java
Normal file
42
Mage.Sets/src/mage/cards/b/BeyondTheQuiet.java
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
package mage.cards.b;
|
||||||
|
|
||||||
|
import mage.abilities.effects.common.ExileAllEffect;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.filter.FilterPermanent;
|
||||||
|
import mage.filter.predicate.Predicates;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class BeyondTheQuiet extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterPermanent filter = new FilterPermanent("creatures and Spacecraft");
|
||||||
|
|
||||||
|
static {
|
||||||
|
filter.add(Predicates.or(
|
||||||
|
CardType.CREATURE.getPredicate(),
|
||||||
|
SubType.SPACECRAFT.getPredicate()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BeyondTheQuiet(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{W}{W}");
|
||||||
|
|
||||||
|
// Exile all creatures and Spacecraft.
|
||||||
|
this.getSpellAbility().addEffect(new ExileAllEffect(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeyondTheQuiet(final BeyondTheQuiet card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BeyondTheQuiet copy() {
|
||||||
|
return new BeyondTheQuiet(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
144
Mage.Sets/src/mage/cards/b/BioengineeredFuture.java
Normal file
144
Mage.Sets/src/mage/cards/b/BioengineeredFuture.java
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
package mage.cards.b;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
import mage.abilities.effects.ReplacementEffectImpl;
|
||||||
|
import mage.abilities.effects.common.CreateTokenEffect;
|
||||||
|
import mage.abilities.hint.Hint;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.Duration;
|
||||||
|
import mage.constants.Outcome;
|
||||||
|
import mage.constants.WatcherScope;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.events.EntersTheBattlefieldEvent;
|
||||||
|
import mage.game.events.GameEvent;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
import mage.game.permanent.token.LanderToken;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
import mage.watchers.Watcher;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class BioengineeredFuture extends CardImpl {
|
||||||
|
|
||||||
|
public BioengineeredFuture(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}{G}");
|
||||||
|
|
||||||
|
// When this enchantment enters, create a Lander token.
|
||||||
|
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new LanderToken())));
|
||||||
|
|
||||||
|
// Each creature you control enters with an additional +1/+1 counter on it for each land that entered the battlefield under your control this turn.
|
||||||
|
this.addAbility(new SimpleStaticAbility(new BioengineeredFutureEffect())
|
||||||
|
.addHint(BioengineeredFutureHint.instance), new BioengineeredFutureWatcher());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BioengineeredFuture(final BioengineeredFuture card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BioengineeredFuture copy() {
|
||||||
|
return new BioengineeredFuture(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BioengineeredFutureEffect extends ReplacementEffectImpl {
|
||||||
|
|
||||||
|
BioengineeredFutureEffect() {
|
||||||
|
super(Duration.WhileOnBattlefield, Outcome.BoostCreature);
|
||||||
|
staticText = "each creature you control enters with an additional +1/+1 counter on it " +
|
||||||
|
"for each land that entered the battlefield under your control this turn";
|
||||||
|
}
|
||||||
|
|
||||||
|
private BioengineeredFutureEffect(final BioengineeredFutureEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BioengineeredFutureEffect copy() {
|
||||||
|
return new BioengineeredFutureEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checksEventType(GameEvent event, Game game) {
|
||||||
|
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||||
|
Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget();
|
||||||
|
return permanent != null
|
||||||
|
&& permanent.isControlledBy(source.getControllerId())
|
||||||
|
&& permanent.isCreature(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
|
||||||
|
Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget();
|
||||||
|
int count = BioengineeredFutureWatcher.getCount(game, source);
|
||||||
|
if (creature != null && count > 0) {
|
||||||
|
creature.addCounters(
|
||||||
|
CounterType.P1P1.createInstance(count), source.getControllerId(),
|
||||||
|
source, game, event.getAppliedEffects()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BioengineeredFutureWatcher extends Watcher {
|
||||||
|
|
||||||
|
private final Map<UUID, Integer> map = new HashMap<>();
|
||||||
|
|
||||||
|
BioengineeredFutureWatcher() {
|
||||||
|
super(WatcherScope.GAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void watch(GameEvent event, Game game) {
|
||||||
|
if (event.getType() != GameEvent.EventType.ENTERS_THE_BATTLEFIELD) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Permanent permanent = ((EntersTheBattlefieldEvent) event).getTarget();
|
||||||
|
if (permanent != null && permanent.isLand(game)) {
|
||||||
|
map.compute(permanent.getControllerId(), CardUtil::setOrIncrementValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getCount(Game game, Ability source) {
|
||||||
|
return game
|
||||||
|
.getState()
|
||||||
|
.getWatcher(BioengineeredFutureWatcher.class)
|
||||||
|
.map
|
||||||
|
.getOrDefault(source.getControllerId(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BioengineeredFutureHint implements Hint {
|
||||||
|
instance;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText(Game game, Ability ability) {
|
||||||
|
return "Lands that entered under your control this turn: " + BioengineeredFutureWatcher.getCount(game, ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Hint copy() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Mage.Sets/src/mage/cards/b/BiomechanEngineer.java
Normal file
49
Mage.Sets/src/mage/cards/b/BiomechanEngineer.java
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package mage.cards.b;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.common.SimpleActivatedAbility;
|
||||||
|
import mage.abilities.costs.mana.GenericManaCost;
|
||||||
|
import mage.abilities.effects.common.CreateTokenEffect;
|
||||||
|
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.game.permanent.token.LanderToken;
|
||||||
|
import mage.game.permanent.token.RobotToken;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class BiomechanEngineer extends CardImpl {
|
||||||
|
|
||||||
|
public BiomechanEngineer(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{U}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.INSECT);
|
||||||
|
this.subtype.add(SubType.ARTIFICER);
|
||||||
|
this.power = new MageInt(2);
|
||||||
|
this.toughness = new MageInt(2);
|
||||||
|
|
||||||
|
// When this creature enters, create a Lander token.
|
||||||
|
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new LanderToken())));
|
||||||
|
|
||||||
|
// {8}: Draw two cards and create a 2/2 colorless Robot artifact creature token.
|
||||||
|
Ability ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(2), new GenericManaCost(8));
|
||||||
|
ability.addEffect(new CreateTokenEffect(new RobotToken()).concatBy("and"));
|
||||||
|
this.addAbility(ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BiomechanEngineer(final BiomechanEngineer card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiomechanEngineer copy() {
|
||||||
|
return new BiomechanEngineer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Mage.Sets/src/mage/cards/b/BiosynthicBurst.java
Normal file
42
Mage.Sets/src/mage/cards/b/BiosynthicBurst.java
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
package mage.cards.b;
|
||||||
|
|
||||||
|
import mage.abilities.effects.common.UntapTargetEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||||
|
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
|
||||||
|
import mage.abilities.keyword.IndestructibleAbility;
|
||||||
|
import mage.abilities.keyword.ReachAbility;
|
||||||
|
import mage.abilities.keyword.TrampleAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import mage.target.common.TargetControlledCreaturePermanent;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Susucr
|
||||||
|
*/
|
||||||
|
public final class BiosynthicBurst extends CardImpl {
|
||||||
|
|
||||||
|
public BiosynthicBurst(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}");
|
||||||
|
|
||||||
|
// Put a +1/+1 counter on target creature you control. It gains reach, trample, and indestructible until end of turn. Untap it.
|
||||||
|
this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance()));
|
||||||
|
this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent());
|
||||||
|
this.getSpellAbility().addEffect(new GainAbilityTargetEffect(ReachAbility.getInstance()).setText("it gains reach"));
|
||||||
|
this.getSpellAbility().addEffect(new GainAbilityTargetEffect(TrampleAbility.getInstance()).setText(", trample"));
|
||||||
|
this.getSpellAbility().addEffect(new GainAbilityTargetEffect(IndestructibleAbility.getInstance()).setText(", and indestructible until end of turn"));
|
||||||
|
this.getSpellAbility().addEffect(new UntapTargetEffect("untap it"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BiosynthicBurst(final BiosynthicBurst card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiosynthicBurst copy() {
|
||||||
|
return new BiosynthicBurst(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Mage.Sets/src/mage/cards/b/BiotechSpecialist.java
Normal file
51
Mage.Sets/src/mage/cards/b/BiotechSpecialist.java
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
package mage.cards.b;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.common.SacrificePermanentTriggeredAbility;
|
||||||
|
import mage.abilities.effects.common.CreateTokenEffect;
|
||||||
|
import mage.abilities.effects.common.DamageTargetEffect;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.filter.StaticFilters;
|
||||||
|
import mage.game.permanent.token.LanderToken;
|
||||||
|
import mage.target.common.TargetOpponent;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class BiotechSpecialist extends CardImpl {
|
||||||
|
|
||||||
|
public BiotechSpecialist(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{G}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.INSECT);
|
||||||
|
this.subtype.add(SubType.SCIENTIST);
|
||||||
|
this.power = new MageInt(1);
|
||||||
|
this.toughness = new MageInt(3);
|
||||||
|
|
||||||
|
// When this creature enters, create a Lander token.
|
||||||
|
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new LanderToken())));
|
||||||
|
|
||||||
|
// Whenever you sacrifice an artifact, this creature deals 2 damage to target opponent.
|
||||||
|
Ability ability = new SacrificePermanentTriggeredAbility(
|
||||||
|
new DamageTargetEffect(2), StaticFilters.FILTER_PERMANENT_ARTIFACT
|
||||||
|
);
|
||||||
|
ability.addTarget(new TargetOpponent());
|
||||||
|
this.addAbility(ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BiotechSpecialist(final BiotechSpecialist card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BiotechSpecialist copy() {
|
||||||
|
return new BiotechSpecialist(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
60
Mage.Sets/src/mage/cards/b/BladeOfTheSwarm.java
Normal file
60
Mage.Sets/src/mage/cards/b/BladeOfTheSwarm.java
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
package mage.cards.b;
|
||||||
|
|
||||||
|
import mage.MageInt;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.Mode;
|
||||||
|
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||||
|
import mage.abilities.effects.common.PutOnLibraryTargetEffect;
|
||||||
|
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||||
|
import mage.abilities.keyword.WarpAbility;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import mage.filter.FilterCard;
|
||||||
|
import mage.filter.predicate.mageobject.AbilityPredicate;
|
||||||
|
import mage.target.common.TargetCardInExile;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public final class BladeOfTheSwarm extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterCard filter = new FilterCard("exiled card with warp");
|
||||||
|
|
||||||
|
static {
|
||||||
|
filter.add(new AbilityPredicate(WarpAbility.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BladeOfTheSwarm(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.INSECT);
|
||||||
|
this.subtype.add(SubType.ASSASSIN);
|
||||||
|
this.power = new MageInt(3);
|
||||||
|
this.toughness = new MageInt(1);
|
||||||
|
|
||||||
|
// When this creature enters, choose one --
|
||||||
|
// * Put two +1/+1 counters on this creature.
|
||||||
|
Ability ability = new EntersBattlefieldTriggeredAbility(
|
||||||
|
new AddCountersSourceEffect(CounterType.P1P1.createInstance(2))
|
||||||
|
);
|
||||||
|
|
||||||
|
// * Put target exiled card with warp on the bottom of its owner's library.
|
||||||
|
ability.addMode(new Mode(new PutOnLibraryTargetEffect(false))
|
||||||
|
.addTarget(new TargetCardInExile(filter)));
|
||||||
|
this.addAbility(ability);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BladeOfTheSwarm(final BladeOfTheSwarm card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BladeOfTheSwarm copy() {
|
||||||
|
return new BladeOfTheSwarm(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
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