mirror of
https://github.com/magefree/mage.git
synced 2026-01-10 04:42:07 -08:00
Merge remote-tracking branch 'Xmage/master' into fork/Jmlundeen/guidelight-matrix-kolodin
# Conflicts: # Mage.Sets/src/mage/sets/Aetherdrift.java
This commit is contained in:
commit
83ba735149
92 changed files with 4006 additions and 479 deletions
|
|
@ -76,7 +76,8 @@ public enum MageIdentifier {
|
|||
TheRuinousPowersAlternateCast,
|
||||
FiresOfMountDoomAlternateCast,
|
||||
PrimalPrayersAlternateCast,
|
||||
QuilledGreatwurmAlternateCast;
|
||||
QuilledGreatwurmAlternateCast,
|
||||
WickerfolkIndomitableAlternateCast;
|
||||
|
||||
/**
|
||||
* Additional text if there is need to differentiate two very similar effects
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package mage.abilities.condition.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.dynamicvalue.common.CardsInExileCount;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.game.Game;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
/**
|
||||
* Cards in exile condition
|
||||
*
|
||||
* @author Jmlundeen
|
||||
*/
|
||||
public class CardsInExileCondition implements Condition
|
||||
{
|
||||
private final ComparisonType type;
|
||||
private final int count;
|
||||
private final DynamicValue cardsInExileCount;
|
||||
|
||||
public CardsInExileCondition(ComparisonType type, int count)
|
||||
{
|
||||
this(type, count, CardsInExileCount.ALL);
|
||||
}
|
||||
|
||||
public CardsInExileCondition(ComparisonType type, int count, DynamicValue cardsInExileCount)
|
||||
{
|
||||
this.type = type;
|
||||
this.count = count;
|
||||
this.cardsInExileCount = cardsInExileCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source)
|
||||
{
|
||||
int exileCards = cardsInExileCount.calculate(game, source, null);
|
||||
return ComparisonType.compare(exileCards, type, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("there are ");
|
||||
String countString = CardUtil.numberToText(count);
|
||||
switch (type) {
|
||||
case MORE_THAN:
|
||||
sb.append("more than ").append(countString).append(" ");
|
||||
break;
|
||||
case FEWER_THAN:
|
||||
sb.append("fewer than ").append(countString).append(" ");
|
||||
break;
|
||||
case OR_LESS:
|
||||
sb.append(countString).append(" or less ");
|
||||
break;
|
||||
case OR_GREATER:
|
||||
sb.append(countString).append(" or more ");
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("comparison rules for " + type + " missing");
|
||||
}
|
||||
sb.append("cards in exile");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package mage.abilities.dynamicvalue.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.hint.ValueHint;
|
||||
import mage.cards.Card;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Jmlundeen
|
||||
*/
|
||||
public enum CardsInExileCount implements DynamicValue {
|
||||
YOU("you"),
|
||||
ALL("all players"),
|
||||
OPPONENTS("your opponents'");
|
||||
|
||||
private final String message;
|
||||
private final ValueHint hint;
|
||||
|
||||
CardsInExileCount(String message) {
|
||||
this.message = "The number of cards owned by " + message + " in exile";
|
||||
this.hint = new ValueHint(this.message, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||
return getExileCards(game, sourceAbility)
|
||||
.mapToInt(x -> 1)
|
||||
.sum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CardsInExileCount copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "X";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public Hint getHint() {
|
||||
return hint;
|
||||
}
|
||||
|
||||
public Stream<Card> getExileCards(Game game, Ability ability) {
|
||||
Collection<UUID> playerIds;
|
||||
switch (this) {
|
||||
case YOU:
|
||||
playerIds = Collections.singletonList(ability.getControllerId());
|
||||
break;
|
||||
case OPPONENTS:
|
||||
playerIds = game.getOpponents(ability.getControllerId());
|
||||
break;
|
||||
case ALL:
|
||||
playerIds = game.getState().getPlayersInRange(ability.getControllerId(), game);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Wrong code usage: miss implementation for " + this);
|
||||
}
|
||||
return playerIds.stream()
|
||||
.map(game::getPlayer)
|
||||
.filter(Objects::nonNull)
|
||||
.map(player -> game.getExile().getAllCards(game, player.getId()))
|
||||
.flatMap(Collection::stream)
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package mage.abilities.dynamicvalue.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.game.Game;
|
||||
import mage.watchers.common.CreaturesDiedWatcher;
|
||||
|
||||
public enum CreaturesYouControlDiedCount implements DynamicValue {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||
return game.getState()
|
||||
.getWatcher(CreaturesDiedWatcher.class)
|
||||
.getAmountOfCreaturesDiedThisTurnByController(sourceAbility.getControllerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CreaturesYouControlDiedCount copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "creature that died under your control this turn";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
|
@ -98,16 +98,12 @@ public class DoIfCostPaid extends OneShotEffect {
|
|||
if (player == null || mageObject == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// nothing to pay (do not support mana cost - it's true all the time)
|
||||
if (!this.cost.canPay(source, source, player.getId(), game)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String message = CardUtil.replaceSourceName(makeChooseText(game, source), mageObject.getName());
|
||||
Outcome payOutcome = executingEffects.getOutcome(source, this.outcome);
|
||||
// nothing to pay (do not support mana cost - it's true all the time)
|
||||
boolean canPay = cost.canPay(source, source, player.getId(), game);
|
||||
boolean didPay = false;
|
||||
if (!optional || player.chooseUse(payOutcome, message, source, game)) {
|
||||
if (canPay && (!optional || player.chooseUse(payOutcome, message, source, game))) {
|
||||
cost.clearPaid();
|
||||
int bookmark = game.bookmarkState();
|
||||
if (cost.pay(source, game, source, player.getId(), false)) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import mage.game.Game;
|
|||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
|
|
@ -38,12 +40,13 @@ public class MillCardsTargetEffect extends OneShotEffect {
|
|||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
|
||||
if (player != null) {
|
||||
player.millCards(numberCards.calculate(game, source, this), source, game);
|
||||
return true;
|
||||
for (UUID playerId : getTargetPointer().getTargets(game, source)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
player.millCards(numberCards.calculate(game, source, this), source, game);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.constants.Outcome;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.watchers.common.AttackedThisTurnWatcher;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class UntapAllThatAttackedEffect extends OneShotEffect {
|
||||
|
||||
public UntapAllThatAttackedEffect() {
|
||||
super(Outcome.Benefit);
|
||||
staticText = "Untap all creatures that attacked this turn";
|
||||
}
|
||||
|
||||
protected UntapAllThatAttackedEffect(final UntapAllThatAttackedEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UntapAllThatAttackedEffect copy() {
|
||||
return new UntapAllThatAttackedEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class);
|
||||
if (watcher != null) {
|
||||
Set<MageObjectReference> attackedThisTurn = watcher.getAttackedThisTurnCreatures();
|
||||
for (MageObjectReference mor : attackedThisTurn) {
|
||||
Permanent permanent = mor.getPermanent(game);
|
||||
if (permanent != null && permanent.isCreature(game)) {
|
||||
permanent.untap(game);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
public class VehiclesBecomeArtifactCreatureEffect extends ContinuousEffectImpl {
|
||||
|
||||
public VehiclesBecomeArtifactCreatureEffect(Duration duration) {
|
||||
super(duration, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.BecomeCreature);
|
||||
staticText = "Vehicles you control become artifact creatures until end of turn";
|
||||
}
|
||||
|
||||
private VehiclesBecomeArtifactCreatureEffect(final VehiclesBecomeArtifactCreatureEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VehiclesBecomeArtifactCreatureEffect copy() {
|
||||
return new VehiclesBecomeArtifactCreatureEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(source.getControllerId())) {
|
||||
if (permanent != null && permanent.hasSubtype(SubType.VEHICLE, game)) {
|
||||
if (sublayer == SubLayer.NA) {
|
||||
permanent.addCardType(game, CardType.ARTIFACT);
|
||||
permanent.addCardType(game, CardType.CREATURE);// TODO: Check if giving CREATURE Type is correct
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,20 +3,36 @@ package mage.abilities.keyword;
|
|||
import mage.abilities.ActivatedAbilityImpl;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.AsThoughEffectType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class ExhaustAbility extends ActivatedAbilityImpl {
|
||||
|
||||
private boolean withReminderText = true;
|
||||
|
||||
public ExhaustAbility(Effect effect, Cost cost) {
|
||||
super(Zone.BATTLEFIELD, effect, cost);
|
||||
}
|
||||
|
||||
public ExhaustAbility(Effect effect, Cost cost, boolean withReminderText) {
|
||||
super(Zone.BATTLEFIELD, effect, cost);
|
||||
this.setRuleVisible(false);
|
||||
this.withReminderText = withReminderText;
|
||||
}
|
||||
|
||||
private ExhaustAbility(final ExhaustAbility ability) {
|
||||
super(ability);
|
||||
this.maxActivationsPerGame = 1;
|
||||
this.withReminderText = ability.withReminderText;
|
||||
}
|
||||
|
||||
public ExhaustAbility withReminderText(boolean withReminderText) {
|
||||
this.withReminderText = withReminderText;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -24,8 +40,23 @@ public class ExhaustAbility extends ActivatedAbilityImpl {
|
|||
return new ExhaustAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreActivationsThisTurn(Game game) {
|
||||
ActivationInfo info = getActivationInfo(game);
|
||||
if (info != null && info.totalActivations >= maxActivationsPerGame) {
|
||||
boolean canActivate = !game.getContinuousEffects()
|
||||
.asThough(sourceId, AsThoughEffectType.ALLOW_EXHAUST_PER_TURN, this, controllerId, game)
|
||||
.isEmpty();
|
||||
if (canActivate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.hasMoreActivationsThisTurn(game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Exhaust — " + super.getRule() + " <i>(Activate each exhaust ability only once.)</i>";
|
||||
return "Exhaust — " + super.getRule() +
|
||||
(withReminderText ? " <i>(Activate each exhaust ability only once.)</i>" : "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public class NinjutsuAbility extends ActivatedAbilityImpl {
|
|||
}
|
||||
|
||||
public NinjutsuAbility(Cost cost, boolean commander) {
|
||||
super(commander ? Zone.ALL : Zone.HAND, new NinjutsuEffect(), cost);
|
||||
super(commander ? Zone.ALL : Zone.HAND, new NinjutsuEffect(commander), cost);
|
||||
this.addCost(new RevealNinjutsuCardCost(commander));
|
||||
this.addCost(new ReturnAttackerToHandTargetCost());
|
||||
this.commander = commander;
|
||||
|
|
@ -84,14 +84,18 @@ public class NinjutsuAbility extends ActivatedAbilityImpl {
|
|||
|
||||
class NinjutsuEffect extends OneShotEffect {
|
||||
|
||||
public NinjutsuEffect() {
|
||||
private final boolean commander;
|
||||
|
||||
NinjutsuEffect(boolean commander) {
|
||||
super(Outcome.PutCreatureInPlay);
|
||||
this.commander = commander;
|
||||
this.staticText = "Put this card onto the battlefield "
|
||||
+ "from your hand tapped and attacking";
|
||||
}
|
||||
|
||||
protected NinjutsuEffect(final NinjutsuEffect effect) {
|
||||
private NinjutsuEffect(final NinjutsuEffect effect) {
|
||||
super(effect);
|
||||
this.commander = effect.commander;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -102,11 +106,12 @@ class NinjutsuEffect extends OneShotEffect {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller == null) {
|
||||
Card card = game.getCard(source.getSourceId());
|
||||
if (controller == null || card == null) {
|
||||
return false;
|
||||
}
|
||||
Card card = game.getCard(source.getSourceId());
|
||||
if (card != null) {
|
||||
Zone cardZone = game.getState().getZone(card.getId());
|
||||
if (cardZone == Zone.HAND || (commander && cardZone == Zone.COMMAND)) {
|
||||
controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null);
|
||||
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||
if (permanent != null) {
|
||||
|
|
@ -144,6 +149,7 @@ class ReturnAttackerToHandTargetCost extends CostImpl {
|
|||
|
||||
public ReturnAttackerToHandTargetCost(ReturnAttackerToHandTargetCost cost) {
|
||||
super(cost);
|
||||
this.defendingPlayerId = cost.defendingPlayerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -59,7 +59,10 @@ public enum AsThoughEffectType {
|
|||
//
|
||||
// ALLOW_FORETELL_ANYTIME:
|
||||
// For Cosmos Charger effect
|
||||
ALLOW_FORETELL_ANYTIME;
|
||||
ALLOW_FORETELL_ANYTIME,
|
||||
// ALLOW_EXHAUST_ACTIVE_ABILITY:
|
||||
// Elvish Refueler effect allows Exhaust on your turn as though it hasn't been activated
|
||||
ALLOW_EXHAUST_PER_TURN(true, false);
|
||||
|
||||
private final boolean needAffectedAbility; // mark what AsThough check must be called for specific ability, not full object (example: spell check)
|
||||
private final boolean needPlayCardAbility; // mark what AsThough check must be called for play/cast abilities
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package mage.filter.predicate.permanent;
|
||||
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.watchers.common.AttackedThisTurnWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author balazskristof
|
||||
*/
|
||||
public enum AttackedThisTurnPredicate implements Predicate<Permanent> {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Permanent input, Game game) {
|
||||
AttackedThisTurnWatcher watcher = game.getState().getWatcher(AttackedThisTurnWatcher.class);
|
||||
return watcher != null && watcher.checkIfAttacked(input, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "attacked this turn";
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import mage.constants.SubType;
|
|||
public final class CarnivoreToken extends TokenImpl {
|
||||
|
||||
public CarnivoreToken() {
|
||||
super("Carnivore Token", "3/1 red Beast creature token");
|
||||
super("Carnivore", "3/1 red Beast creature token");
|
||||
cardType.add(CardType.CREATURE);
|
||||
color.setRed(true);
|
||||
subtype.add(SubType.BEAST);
|
||||
|
|
|
|||
|
|
@ -1346,6 +1346,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
castEvent.setZone(fromZone);
|
||||
game.fireEvent(castEvent);
|
||||
if (spell.activate(game, allowedIdentifiers, noMana)) {
|
||||
game.processAction();
|
||||
GameEvent castedEvent = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST,
|
||||
ability.getId(), ability, playerId, approvingObject);
|
||||
castedEvent.setZone(fromZone);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue