mirror of
https://github.com/correl/mage.git
synced 2024-11-15 03:00:16 +00:00
[IKO] Implement Companion and 2 companions
Keruga, the Macrosage and Umori, the Collector
This commit is contained in:
parent
395ae9ec11
commit
c3684a732b
21 changed files with 866 additions and 128 deletions
|
@ -30,7 +30,7 @@ public class CardInfoWindowDialog extends MageDialog {
|
|||
private static final Logger LOGGER = Logger.getLogger(CardInfoWindowDialog.class);
|
||||
|
||||
public enum ShowType {
|
||||
REVEAL, REVEAL_TOP_LIBRARY, LOOKED_AT, EXILE, GRAVEYARD, OTHER
|
||||
REVEAL, REVEAL_TOP_LIBRARY, LOOKED_AT, EXILE, GRAVEYARD, COMPANION, OTHER
|
||||
}
|
||||
|
||||
private final ShowType showType;
|
||||
|
@ -72,6 +72,10 @@ public class CardInfoWindowDialog extends MageDialog {
|
|||
case EXILE:
|
||||
this.setFrameIcon(new ImageIcon(ImageManagerImpl.instance.getExileImage()));
|
||||
break;
|
||||
case COMPANION:
|
||||
this.setFrameIcon(new ImageIcon(ImageManagerImpl.instance.getTokenIconImage()));
|
||||
this.setClosable(false);
|
||||
break;
|
||||
default:
|
||||
// no icon yet
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import javax.swing.plaf.basic.BasicSplitPaneDivider;
|
|||
import javax.swing.plaf.basic.BasicSplitPaneUI;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.beans.PropertyVetoException;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
@ -74,6 +75,7 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
private final Map<String, CardInfoWindowDialog> revealed = new HashMap<>();
|
||||
private final Map<String, CardInfoWindowDialog> lookedAt = new HashMap<>();
|
||||
private final Map<String, CardInfoWindowDialog> graveyardWindows = new HashMap<>();
|
||||
private final Map<String, CardInfoWindowDialog> companion = new HashMap<>();
|
||||
private final Map<String, CardsView> graveyards = new HashMap<>();
|
||||
|
||||
private final ArrayList<ShowCardsDialog> pickTarget = new ArrayList<>();
|
||||
|
@ -241,6 +243,10 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
lookedAtDialog.cleanUp();
|
||||
lookedAtDialog.removeDialog();
|
||||
}
|
||||
for (CardInfoWindowDialog companionDialog : companion.values()) {
|
||||
companionDialog.cleanUp();
|
||||
companionDialog.removeDialog();
|
||||
}
|
||||
for (ShowCardsDialog pickTargetDialog : pickTarget) {
|
||||
pickTargetDialog.cleanUp();
|
||||
pickTargetDialog.removeDialog();
|
||||
|
@ -275,6 +281,9 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
for (CardInfoWindowDialog cardInfoWindowDialog : lookedAt.values()) {
|
||||
cardInfoWindowDialog.changeGUISize();
|
||||
}
|
||||
for (CardInfoWindowDialog cardInfoWindowDialog : companion.values()) {
|
||||
cardInfoWindowDialog.changeGUISize();
|
||||
}
|
||||
for (CardInfoWindowDialog cardInfoWindowDialog : graveyardWindows.values()) {
|
||||
cardInfoWindowDialog.changeGUISize();
|
||||
}
|
||||
|
@ -781,6 +790,7 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
|
||||
showRevealed(game);
|
||||
showLookedAt(game);
|
||||
showCompanion(game);
|
||||
if (!game.getCombat().isEmpty()) {
|
||||
CombatManager.instance.showCombat(game.getCombat(), gameId);
|
||||
} else {
|
||||
|
@ -1085,6 +1095,9 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
for (CardInfoWindowDialog lookedAtDialog : lookedAt.values()) {
|
||||
lookedAtDialog.hideDialog();
|
||||
}
|
||||
for (CardInfoWindowDialog companionDialog : companion.values()) {
|
||||
companionDialog.hideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
// Called if the game frame comes to front again
|
||||
|
@ -1102,6 +1115,9 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
for (CardInfoWindowDialog lookedAtDialog : lookedAt.values()) {
|
||||
lookedAtDialog.show();
|
||||
}
|
||||
for (CardInfoWindowDialog companionDialog : companion.values()) {
|
||||
companionDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
public void openGraveyardWindow(String playerName) {
|
||||
|
@ -1146,6 +1162,23 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
removeClosedCardInfoWindows(lookedAt);
|
||||
}
|
||||
|
||||
private void showCompanion(GameView game) {
|
||||
for (RevealedView revealView : game.getCompanion()) {
|
||||
handleGameInfoWindow(companion, ShowType.COMPANION, revealView.getName(), revealView.getCards());
|
||||
}
|
||||
// Close the companion view if not in the game view
|
||||
companion.forEach((name, companionDialog) -> {
|
||||
if (game.getCompanion().stream().noneMatch(revealedView -> revealedView.getName().equals(name))) {
|
||||
try {
|
||||
companionDialog.setClosed(true);
|
||||
} catch (PropertyVetoException e) {
|
||||
logger.error("Couldn't close companion dialog", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
removeClosedCardInfoWindows(companion);
|
||||
}
|
||||
|
||||
private void handleGameInfoWindow(Map<String, CardInfoWindowDialog> windowMap, ShowType showType, String name, LinkedHashMap cardsView) {
|
||||
CardInfoWindowDialog cardInfoWindowDialog;
|
||||
if (!windowMap.containsKey(name)) {
|
||||
|
@ -1160,6 +1193,7 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
switch (showType) {
|
||||
case REVEAL:
|
||||
case REVEAL_TOP_LIBRARY:
|
||||
case COMPANION:
|
||||
cardInfoWindowDialog.loadCards((CardsView) cardsView, bigCard, gameId);
|
||||
break;
|
||||
case LOOKED_AT:
|
||||
|
@ -1332,6 +1366,22 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
}
|
||||
}
|
||||
|
||||
// companion
|
||||
for (RevealedView rev : gameView.getCompanion()) {
|
||||
for (Map.Entry<UUID, CardView> card : rev.getCards().entrySet()) {
|
||||
if (needSelectable.contains(card.getKey())) {
|
||||
card.getValue().setChoosable(true);
|
||||
}
|
||||
if (needChoosen.contains(card.getKey())) {
|
||||
card.getValue().setSelected(true);
|
||||
}
|
||||
if (needPlayable.containsKey(card.getKey())) {
|
||||
card.getValue().setPlayable(true);
|
||||
card.getValue().setPlayableAmount(needPlayable.get(card.getKey()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// looked at
|
||||
for (LookedAtView look : gameView.getLookedAt()) {
|
||||
for (Map.Entry<UUID, SimpleCardView> card : look.getCards().entrySet()) {
|
||||
|
|
|
@ -49,6 +49,7 @@ public class GameView implements Serializable {
|
|||
private final List<ExileView> exiles = new ArrayList<>();
|
||||
private final List<RevealedView> revealed = new ArrayList<>();
|
||||
private List<LookedAtView> lookedAt = new ArrayList<>();
|
||||
private final List<RevealedView> companion = new ArrayList<>();
|
||||
private final List<CombatGroupView> combat = new ArrayList<>();
|
||||
private final TurnPhase phase;
|
||||
private final PhaseStep step;
|
||||
|
@ -149,6 +150,12 @@ public class GameView implements Serializable {
|
|||
for (String name : state.getRevealed().keySet()) {
|
||||
revealed.add(new RevealedView(name, state.getRevealed().get(name), game));
|
||||
}
|
||||
for (String name : state.getCompanion().keySet()) {
|
||||
// Only show the companion window when the companion is still outside the game.
|
||||
if (state.getCompanion().get(name).stream().anyMatch(cardId -> state.getZone(cardId) == Zone.OUTSIDE)) {
|
||||
companion.add(new RevealedView(name, state.getCompanion().get(name), game));
|
||||
}
|
||||
}
|
||||
this.phase = state.getTurn().getPhaseType();
|
||||
this.step = state.getTurn().getStepType();
|
||||
this.turn = state.getTurnNum();
|
||||
|
@ -266,6 +273,10 @@ public class GameView implements Serializable {
|
|||
return lookedAt;
|
||||
}
|
||||
|
||||
public List<RevealedView> getCompanion() {
|
||||
return companion;
|
||||
}
|
||||
|
||||
public void setLookedAt(List<LookedAtView> list) {
|
||||
this.lookedAt = list;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package mage.deck;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.CanBeYourCommanderAbility;
|
||||
import mage.abilities.keyword.CompanionAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.decks.Constructed;
|
||||
import mage.cards.decks.Deck;
|
||||
|
@ -39,9 +41,50 @@ public class Brawl extends Constructed {
|
|||
@Override
|
||||
public boolean validate(Deck deck) {
|
||||
boolean valid = true;
|
||||
Card brawler = null;
|
||||
Card companion = null;
|
||||
FilterMana colorIdentity = new FilterMana();
|
||||
|
||||
if (deck.getCards().size() + deck.getSideboard().size() != getDeckMinSize()) {
|
||||
if (deck.getSideboard().size() == 1) {
|
||||
for (Card card : deck.getSideboard()) {
|
||||
brawler = card;
|
||||
}
|
||||
} else if (deck.getSideboard().size() == 2) {
|
||||
Iterator<Card> iter = deck.getSideboard().iterator();
|
||||
Card card1 = iter.next();
|
||||
Card card2 = iter.next();
|
||||
if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card1;
|
||||
brawler = card2;
|
||||
} else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card2;
|
||||
brawler = card1;
|
||||
} else {
|
||||
invalid.put("Brawl", "Sideboard must contain only the brawler and up to 1 companion");
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
invalid.put("Brawl", "Sideboard must contain only the brawler and up to 1 companion");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (brawler != null) {
|
||||
ManaUtil.collectColorIdentity(colorIdentity, brawler.getColorIdentity());
|
||||
if (bannedCommander.contains(brawler.getName())) {
|
||||
invalid.put("Brawl", "Brawler banned (" + brawler.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
if (!((brawler.isCreature() && brawler.isLegendary())
|
||||
|| brawler.isPlaneswalker() || brawler.getAbilities().contains(CanBeYourCommanderAbility.getInstance()))) {
|
||||
invalid.put("Brawl", "Invalid Brawler (" + brawler.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (companion != null && deck.getCards().size() + deck.getSideboard().size() != getDeckMinSize() + 1) {
|
||||
invalid.put("Deck", "Must contain " + (getDeckMinSize() + 1) + " cards (companion doesn't count in deck size requirement): has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards");
|
||||
valid = false;
|
||||
} else if (companion == null && deck.getCards().size() + deck.getSideboard().size() != getDeckMinSize()) {
|
||||
invalid.put("Deck", "Must contain " + getDeckMinSize() + " cards: has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards");
|
||||
valid = false;
|
||||
}
|
||||
|
@ -58,23 +101,6 @@ public class Brawl extends Constructed {
|
|||
}
|
||||
}
|
||||
|
||||
if (deck.getSideboard().size() != 1) {
|
||||
invalid.put("Brawl", "Sideboard must contain only the commander)");
|
||||
valid = false;
|
||||
} else {
|
||||
for (Card commander : deck.getSideboard()) {
|
||||
if (bannedCommander.contains(commander.getName())) {
|
||||
invalid.put("Brawl", "Brawl banned (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
if (!((commander.isCreature() && commander.isLegendary())
|
||||
|| commander.isPlaneswalker() || commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance()))) {
|
||||
invalid.put("Brawl", "Invalid Commander (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity());
|
||||
}
|
||||
}
|
||||
Set<String> basicsInDeck = new HashSet<>();
|
||||
if (colorIdentity.isColorless()) {
|
||||
for (Card card : deck.getCards()) {
|
||||
|
@ -92,6 +118,15 @@ public class Brawl extends Constructed {
|
|||
valid = false;
|
||||
}
|
||||
}
|
||||
for (Card card : deck.getSideboard()) {
|
||||
if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())
|
||||
&& !(colorIdentity.isColorless()
|
||||
&& basicsInDeck.size() == 1
|
||||
&& basicsInDeck.contains(card.getName()))) {
|
||||
invalid.put(card.getName(), "Invalid color (" + colorIdentity.toString() + ')');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
for (Card card : deck.getCards()) {
|
||||
if (!isSetAllowed(card.getExpansionSetCode())) {
|
||||
if (!legalSets(card)) {
|
||||
|
@ -108,7 +143,22 @@ public class Brawl extends Constructed {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Check for companion legality
|
||||
if (companion != null) {
|
||||
Set<Card> cards = new HashSet<>(deck.getCards());
|
||||
cards.add(brawler);
|
||||
for (Ability ability : companion.getAbilities()) {
|
||||
if (ability instanceof CompanionAbility) {
|
||||
CompanionAbility companionAbility = (CompanionAbility) ability;
|
||||
if (!companionAbility.isLegal(cards)) {
|
||||
invalid.put(companion.getName(), "Deck invalid for companion");
|
||||
valid = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import mage.ObjectColor;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.CanBeYourCommanderAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.keyword.CompanionAbility;
|
||||
import mage.abilities.keyword.PartnerAbility;
|
||||
import mage.abilities.keyword.PartnerWithAbility;
|
||||
import mage.cards.Card;
|
||||
|
@ -91,8 +92,55 @@ public class Commander extends Constructed {
|
|||
public boolean validate(Deck deck) {
|
||||
boolean valid = true;
|
||||
FilterMana colorIdentity = new FilterMana();
|
||||
Set<Card> commanders = new HashSet<>();
|
||||
Card companion = null;
|
||||
|
||||
if (deck.getCards().size() + deck.getSideboard().size() != 100) {
|
||||
if (deck.getSideboard().size() == 1) {
|
||||
commanders.add(deck.getSideboard().iterator().next());
|
||||
} else if (deck.getSideboard().size() == 2) {
|
||||
Iterator<Card> iter = deck.getSideboard().iterator();
|
||||
Card card1 = iter.next();
|
||||
Card card2 = iter.next();
|
||||
if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card1;
|
||||
commanders.add(card2);
|
||||
} else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card2;
|
||||
commanders.add(card1);
|
||||
} else {
|
||||
commanders.add(card1);
|
||||
commanders.add(card2);
|
||||
}
|
||||
} else if (deck.getSideboard().size() == 3) {
|
||||
Iterator<Card> iter = deck.getSideboard().iterator();
|
||||
Card card1 = iter.next();
|
||||
Card card2 = iter.next();
|
||||
Card card3 = iter.next();
|
||||
if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card1;
|
||||
commanders.add(card2);
|
||||
commanders.add(card3);
|
||||
} else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card2;
|
||||
commanders.add(card1);
|
||||
commanders.add(card3);
|
||||
} else if (card3.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card3;
|
||||
commanders.add(card1);
|
||||
commanders.add(card2);
|
||||
} else {
|
||||
invalid.put("Commander", "Sideboard must contain only the commander(s) and up to 1 companion");
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
invalid.put("Commander", "Sideboard must contain only the commander(s) and up to 1 companion");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (companion != null && deck.getCards().size() + deck.getSideboard().size() != 101) {
|
||||
invalid.put("Deck", "Must contain " + 101 + " cards (companion doesn't count for deck size): has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards");
|
||||
valid = false;
|
||||
} else if (companion == null && deck.getCards().size() + deck.getSideboard().size() != 100) {
|
||||
invalid.put("Deck", "Must contain " + 100 + " cards: has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards");
|
||||
valid = false;
|
||||
}
|
||||
|
@ -109,48 +157,40 @@ public class Commander extends Constructed {
|
|||
}
|
||||
}
|
||||
|
||||
if (deck.getSideboard().isEmpty() || deck.getSideboard().size() > 2) {
|
||||
if ((deck.getSideboard().size() > 1 && !partnerAllowed)) {
|
||||
invalid.put("Commander", "You may only have one commander");
|
||||
Set<String> commanderNames = new HashSet<>();
|
||||
for (Card commander : commanders) {
|
||||
commanderNames.add(commander.getName());
|
||||
}
|
||||
for (Card commander : commanders) {
|
||||
if (bannedCommander.contains(commander.getName())) {
|
||||
invalid.put("Commander", "Commander banned (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
invalid.put("Commander", "Sideboard must contain only the commander(s)");
|
||||
valid = false;
|
||||
} else {
|
||||
Set<String> commanderNames = new HashSet<>();
|
||||
for (Card commander : deck.getSideboard()) {
|
||||
commanderNames.add(commander.getName());
|
||||
if ((!commander.isCreature() || !commander.isLegendary())
|
||||
&& (!commander.isPlaneswalker() || !commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance()))) {
|
||||
invalid.put("Commander", "Commander invalid (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
for (Card commander : deck.getSideboard()) {
|
||||
if (bannedCommander.contains(commander.getName())) {
|
||||
invalid.put("Commander", "Commander banned (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
if ((!commander.isCreature() || !commander.isLegendary())
|
||||
&& (!commander.isPlaneswalker() || !commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance()))) {
|
||||
invalid.put("Commander", "Commander invalid (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
if (deck.getSideboard().size() == 2) {
|
||||
if (commander.getAbilities().contains(PartnerAbility.getInstance())) {
|
||||
if (bannedPartner.contains(commander.getName())) {
|
||||
invalid.put("Commander", "Partner banned (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
boolean partnersWith = commander.getAbilities()
|
||||
.stream()
|
||||
.filter(PartnerWithAbility.class::isInstance)
|
||||
.map(PartnerWithAbility.class::cast)
|
||||
.map(PartnerWithAbility::getPartnerName)
|
||||
.anyMatch(commanderNames::contains);
|
||||
if (!partnersWith) {
|
||||
invalid.put("Commander", "Commander without Partner (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
if (commanders.size() == 2) {
|
||||
if (commander.getAbilities().contains(PartnerAbility.getInstance())) {
|
||||
if (bannedPartner.contains(commander.getName())) {
|
||||
invalid.put("Commander", "Partner banned (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
boolean partnersWith = commander.getAbilities()
|
||||
.stream()
|
||||
.filter(PartnerWithAbility.class::isInstance)
|
||||
.map(PartnerWithAbility.class::cast)
|
||||
.map(PartnerWithAbility::getPartnerName)
|
||||
.anyMatch(commanderNames::contains);
|
||||
if (!partnersWith) {
|
||||
invalid.put("Commander", "Commander without Partner (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity());
|
||||
}
|
||||
ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity());
|
||||
}
|
||||
|
||||
// no needs in cards check on wrong commanders
|
||||
|
@ -164,6 +204,12 @@ public class Commander extends Constructed {
|
|||
valid = false;
|
||||
}
|
||||
}
|
||||
for (Card card : deck.getSideboard()) {
|
||||
if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) {
|
||||
invalid.put(card.getName(), "Invalid color (" + colorIdentity.toString() + ')');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
for (Card card : deck.getCards()) {
|
||||
if (!isSetAllowed(card.getExpansionSetCode())) {
|
||||
if (!legalSets(card)) {
|
||||
|
@ -180,6 +226,21 @@ public class Commander extends Constructed {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Check for companion legality
|
||||
if (companion != null) {
|
||||
Set<Card> cards = new HashSet<>(deck.getCards());
|
||||
cards.addAll(commanders);
|
||||
for (Ability ability : companion.getAbilities()) {
|
||||
if (ability instanceof CompanionAbility) {
|
||||
CompanionAbility companionAbility = (CompanionAbility) ability;
|
||||
if (!companionAbility.isLegal(cards)) {
|
||||
invalid.put(companion.getName(), "Deck invalid for companion");
|
||||
valid = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package mage.deck;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.keyword.CompanionAbility;
|
||||
import mage.abilities.keyword.PartnerAbility;
|
||||
import mage.abilities.keyword.PartnerWithAbility;
|
||||
import mage.cards.Card;
|
||||
|
@ -49,8 +50,55 @@ public class FreeformCommander extends Constructed {
|
|||
public boolean validate(Deck deck) {
|
||||
boolean valid = true;
|
||||
FilterMana colorIdentity = new FilterMana();
|
||||
Set<Card> commanders = new HashSet<>();
|
||||
Card companion = null;
|
||||
|
||||
if (deck.getCards().size() + deck.getSideboard().size() != 100) {
|
||||
if (deck.getSideboard().size() == 1) {
|
||||
commanders.add(deck.getSideboard().iterator().next());
|
||||
} else if (deck.getSideboard().size() == 2) {
|
||||
Iterator<Card> iter = deck.getSideboard().iterator();
|
||||
Card card1 = iter.next();
|
||||
Card card2 = iter.next();
|
||||
if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card1;
|
||||
commanders.add(card2);
|
||||
} else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card2;
|
||||
commanders.add(card1);
|
||||
} else {
|
||||
commanders.add(card1);
|
||||
commanders.add(card2);
|
||||
}
|
||||
} else if (deck.getSideboard().size() == 3) {
|
||||
Iterator<Card> iter = deck.getSideboard().iterator();
|
||||
Card card1 = iter.next();
|
||||
Card card2 = iter.next();
|
||||
Card card3 = iter.next();
|
||||
if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card1;
|
||||
commanders.add(card2);
|
||||
commanders.add(card3);
|
||||
} else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card2;
|
||||
commanders.add(card1);
|
||||
commanders.add(card3);
|
||||
} else if (card3.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card3;
|
||||
commanders.add(card1);
|
||||
commanders.add(card2);
|
||||
} else {
|
||||
invalid.put("Commander", "Sideboard must contain only the commander(s) and up to 1 companion");
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
invalid.put("Commander", "Sideboard must contain only the commander(s) and up to 1 companion");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (companion != null && deck.getCards().size() + deck.getSideboard().size() != 101) {
|
||||
invalid.put("Deck", "Must contain " + 101 + " cards (companion doesn't count for deck size): has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards");
|
||||
valid = false;
|
||||
} else if (companion == null && deck.getCards().size() + deck.getSideboard().size() != 100) {
|
||||
invalid.put("Deck", "Must contain " + 100 + " cards: has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards");
|
||||
valid = false;
|
||||
}
|
||||
|
@ -58,39 +106,32 @@ public class FreeformCommander extends Constructed {
|
|||
Map<String, Integer> counts = new HashMap<>();
|
||||
countCards(counts, deck.getCards());
|
||||
countCards(counts, deck.getSideboard());
|
||||
|
||||
valid = checkCounts(1, counts) && valid;
|
||||
|
||||
if (deck.getSideboard().isEmpty() || deck.getSideboard().size() > 2) {
|
||||
invalid.put("Commander", "Sideboard must contain only the commander(s)");
|
||||
valid = false;
|
||||
} else {
|
||||
Set<String> commanderNames = new HashSet<>();
|
||||
for (Card commander : deck.getSideboard()) {
|
||||
commanderNames.add(commander.getName());
|
||||
Set<String> commanderNames = new HashSet<>();
|
||||
for (Card commander : commanders) {
|
||||
commanderNames.add(commander.getName());
|
||||
}
|
||||
for (Card commander : commanders) {
|
||||
if (!commander.isCreature() || !commander.isLegendary()) {
|
||||
invalid.put("Commander", "For Freeform Commander, the commander must be a creature or be legendary. Yours was: " + commander.getName());
|
||||
valid = false;
|
||||
}
|
||||
for (Card commander : deck.getSideboard()) {
|
||||
if (!(commander.isCreature()
|
||||
|| commander.isLegendary())) {
|
||||
invalid.put("Commander", "For Freeform Commander, the commander must be a creature or be legendary. Yours was: " + commander.getName());
|
||||
valid = false;
|
||||
}
|
||||
if (deck.getSideboard().size() == 2 && !commander.getAbilities().contains(PartnerAbility.getInstance())) {
|
||||
boolean partnersWith = false;
|
||||
for (Ability ability : commander.getAbilities()) {
|
||||
if (ability instanceof PartnerWithAbility
|
||||
&& commanderNames.contains(((PartnerWithAbility) ability).getPartnerName())) {
|
||||
partnersWith = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (commanders.size() == 2) {
|
||||
if (!commander.getAbilities().contains(PartnerAbility.getInstance())) {
|
||||
boolean partnersWith = commander.getAbilities()
|
||||
.stream()
|
||||
.filter(PartnerWithAbility.class::isInstance)
|
||||
.map(PartnerWithAbility.class::cast)
|
||||
.map(PartnerWithAbility::getPartnerName)
|
||||
.anyMatch(commanderNames::contains);
|
||||
if (!partnersWith) {
|
||||
invalid.put("Commander", "Commander without Partner (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity());
|
||||
}
|
||||
ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity());
|
||||
}
|
||||
|
||||
// no needs in cards check on wrong commanders
|
||||
|
@ -104,12 +145,24 @@ public class FreeformCommander extends Constructed {
|
|||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (Card card : deck.getSideboard()) {
|
||||
if (!isSetAllowed(card.getExpansionSetCode())) {
|
||||
if (!legalSets(card)) {
|
||||
invalid.put(card.getName(), "Not allowed Set: " + card.getExpansionSetCode());
|
||||
valid = false;
|
||||
if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) {
|
||||
invalid.put(card.getName(), "Invalid color (" + colorIdentity.toString() + ')');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
// Check for companion legality
|
||||
if (companion != null) {
|
||||
Set<Card> cards = new HashSet<>(deck.getCards());
|
||||
cards.addAll(commanders);
|
||||
for (Ability ability : companion.getAbilities()) {
|
||||
if (ability instanceof CompanionAbility) {
|
||||
CompanionAbility companionAbility = (CompanionAbility) ability;
|
||||
if (!companionAbility.isLegal(cards)) {
|
||||
invalid.put(companion.getName(), "Deck invalid for companion");
|
||||
valid = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package mage.deck;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.CanBeYourCommanderAbility;
|
||||
import mage.abilities.keyword.CompanionAbility;
|
||||
import mage.abilities.keyword.PartnerAbility;
|
||||
import mage.abilities.keyword.PartnerWithAbility;
|
||||
import mage.cards.Card;
|
||||
|
@ -51,25 +52,63 @@ public class PennyDreadfulCommander extends Constructed {
|
|||
public boolean validate(Deck deck) {
|
||||
boolean valid = true;
|
||||
FilterMana colorIdentity = new FilterMana();
|
||||
Set<Card> commanders = new HashSet<>();
|
||||
Card companion = null;
|
||||
|
||||
if (deck.getCards().size() + deck.getSideboard().size() != 100) {
|
||||
if (deck.getSideboard().size() == 1) {
|
||||
commanders.add(deck.getSideboard().iterator().next());
|
||||
} else if (deck.getSideboard().size() == 2) {
|
||||
Iterator<Card> iter = deck.getSideboard().iterator();
|
||||
Card card1 = iter.next();
|
||||
Card card2 = iter.next();
|
||||
if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card1;
|
||||
commanders.add(card2);
|
||||
} else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card2;
|
||||
commanders.add(card1);
|
||||
} else {
|
||||
commanders.add(card1);
|
||||
commanders.add(card2);
|
||||
}
|
||||
} else if (deck.getSideboard().size() == 3) {
|
||||
Iterator<Card> iter = deck.getSideboard().iterator();
|
||||
Card card1 = iter.next();
|
||||
Card card2 = iter.next();
|
||||
Card card3 = iter.next();
|
||||
if (card1.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card1;
|
||||
commanders.add(card2);
|
||||
commanders.add(card3);
|
||||
} else if (card2.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card2;
|
||||
commanders.add(card1);
|
||||
commanders.add(card3);
|
||||
} else if (card3.getAbilities().stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
companion = card3;
|
||||
commanders.add(card1);
|
||||
commanders.add(card2);
|
||||
} else {
|
||||
invalid.put("Commander", "Sideboard must contain only the commander(s) and up to 1 companion");
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
invalid.put("Commander", "Sideboard must contain only the commander(s) and up to 1 companion");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (companion != null && deck.getCards().size() + deck.getSideboard().size() != 101) {
|
||||
invalid.put("Deck", "Must contain " + 101 + " cards (companion doesn't count for deck size): has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards");
|
||||
valid = false;
|
||||
} else if (companion == null && deck.getCards().size() + deck.getSideboard().size() != 100) {
|
||||
invalid.put("Deck", "Must contain " + 100 + " cards: has " + (deck.getCards().size() + deck.getSideboard().size()) + " cards");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
List<String> basicLandNames = new ArrayList<>(Arrays.asList("Forest", "Island", "Mountain", "Swamp", "Plains", "Wastes"));
|
||||
Map<String, Integer> counts = new HashMap<>();
|
||||
countCards(counts, deck.getCards());
|
||||
countCards(counts, deck.getSideboard());
|
||||
|
||||
for (Map.Entry<String, Integer> entry : counts.entrySet()) {
|
||||
if (entry.getValue() > 1) {
|
||||
if (!basicLandNames.contains(entry.getKey())) {
|
||||
invalid.put(entry.getKey(), "Too many: " + entry.getValue());
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
valid = checkCounts(1, counts) && valid;
|
||||
|
||||
generatePennyDreadfulHash();
|
||||
for (String wantedCard : counts.keySet()) {
|
||||
|
@ -79,36 +118,35 @@ public class PennyDreadfulCommander extends Constructed {
|
|||
}
|
||||
}
|
||||
|
||||
if (deck.getSideboard().isEmpty() || deck.getSideboard().size() > 2) {
|
||||
invalid.put("Commander", "Sideboard must contain only the commander(s)");
|
||||
valid = false;
|
||||
} else {
|
||||
Set<String> commanderNames = new HashSet<>();
|
||||
for (Card commander : deck.getSideboard()) {
|
||||
commanderNames.add(commander.getName());
|
||||
Set<String> commanderNames = new HashSet<>();
|
||||
for (Card commander : commanders) {
|
||||
commanderNames.add(commander.getName());
|
||||
}
|
||||
for (Card commander : commanders) {
|
||||
if (bannedCommander.contains(commander.getName())) {
|
||||
invalid.put("Commander", "Commander banned (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
for (Card commander : deck.getSideboard()) {
|
||||
if ((!commander.isCreature() || !commander.isLegendary())
|
||||
&& (!commander.isPlaneswalker() || !commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance()))) {
|
||||
invalid.put("Commander", "Commander invalid (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
if (deck.getSideboard().size() == 2 && !commander.getAbilities().contains(PartnerAbility.getInstance())) {
|
||||
boolean partnersWith = false;
|
||||
for (Ability ability : commander.getAbilities()) {
|
||||
if (ability instanceof PartnerWithAbility
|
||||
&& commanderNames.contains(((PartnerWithAbility) ability).getPartnerName())) {
|
||||
partnersWith = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((!commander.isCreature() || !commander.isLegendary())
|
||||
&& (!commander.isPlaneswalker() || !commander.getAbilities().contains(CanBeYourCommanderAbility.getInstance()))) {
|
||||
invalid.put("Commander", "Commander invalid (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
if (commanders.size() == 2) {
|
||||
if (!commander.getAbilities().contains(PartnerAbility.getInstance())) {
|
||||
boolean partnersWith = commander.getAbilities()
|
||||
.stream()
|
||||
.filter(PartnerWithAbility.class::isInstance)
|
||||
.map(PartnerWithAbility.class::cast)
|
||||
.map(PartnerWithAbility::getPartnerName)
|
||||
.anyMatch(commanderNames::contains);
|
||||
if (!partnersWith) {
|
||||
invalid.put("Commander", "Commander without Partner (" + commander.getName() + ')');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity());
|
||||
}
|
||||
ManaUtil.collectColorIdentity(colorIdentity, commander.getColorIdentity());
|
||||
}
|
||||
|
||||
// no needs in cards check on wrong commanders
|
||||
|
@ -122,6 +160,12 @@ public class PennyDreadfulCommander extends Constructed {
|
|||
valid = false;
|
||||
}
|
||||
}
|
||||
for (Card card : deck.getSideboard()) {
|
||||
if (!ManaUtil.isColorIdentityCompatible(colorIdentity, card.getColorIdentity())) {
|
||||
invalid.put(card.getName(), "Invalid color (" + colorIdentity.toString() + ')');
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
for (Card card : deck.getCards()) {
|
||||
if (!isSetAllowed(card.getExpansionSetCode())) {
|
||||
if (!legalSets(card)) {
|
||||
|
@ -138,6 +182,21 @@ public class PennyDreadfulCommander extends Constructed {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Check for companion legality
|
||||
if (companion != null) {
|
||||
Set<Card> cards = new HashSet<>(deck.getCards());
|
||||
cards.addAll(commanders);
|
||||
for (Ability ability : companion.getAbilities()) {
|
||||
if (ability instanceof CompanionAbility) {
|
||||
CompanionAbility companionAbility = (CompanionAbility) ability;
|
||||
if (!companionAbility.isLegal(cards)) {
|
||||
invalid.put(companion.getName(), "Deck invalid for companion");
|
||||
valid = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
|
|
71
Mage.Sets/src/mage/cards/k/KerugaTheMacrosage.java
Normal file
71
Mage.Sets/src/mage/cards/k/KerugaTheMacrosage.java
Normal file
|
@ -0,0 +1,71 @@
|
|||
package mage.cards.k;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.keyword.CompanionAbility;
|
||||
import mage.abilities.keyword.CompanionCondition;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.predicate.mageobject.ConvertedManaCostPredicate;
|
||||
import mage.filter.predicate.permanent.AnotherPredicate;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author emerald000
|
||||
*/
|
||||
public final class KerugaTheMacrosage extends CardImpl {
|
||||
|
||||
private static final FilterControlledPermanent filter = new FilterControlledPermanent("other permanent you control with converted mana cost 3 or greater");
|
||||
|
||||
static {
|
||||
filter.add(AnotherPredicate.instance);
|
||||
filter.add(new ConvertedManaCostPredicate(ComparisonType.MORE_THAN, 2));
|
||||
}
|
||||
|
||||
public KerugaTheMacrosage(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G/U}{G/U}");
|
||||
|
||||
this.addSuperType(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.DINOSAUR);
|
||||
this.subtype.add(SubType.HIPPO);
|
||||
this.power = new MageInt(5);
|
||||
this.toughness = new MageInt(4);
|
||||
|
||||
// Companion — Your starting deck contains only cards with converted mana cost 3 or greater and land cards.
|
||||
this.addAbility(new CompanionAbility(new KerugaCondition()));
|
||||
// When Keruga, the Macrosage enters the battlefield, draw a card for each other permanent you control with converted mana cost 3 or greater.
|
||||
this.addAbility(new EntersBattlefieldTriggeredAbility(new DrawCardSourceControllerEffect(new PermanentsOnBattlefieldCount(filter))));
|
||||
}
|
||||
|
||||
private KerugaTheMacrosage(final KerugaTheMacrosage card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KerugaTheMacrosage copy() {
|
||||
return new KerugaTheMacrosage(this);
|
||||
}
|
||||
}
|
||||
|
||||
class KerugaCondition implements CompanionCondition {
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Your starting deck contains only cards with converted mana cost 3 or greater and land cards.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck) {
|
||||
return deck.stream().allMatch(card -> card.isLand() || card.getConvertedManaCost() >= 3);
|
||||
}
|
||||
}
|
85
Mage.Sets/src/mage/cards/u/UmoriTheCollector.java
Normal file
85
Mage.Sets/src/mage/cards/u/UmoriTheCollector.java
Normal file
|
@ -0,0 +1,85 @@
|
|||
package mage.cards.u;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.common.AsEntersBattlefieldAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.ChooseCardTypeEffect;
|
||||
import mage.abilities.effects.common.cost.SpellsCostReductionAllOfChosenCardTypeEffect;
|
||||
import mage.abilities.keyword.CompanionAbility;
|
||||
import mage.abilities.keyword.CompanionCondition;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.filter.FilterCard;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author emerald000
|
||||
*/
|
||||
public final class UmoriTheCollector extends CardImpl {
|
||||
|
||||
private static final FilterCard filter = new FilterCard("Spells you cast of the chosen type");
|
||||
|
||||
public UmoriTheCollector(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B/G}{B/G}");
|
||||
|
||||
this.addSuperType(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.OOZE);
|
||||
this.power = new MageInt(4);
|
||||
this.toughness = new MageInt(5);
|
||||
|
||||
// Companion — Each nonland card in your starting deck shares a card type.
|
||||
this.addAbility(new CompanionAbility(new UmoriCondition()));
|
||||
|
||||
// As Umori, the Collector enters the battlefield, choose a card type.
|
||||
this.addAbility(new AsEntersBattlefieldAbility(new ChooseCardTypeEffect(Outcome.Benefit)));
|
||||
|
||||
// Spells you cast of the chosen type cost {1} less to cast.
|
||||
this.addAbility(new SimpleStaticAbility(new SpellsCostReductionAllOfChosenCardTypeEffect(filter, 1, true)));
|
||||
}
|
||||
|
||||
private UmoriTheCollector(final UmoriTheCollector card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UmoriTheCollector copy() {
|
||||
return new UmoriTheCollector(this);
|
||||
}
|
||||
}
|
||||
|
||||
class UmoriCondition implements CompanionCondition {
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Each nonland card in your starting deck shares a card type.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLegal(Set<Card> deck) {
|
||||
Set<CardType> cardTypes = new HashSet<>();
|
||||
for (Card card : deck) {
|
||||
// Lands are fine.
|
||||
if (card.isLand()) {
|
||||
continue;
|
||||
}
|
||||
// First nonland checked.
|
||||
if (cardTypes.isEmpty()) {
|
||||
cardTypes.addAll(card.getCardType());
|
||||
} else {
|
||||
cardTypes.retainAll(card.getCardType());
|
||||
if (cardTypes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -182,6 +182,7 @@ public final class IkoriaLairOfBehemoths extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Jungle Hollow", 249, Rarity.COMMON, mage.cards.j.JungleHollow.class));
|
||||
cards.add(new SetCardInfo("Keensight Mentor", 18, Rarity.UNCOMMON, mage.cards.k.KeensightMentor.class));
|
||||
cards.add(new SetCardInfo("Keep Safe", 56, Rarity.COMMON, mage.cards.k.KeepSafe.class));
|
||||
cards.add(new SetCardInfo("Keruga, the Macrosage", 354, Rarity.RARE, mage.cards.k.KerugaTheMacrosage.class));
|
||||
cards.add(new SetCardInfo("Ketria Crystal", 236, Rarity.UNCOMMON, mage.cards.k.KetriaCrystal.class));
|
||||
cards.add(new SetCardInfo("Ketria Triome", 250, Rarity.RARE, mage.cards.k.KetriaTriome.class));
|
||||
cards.add(new SetCardInfo("Kogla, the Titan Ape", 162, Rarity.RARE, mage.cards.k.KoglaTheTitanApe.class));
|
||||
|
@ -279,6 +280,7 @@ public final class IkoriaLairOfBehemoths extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Titanoth Rex", 174, Rarity.UNCOMMON, mage.cards.t.TitanothRex.class));
|
||||
cards.add(new SetCardInfo("Tranquil Cove", 257, Rarity.COMMON, mage.cards.t.TranquilCove.class));
|
||||
cards.add(new SetCardInfo("Trumpeting Gnarr", 213, Rarity.UNCOMMON, mage.cards.t.TrumpetingGnarr.class));
|
||||
cards.add(new SetCardInfo("Umori, the Collector", 231, Rarity.RARE, mage.cards.u.UmoriTheCollector.class));
|
||||
cards.add(new SetCardInfo("Unbreakable Bond", 101, Rarity.UNCOMMON, mage.cards.u.UnbreakableBond.class));
|
||||
cards.add(new SetCardInfo("Unexpected Fangs", 102, Rarity.COMMON, mage.cards.u.UnexpectedFangs.class));
|
||||
cards.add(new SetCardInfo("Unlikely Aid", 103, Rarity.COMMON, mage.cards.u.UnlikelyAid.class));
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceCardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
/**
|
||||
* @author emerald000
|
||||
*/
|
||||
public class ChooseCardTypeEffect extends OneShotEffect {
|
||||
|
||||
public ChooseCardTypeEffect(Outcome outcome) {
|
||||
super(outcome);
|
||||
staticText = "choose a card type";
|
||||
}
|
||||
|
||||
private ChooseCardTypeEffect(final ChooseCardTypeEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChooseCardTypeEffect copy() {
|
||||
return new ChooseCardTypeEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
MageObject mageObject = game.getPermanentEntering(source.getSourceId());
|
||||
if (mageObject == null) {
|
||||
mageObject = game.getObject(source.getSourceId());
|
||||
}
|
||||
if (controller != null && mageObject != null) {
|
||||
Choice typeChoice = new ChoiceCardType();
|
||||
if (controller.choose(outcome, typeChoice, game)) {
|
||||
game.informPlayers(mageObject.getLogName() + ": " + controller.getLogName() + " has chosen: " + typeChoice.getChoice());
|
||||
game.getState().setValue(source.getSourceId() + "_type", typeChoice.getChoice());
|
||||
if (mageObject instanceof Permanent) {
|
||||
((Permanent) mageObject).addInfo("chosen type", CardUtil.addToolTipMarkTags("Chosen type: " + typeChoice.getChoice()), game);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package mage.abilities.effects.common.cost;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.CardType;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.game.Game;
|
||||
|
||||
/**
|
||||
* @author emerald000
|
||||
*/
|
||||
public class SpellsCostReductionAllOfChosenCardTypeEffect extends SpellsCostReductionAllEffect {
|
||||
|
||||
public SpellsCostReductionAllOfChosenCardTypeEffect(FilterCard filter, int amount) {
|
||||
this(filter, amount, false);
|
||||
}
|
||||
|
||||
public SpellsCostReductionAllOfChosenCardTypeEffect(FilterCard filter, int amount, boolean onlyControlled) {
|
||||
super(filter, amount, false, onlyControlled);
|
||||
}
|
||||
|
||||
public SpellsCostReductionAllOfChosenCardTypeEffect(final SpellsCostReductionAllOfChosenCardTypeEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpellsCostReductionAllOfChosenCardTypeEffect copy() {
|
||||
return new SpellsCostReductionAllOfChosenCardTypeEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean selectedByRuntimeData(Card card, Ability source, Game game) {
|
||||
Object savedType = game.getState().getValue(source.getSourceId() + "_type");
|
||||
if (savedType instanceof String) {
|
||||
return card.getCardType().contains(CardType.fromString((String) savedType));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package mage.abilities.effects.keyword;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.AsThoughEffectImpl;
|
||||
import mage.constants.AsThoughEffectType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/*
|
||||
* @author emerald000
|
||||
*/
|
||||
public class CompanionEffect extends AsThoughEffectImpl {
|
||||
|
||||
public CompanionEffect() {
|
||||
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit);
|
||||
staticText = "Once during the game, you may cast your chosen companion from your sideboard";
|
||||
}
|
||||
|
||||
private CompanionEffect(final CompanionEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompanionEffect copy() {
|
||||
return new CompanionEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
|
||||
return objectId.equals(source.getSourceId()) && affectedControllerId.equals(source.getControllerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.effects.keyword.CompanionEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.Zone;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/*
|
||||
* @author emerald000
|
||||
*/
|
||||
public class CompanionAbility extends StaticAbility {
|
||||
|
||||
private final CompanionCondition condition;
|
||||
|
||||
public CompanionAbility(CompanionCondition condition) {
|
||||
super(Zone.OUTSIDE, new CompanionEffect());
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
private CompanionAbility(final CompanionAbility ability) {
|
||||
super(ability);
|
||||
this.condition = ability.condition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompanionAbility copy() {
|
||||
return new CompanionAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Companion — " + condition.getRule();
|
||||
}
|
||||
|
||||
public boolean isLegal(Set<Card> cards) {
|
||||
return condition.isLegal(cards);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.cards.Card;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
/*
|
||||
* @author emerald000
|
||||
*/
|
||||
public interface CompanionCondition extends Serializable {
|
||||
|
||||
/**
|
||||
* @return The rule to get added to the card text. (Everything after the dash)
|
||||
*/
|
||||
String getRule();
|
||||
|
||||
/**
|
||||
* @param deck The set of cards to check.
|
||||
* @return Whether the companion is valid for that deck.
|
||||
*/
|
||||
boolean isLegal(Set<Card> deck);
|
||||
}
|
31
Mage/src/main/java/mage/choices/ChoiceCardType.java
Normal file
31
Mage/src/main/java/mage/choices/ChoiceCardType.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
package mage.choices;
|
||||
|
||||
import mage.constants.CardType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author emerald000
|
||||
*/
|
||||
public class ChoiceCardType extends ChoiceImpl {
|
||||
|
||||
public ChoiceCardType() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public ChoiceCardType(boolean required) {
|
||||
super(required);
|
||||
this.choices.addAll(Arrays.stream(CardType.values()).map(CardType::toString).collect(Collectors.toList()));
|
||||
this.message = "Choose a card type";
|
||||
}
|
||||
|
||||
private ChoiceCardType(final ChoiceCardType choice) {
|
||||
super(choice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChoiceCardType copy() {
|
||||
return new ChoiceCardType(this);
|
||||
}
|
||||
}
|
|
@ -17,9 +17,13 @@ public enum CardType {
|
|||
ENCHANTMENT("Enchantment", true),
|
||||
INSTANT("Instant", false),
|
||||
LAND("Land", true),
|
||||
PHENOMENON("Phenomenon", false),
|
||||
PLANE("Plane", false),
|
||||
PLANESWALKER("Planeswalker", true),
|
||||
SCHEME("Scheme", false),
|
||||
SORCERY("Sorcery", false),
|
||||
TRIBAL("Tribal", false);
|
||||
TRIBAL("Tribal", false),
|
||||
VANGUARD("Vanguard", false);
|
||||
|
||||
private final String text;
|
||||
private final boolean permanentType;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package mage.game;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.InfoEffect;
|
||||
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
|
||||
import mage.abilities.effects.common.cost.CommanderCostModification;
|
||||
import mage.abilities.keyword.CompanionAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.PhaseStep;
|
||||
|
@ -18,6 +17,9 @@ import mage.players.Player;
|
|||
import mage.watchers.common.CommanderInfoWatcher;
|
||||
import mage.watchers.common.CommanderPlaysCountWatcher;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class GameCommanderImpl extends GameImpl {
|
||||
|
||||
// private final Map<UUID, Cards> mulliganedCards = new HashMap<>();
|
||||
|
@ -54,9 +56,13 @@ public abstract class GameCommanderImpl extends GameImpl {
|
|||
if (player != null) {
|
||||
// add new commanders
|
||||
for (UUID id : player.getSideboard()) {
|
||||
Card commander = this.getCard(id);
|
||||
if (commander != null) {
|
||||
addCommander(commander, player);
|
||||
Card card = this.getCard(id);
|
||||
if (card != null) {
|
||||
// Check for companions. If it is the only card in the sideboard, it is the commander, not a companion.
|
||||
if (player.getSideboard().size() > 1 && card.getAbilities(this).stream().anyMatch(ability -> ability instanceof CompanionAbility)) {
|
||||
continue;
|
||||
}
|
||||
addCommander(card, player);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import mage.abilities.effects.Effect;
|
|||
import mage.abilities.effects.PreventionEffectData;
|
||||
import mage.abilities.effects.common.CopyEffect;
|
||||
import mage.abilities.keyword.BestowAbility;
|
||||
import mage.abilities.keyword.CompanionAbility;
|
||||
import mage.abilities.keyword.MorphAbility;
|
||||
import mage.abilities.keyword.TransformAbility;
|
||||
import mage.abilities.mana.DelayedTriggeredManaAbility;
|
||||
|
@ -929,6 +930,39 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle companions
|
||||
Map<Player, Card> playerCompanionMap = new HashMap<>();
|
||||
for (Player player : state.getPlayers().values()) {
|
||||
// Make a list of legal companions present in the sideboard
|
||||
Set<Card> potentialCompanions = new HashSet<>();
|
||||
for (Card card : player.getSideboard().getUniqueCards(this)) {
|
||||
for (Ability ability : card.getAbilities(this)) {
|
||||
if (ability instanceof CompanionAbility) {
|
||||
CompanionAbility companionAbility = (CompanionAbility) ability;
|
||||
if (companionAbility.isLegal(new HashSet<>(player.getLibrary().getCards(this)))) {
|
||||
potentialCompanions.add(card);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Choose a companion from the list of legal companions
|
||||
for (Card card : potentialCompanions) {
|
||||
if (player.chooseUse(Outcome.Benefit, "Use " + card.getName() + " as your companion?", null, this)) {
|
||||
playerCompanionMap.put(player, card);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Announce companions and set the companion effect
|
||||
playerCompanionMap.forEach((player, companion) -> {
|
||||
if (companion != null) {
|
||||
this.informPlayers(player.getLogName() + " has chosen " + companion.getLogName() + " as their companion.");
|
||||
this.getState().getCompanion().update(player.getName() + "'s companion", new CardsImpl(companion));
|
||||
}
|
||||
});
|
||||
|
||||
//20091005 - 103.1
|
||||
if (!gameOptions.skipInitShuffling) { //don't shuffle in test mode for card injection on top of player's libraries
|
||||
for (Player player : state.getPlayers().values()) {
|
||||
|
|
|
@ -59,6 +59,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
// revealed cards <Name, <Cards>>, will be reset if all players pass priority
|
||||
private final Revealed revealed;
|
||||
private final Map<UUID, LookedAt> lookedAt = new HashMap<>();
|
||||
private final Revealed companion;
|
||||
|
||||
private DelayedTriggeredAbilities delayed;
|
||||
private SpecialActions specialActions;
|
||||
|
@ -106,6 +107,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
command = new Command();
|
||||
exile = new Exile();
|
||||
revealed = new Revealed();
|
||||
companion = new Revealed();
|
||||
battlefield = new Battlefield();
|
||||
effects = new ContinuousEffects();
|
||||
triggers = new TriggeredAbilities();
|
||||
|
@ -123,6 +125,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
this.choosingPlayerId = state.choosingPlayerId;
|
||||
this.revealed = state.revealed.copy();
|
||||
this.lookedAt.putAll(state.lookedAt);
|
||||
this.companion = state.companion.copy();
|
||||
this.gameOver = state.gameOver;
|
||||
this.paused = state.paused;
|
||||
|
||||
|
@ -473,6 +476,10 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
return lookedAt.get(playerId);
|
||||
}
|
||||
|
||||
public Revealed getCompanion() {
|
||||
return companion;
|
||||
}
|
||||
|
||||
public void clearRevealed() {
|
||||
revealed.clear();
|
||||
}
|
||||
|
@ -481,6 +488,10 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
lookedAt.clear();
|
||||
}
|
||||
|
||||
public void clearCompanion() {
|
||||
companion.clear();
|
||||
}
|
||||
|
||||
public Turn getTurn() {
|
||||
return turn;
|
||||
}
|
||||
|
@ -1067,6 +1078,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
isPlaneChase = false;
|
||||
revealed.clear();
|
||||
lookedAt.clear();
|
||||
companion.clear();
|
||||
turnNum = 0;
|
||||
stepNum = 0;
|
||||
extraTurn = false;
|
||||
|
|
|
@ -3446,6 +3446,15 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
// check to play companion cards
|
||||
if (fromAll || fromZone == Zone.OUTSIDE) {
|
||||
for (Cards companionCards : game.getState().getCompanion().values()) {
|
||||
for (Card card : companionCards.getCards(game)) {
|
||||
getPlayableFromNonHandCardAll(game, Zone.OUTSIDE, card, availableMana, playable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if it's possible to play the top card of a library
|
||||
if (fromAll || fromZone == Zone.LIBRARY) {
|
||||
for (UUID playerInRangeId : game.getState().getPlayersInRange(getId(), game)) {
|
||||
|
|
Loading…
Reference in a new issue