mirror of
https://github.com/magefree/mage.git
synced 2025-12-25 04:52:07 -08:00
new feature: Emblem Cards (#10498)
* new feature: Emblem Cards Allows match/tournament creator to specify cards to give each player emblem versions of (or just the starting player for symmetric effects). Technical details: - new UI for specifying emblem cards (.dck files) - available for all match/tournament types - new class `EmblemOfCard` - new method `copyWithZone` on `AbilityImpl` (used to make abilities work from command zone) - new fields on `GameOptions` and `MatchOptions` for emblem cards - emblems are granted after mulligans, before first turn (technically after Planechase starting plane creation) * fixes * defaults for emblem cards in match options (fixes quick game buttons) * minor fixes * use DeckCardInfo instead of Card for emblem cards options * restore accessible parent properties * fix images for card emblems * look up cards in a way that preserves which art * fix typos; make Emblem.sourceObject protected * add descriptions to planechase and emblem cards * fixes * add some unit tests for known working cards * fix author name * add explanation comment * fix up tests * copyWithZone: no longer modifies zone for singleton abilities * directly check for MageSingleton
This commit is contained in:
parent
04dba063aa
commit
41874b0b4b
15 changed files with 774 additions and 39 deletions
|
|
@ -1481,6 +1481,13 @@ public abstract class AbilityImpl implements Ability {
|
|||
}
|
||||
|
||||
public AbilityImpl copyWithZone(Zone zone) {
|
||||
if (this instanceof MageSingleton) {
|
||||
// not safe to change zone for singletons
|
||||
// in theory there could be some sort of wrapper to effectively change
|
||||
// the zone here, but currently no use of copyWithZone actually needs
|
||||
// to change the zone of any existing singleton abilities
|
||||
return this;
|
||||
}
|
||||
AbilityImpl copy = ((AbilityImpl)this.copy());
|
||||
copy.zone = zone;
|
||||
copy.newId();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import mage.abilities.mana.TriggeredManaAbility;
|
|||
import mage.actions.impl.MageAction;
|
||||
import mage.cards.*;
|
||||
import mage.cards.decks.Deck;
|
||||
import mage.cards.decks.DeckCardInfo;
|
||||
import mage.choices.Choice;
|
||||
import mage.constants.*;
|
||||
import mage.counters.CounterType;
|
||||
|
|
@ -41,6 +42,7 @@ import mage.game.combat.Combat;
|
|||
import mage.game.combat.CombatGroup;
|
||||
import mage.game.command.*;
|
||||
import mage.game.command.dungeons.UndercityDungeon;
|
||||
import mage.game.command.emblems.EmblemOfCard;
|
||||
import mage.game.command.emblems.TheRingEmblem;
|
||||
import mage.game.events.*;
|
||||
import mage.game.events.TableEvent.EventType;
|
||||
|
|
@ -1317,6 +1319,22 @@ public abstract class GameImpl implements Game {
|
|||
addPlane(plane, startingPlayerId);
|
||||
state.setPlaneChase(this, gameOptions.planeChase);
|
||||
}
|
||||
|
||||
if (!gameOptions.perPlayerEmblemCards.isEmpty()) {
|
||||
for (UUID playerId : state.getPlayerList(startingPlayerId)) {
|
||||
for (DeckCardInfo info : gameOptions.perPlayerEmblemCards) {
|
||||
Card card = EmblemOfCard.cardFromDeckInfo(info);
|
||||
addEmblem(new EmblemOfCard(card), card, playerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gameOptions.globalEmblemCards.isEmpty()) {
|
||||
for (DeckCardInfo info : gameOptions.globalEmblemCards) {
|
||||
Card card = EmblemOfCard.cardFromDeckInfo(info);
|
||||
addEmblem(new EmblemOfCard(card), card, startingPlayerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void initGameDefaultWatchers() {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package mage.game;
|
||||
|
||||
import mage.cards.decks.DeckCardInfo;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.util.Copyable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
|
@ -52,6 +55,15 @@ public class GameOptions implements Serializable, Copyable<GameOptions> {
|
|||
*/
|
||||
public Set<String> bannedUsers = Collections.emptySet();
|
||||
|
||||
/**
|
||||
* Cards to be given to each player as emblems
|
||||
*/
|
||||
public Collection<DeckCardInfo> perPlayerEmblemCards = Collections.emptySet();
|
||||
/**
|
||||
* Cards to be given to the starting player as emblems
|
||||
*/
|
||||
public Collection<DeckCardInfo> globalEmblemCards = Collections.emptySet();
|
||||
|
||||
|
||||
// PLANECHASE game mode
|
||||
public boolean planeChase = false;
|
||||
|
|
@ -73,6 +85,8 @@ public class GameOptions implements Serializable, Copyable<GameOptions> {
|
|||
this.rollbackTurnsAllowed = options.rollbackTurnsAllowed;
|
||||
this.bannedUsers.addAll(options.bannedUsers);
|
||||
this.planeChase = options.planeChase;
|
||||
this.perPlayerEmblemCards = new HashSet<>(options.perPlayerEmblemCards);
|
||||
this.globalEmblemCards = new HashSet<>(options.globalEmblemCards);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public abstract class Emblem extends CommandObjectImpl {
|
|||
private static final ManaCosts emptyCost = new ManaCostsImpl<>();
|
||||
|
||||
private UUID controllerId;
|
||||
private MageObject sourceObject;
|
||||
protected MageObject sourceObject;
|
||||
private boolean copy;
|
||||
private MageObject copyFrom; // copied card INFO (used to call original adjusters)
|
||||
private FrameStyle frameStyle;
|
||||
|
|
|
|||
108
Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java
Normal file
108
Mage/src/main/java/mage/game/command/emblems/EmblemOfCard.java
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package mage.game.command.emblems;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.AbilityImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.decks.DeckCardInfo;
|
||||
import mage.cards.mock.MockCard;
|
||||
import mage.cards.repository.CardCriteria;
|
||||
import mage.cards.repository.CardInfo;
|
||||
import mage.cards.repository.CardRepository;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.command.Emblem;
|
||||
import mage.util.CardUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author artemiswkearney
|
||||
* Emblem with all the abilities of an existing card.
|
||||
* Can be used for custom gamemodes like Omniscience Draft (as seen on Arena),
|
||||
* mana burn with Yurlok of Scorch Thrash, and anything else players might think of.
|
||||
*/
|
||||
public final class EmblemOfCard extends Emblem {
|
||||
private final boolean usesVariousArt;
|
||||
private static final Logger logger = Logger.getLogger(EmblemOfCard.class);
|
||||
|
||||
public static Card lookupCard(
|
||||
String cardName,
|
||||
String cardNumber,
|
||||
String setCode,
|
||||
String infoTypeForError
|
||||
) {
|
||||
int cardNumberInt = CardUtil.parseCardNumberAsInt(cardNumber);
|
||||
List<CardInfo> found = CardRepository.instance.findCards(new CardCriteria()
|
||||
.name(cardName)
|
||||
.minCardNumber(cardNumberInt)
|
||||
.maxCardNumber(cardNumberInt)
|
||||
.setCodes(setCode));
|
||||
return found.stream()
|
||||
.filter(ci -> ci.getCardNumber().equals(cardNumber))
|
||||
.findFirst()
|
||||
.orElseGet(() -> found.stream()
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("No real card for " + infoTypeForError + " " + cardName)))
|
||||
.getCard();
|
||||
}
|
||||
|
||||
public static Card cardFromDeckInfo(DeckCardInfo info) {
|
||||
return lookupCard(
|
||||
info.getCardName(),
|
||||
info.getCardNum(),
|
||||
info.getSetCode(),
|
||||
"DeckCardInfo"
|
||||
);
|
||||
}
|
||||
|
||||
public EmblemOfCard(Card card, Zone zone) {
|
||||
super(card.getName());
|
||||
if (card instanceof MockCard) {
|
||||
card = lookupCard(
|
||||
card.getName(),
|
||||
card.getCardNumber(),
|
||||
card.getExpansionSetCode(),
|
||||
"MockCard"
|
||||
);
|
||||
}
|
||||
this.getAbilities().addAll(card.getAbilities().stream().filter(
|
||||
ability -> zone.match(ability.getZone())
|
||||
).map(ability -> {
|
||||
if (ability instanceof AbilityImpl && ability.getZone() == zone) {
|
||||
return ((AbilityImpl)ability).copyWithZone(Zone.COMMAND);
|
||||
}
|
||||
return ability;
|
||||
}).collect(Collectors.toList()));
|
||||
this.getAbilities().setSourceId(this.getId());
|
||||
this.setExpansionSetCode(card.getExpansionSetCode());
|
||||
this.setCardNumber(card.getCardNumber());
|
||||
this.setImageNumber(card.getImageNumber());
|
||||
this.usesVariousArt = card.getUsesVariousArt();
|
||||
}
|
||||
|
||||
public EmblemOfCard(Card card) {
|
||||
this(card, Zone.BATTLEFIELD);
|
||||
}
|
||||
|
||||
private EmblemOfCard(EmblemOfCard eoc) {
|
||||
super(eoc);
|
||||
this.usesVariousArt = eoc.usesVariousArt;
|
||||
}
|
||||
@Override
|
||||
public EmblemOfCard copy() {
|
||||
return new EmblemOfCard(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSourceObject(MageObject sourceObject) {
|
||||
this.sourceObject = sourceObject;
|
||||
// super method would try and fail to find the emblem image here
|
||||
// (not sure why that would be setSoureObject's job; we get our image during construction)
|
||||
}
|
||||
|
||||
public boolean getUsesVariousArt() {
|
||||
return usesVariousArt;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
package mage.game.match;
|
||||
|
||||
import mage.cards.decks.DeckCardInfo;
|
||||
import mage.constants.MatchBufferTime;
|
||||
import mage.constants.MatchTimeLimit;
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
|
|
@ -11,10 +12,7 @@ import mage.game.result.ResultProtos;
|
|||
import mage.players.PlayerType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
@ -52,6 +50,9 @@ public class MatchOptions implements Serializable {
|
|||
protected MatchBufferTime matchBufferTime; // Amount of time each player gets before their normal time limit counts down. Refreshes each time the normal timer is invoked.
|
||||
protected MulliganType mulliganType;
|
||||
|
||||
protected Collection<DeckCardInfo> perPlayerEmblemCards;
|
||||
protected Collection<DeckCardInfo> globalEmblemCards;
|
||||
|
||||
/*public MatchOptions(String name, String gameType) {
|
||||
this.name = name;
|
||||
this.gameType = gameType;
|
||||
|
|
@ -65,6 +66,8 @@ public class MatchOptions implements Serializable {
|
|||
this.password = "";
|
||||
this.multiPlayer = multiPlayer;
|
||||
this.numSeats = numSeats;
|
||||
this.perPlayerEmblemCards = Collections.emptySet();
|
||||
this.globalEmblemCards = Collections.emptySet();
|
||||
}
|
||||
|
||||
public void setNumSeats (int numSeats) {
|
||||
|
|
@ -288,4 +291,19 @@ public class MatchOptions implements Serializable {
|
|||
return mulliganType;
|
||||
}
|
||||
|
||||
public Collection<DeckCardInfo> getPerPlayerEmblemCards() {
|
||||
return perPlayerEmblemCards;
|
||||
}
|
||||
|
||||
public void setPerPlayerEmblemCards(Collection<DeckCardInfo> perPlayerEmblemCards) {
|
||||
this.perPlayerEmblemCards = perPlayerEmblemCards;
|
||||
}
|
||||
|
||||
public Collection<DeckCardInfo> getGlobalEmblemCards() {
|
||||
return globalEmblemCards;
|
||||
}
|
||||
|
||||
public void setGlobalEmblemCards(Collection<DeckCardInfo> globalEmblemCards) {
|
||||
this.globalEmblemCards = globalEmblemCards;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue