mirror of
https://github.com/magefree/mage.git
synced 2025-12-25 21:12:04 -08: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
|
|
@ -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…
Add table
Add a link
Reference in a new issue