mirror of
https://github.com/magefree/mage.git
synced 2026-01-25 12:49:39 -08:00
Merge branch 'master' into refactor/multiple-names
This commit is contained in:
commit
d18bd25d21
190 changed files with 4573 additions and 812 deletions
|
|
@ -38,7 +38,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> implements Copyable<Modes>
|
|||
private int maxPawPrints;
|
||||
private Filter maxModesFilter; // calculates the max number of available modes
|
||||
private Condition moreCondition; // allows multiple modes choose (example: choose one... if condition, you may choose both)
|
||||
private int moreLimit = Integer.MAX_VALUE; // if multiple modes are allowed, this limits how many additional modes may be chosen (usually doesn't need to change)
|
||||
private int moreLimit; // if multiple modes are allowed, this limits how many additional modes may be chosen
|
||||
|
||||
private boolean limitUsageByOnce = false; // limit mode selection to once per game
|
||||
private boolean limitUsageResetOnNewTurn = false; // reset once per game limit on new turn, example: Galadriel, Light of Valinor
|
||||
|
|
@ -245,7 +245,7 @@ public class Modes extends LinkedHashMap<UUID, Mode> implements Copyable<Modes>
|
|||
|
||||
// use case: make more modes chooseable
|
||||
if (moreCondition != null && moreCondition.apply(game, source)) {
|
||||
realMaxModes = this.moreLimit;
|
||||
realMaxModes = Math.min(this.moreLimit, this.size());
|
||||
}
|
||||
|
||||
// use case: limit max modes by opponents (example: choose one or more... each mode must target a different player)
|
||||
|
|
@ -303,12 +303,9 @@ public class Modes extends LinkedHashMap<UUID, Mode> implements Copyable<Modes>
|
|||
this.put(mode.getId(), mode);
|
||||
}
|
||||
|
||||
public void setMoreCondition(Condition moreCondition) {
|
||||
this.moreCondition = moreCondition;
|
||||
}
|
||||
|
||||
public void setMoreLimit(int moreLimit) {
|
||||
public void setMoreCondition(int moreLimit, Condition moreCondition) {
|
||||
this.moreLimit = moreLimit;
|
||||
this.moreCondition = moreCondition;
|
||||
}
|
||||
|
||||
private boolean isAlreadySelectedModesOutdated(Game game, Ability source) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public class ActivateIfConditionActivatedAbility extends ActivatedAbilityImpl {
|
|||
public ActivateIfConditionActivatedAbility(Effect effect, Cost cost, Condition condition) {
|
||||
this(Zone.BATTLEFIELD, effect, cost, condition, TimingRule.INSTANT);
|
||||
}
|
||||
|
||||
|
||||
public ActivateIfConditionActivatedAbility(Zone zone, Effect effect, Cost cost, Condition condition) {
|
||||
this(zone, effect, cost, condition, TimingRule.INSTANT);
|
||||
}
|
||||
|
|
@ -34,6 +34,10 @@ public class ActivateIfConditionActivatedAbility extends ActivatedAbilityImpl {
|
|||
@Override
|
||||
public String getRule() {
|
||||
StringBuilder sb = new StringBuilder(super.getRule());
|
||||
if (condition.toString().startsWith("You may also")) {
|
||||
sb.append(' ').append(condition.toString()).append('.');
|
||||
return sb.toString();
|
||||
}
|
||||
if (condition instanceof InvertCondition) {
|
||||
sb.append(" You can't activate this ability ");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -7,11 +7,14 @@ import mage.constants.TimingRule;
|
|||
import mage.constants.Zone;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author weirddan455
|
||||
*/
|
||||
public class ActivateOncePerGameActivatedAbility extends ActivatedAbilityImpl {
|
||||
|
||||
public ActivateOncePerGameActivatedAbility(Effect effect, Cost cost) {
|
||||
this(Zone.BATTLEFIELD, effect, cost, TimingRule.INSTANT);
|
||||
}
|
||||
|
||||
public ActivateOncePerGameActivatedAbility(Zone zone, Effect effect, Cost cost, TimingRule timingRule) {
|
||||
super(zone, effect, cost);
|
||||
this.timing = timingRule;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
|
||||
package mage.abilities.common;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.SetTargetPointer;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent.EventType;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author LoneFox
|
||||
*/
|
||||
|
|
@ -27,7 +25,7 @@ public class DealtDamageAttachedTriggeredAbility extends TriggeredAbilityImpl {
|
|||
public DealtDamageAttachedTriggeredAbility(Zone zone, Effect effect, boolean optional, SetTargetPointer setTargetPointer) {
|
||||
super(zone, effect, optional);
|
||||
this.setTargetPointer = setTargetPointer;
|
||||
setTriggerPhrase("Whenever enchanted creature is dealt damage, ");
|
||||
setTriggerPhrase(getWhen() + "enchanted creature is dealt damage, ");
|
||||
}
|
||||
|
||||
protected DealtDamageAttachedTriggeredAbility(final DealtDamageAttachedTriggeredAbility ability) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ public class EntersBattlefieldOrAttacksSourceTriggeredAbility extends TriggeredA
|
|||
public EntersBattlefieldOrAttacksSourceTriggeredAbility(Effect effect, boolean optional) {
|
||||
super(Zone.BATTLEFIELD, effect, optional);
|
||||
setTriggerPhrase("Whenever {this} enters or attacks, ");
|
||||
this.withRuleTextReplacement(true);
|
||||
}
|
||||
|
||||
protected EntersBattlefieldOrAttacksSourceTriggeredAbility(final EntersBattlefieldOrAttacksSourceTriggeredAbility ability) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class TransformsOrEntersTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
public TransformsOrEntersTriggeredAbility(Effect effect, boolean optional) {
|
||||
super(Zone.BATTLEFIELD, effect, optional);
|
||||
setTriggerPhrase("Whenever this creature enters the battlefield or transforms into {this}, ");
|
||||
setTriggerPhrase("Whenever this creature enters or transforms into {this}, ");
|
||||
}
|
||||
|
||||
private TransformsOrEntersTriggeredAbility(final TransformsOrEntersTriggeredAbility ability) {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,10 @@ public class AlternativeCostImpl<T extends AlternativeCostImpl<T>> extends Costs
|
|||
if (onlyCost) {
|
||||
return getText();
|
||||
} else {
|
||||
return (name != null ? name : "") + (isMana ? " " : "—") + getText() + (isMana ? "" : '.');
|
||||
String costName = (name != null ? name : "");
|
||||
String delimiter = (!isMana || (!costName.isEmpty() && costName.substring(costName.length() - 1).matches("\\d")))
|
||||
? "—" : " ";
|
||||
return costName + delimiter + getText() + (isMana ? "" : '.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,11 +30,15 @@ public abstract class AlternativeSourceCostsImpl extends StaticAbility implement
|
|||
}
|
||||
|
||||
protected AlternativeSourceCostsImpl(String name, String reminderText, Cost cost) {
|
||||
this(name, reminderText, cost, name);
|
||||
}
|
||||
|
||||
protected AlternativeSourceCostsImpl(String name, String reminderText, Cost cost, String activationKey) {
|
||||
super(Zone.ALL, null);
|
||||
this.name = name;
|
||||
this.reminderText = reminderText;
|
||||
this.alternativeCost = new AlternativeCostImpl<>(name, reminderText, cost);
|
||||
this.activationKey = getActivationKey(name);
|
||||
this.activationKey = getActivationKey(activationKey);
|
||||
}
|
||||
|
||||
protected AlternativeSourceCostsImpl(final AlternativeSourceCostsImpl ability) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
package mage.abilities.costs.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbilityImpl;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.CostImpl;
|
||||
import mage.abilities.costs.SacrificeCost;
|
||||
import mage.constants.AbilityType;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
|
@ -46,19 +45,15 @@ public class SacrificeAllCost extends CostImpl implements SacrificeCost {
|
|||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
UUID activator = controllerId;
|
||||
if (ability.getAbilityType().isActivatedAbility() || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
|
||||
if (((ActivatedAbilityImpl) ability).getActivatorId() != null) {
|
||||
activator = ((ActivatedAbilityImpl) ability).getActivatorId();
|
||||
} // else, Activator not filled?
|
||||
Player controller = game.getPlayer(controllerId);
|
||||
if (controller == null){
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllerId, game)) {
|
||||
if (!game.getPlayer(activator).canPaySacrificeCost(permanent, source, controllerId, game)) {
|
||||
if (!controller.canPaySacrificeCost(permanent, source, controllerId, game)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
package mage.abilities.costs.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbilityImpl;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.CostImpl;
|
||||
import mage.abilities.costs.SacrificeCost;
|
||||
import mage.constants.AbilityType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetSacrifice;
|
||||
import mage.util.CardUtil;
|
||||
|
|
@ -58,12 +57,8 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost {
|
|||
|
||||
@Override
|
||||
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
|
||||
UUID activator = controllerId;
|
||||
if (ability.getAbilityType().isActivatedAbility() || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
|
||||
activator = ((ActivatedAbilityImpl) ability).getActivatorId();
|
||||
}
|
||||
// can be cancel by user
|
||||
if (this.getTargets().choose(Outcome.Sacrifice, activator, source.getSourceId(), source, game)) {
|
||||
// can be cancelled by user
|
||||
if (this.getTargets().choose(Outcome.Sacrifice, controllerId, source.getSourceId(), source, game)) {
|
||||
for (UUID targetId : this.getTargets().get(0).getTargets()) {
|
||||
Permanent permanent = game.getPermanent(targetId);
|
||||
if (permanent == null) {
|
||||
|
|
@ -88,17 +83,14 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost {
|
|||
|
||||
@Override
|
||||
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
|
||||
UUID activator = controllerId;
|
||||
if (ability.getAbilityType().isActivatedAbility() || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) {
|
||||
if (((ActivatedAbilityImpl) ability).getActivatorId() != null) {
|
||||
activator = ((ActivatedAbilityImpl) ability).getActivatorId();
|
||||
} // else, Activator not filled?
|
||||
Player controller = game.getPlayer(controllerId);
|
||||
if (controller == null){
|
||||
return false;
|
||||
}
|
||||
|
||||
int validTargets = 0;
|
||||
int neededTargets = this.getTargets().get(0).getNumberOfTargets();
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(((TargetPermanent) this.getTargets().get(0)).getFilter(), controllerId, source, game)) {
|
||||
if (game.getPlayer(activator).canPaySacrificeCost(permanent, source, controllerId, game)) {
|
||||
if (controller.canPaySacrificeCost(permanent, source, controllerId, game)) {
|
||||
validTargets++;
|
||||
if (validTargets >= neededTargets) {
|
||||
return true;
|
||||
|
|
@ -106,10 +98,7 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost {
|
|||
}
|
||||
}
|
||||
// solves issue #8097, if a sacrifice cost is optional and you don't have valid targets, then the cost can be paid
|
||||
if (validTargets == 0 && this.getTargets().get(0).getMinNumberOfTargets() == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return validTargets == 0 && this.getTargets().get(0).getMinNumberOfTargets() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import mage.abilities.effects.ReplacementEffectImpl;
|
|||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.mageobject.AnotherPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.EntersTheBattlefieldEvent;
|
||||
|
|
@ -45,9 +44,9 @@ public class DevourEffect extends ReplacementEffectImpl {
|
|||
// "creature" is a special case as the rule will not mention it.
|
||||
//
|
||||
// 's' will be added to pluralize, so far so good with the current text generation.
|
||||
private final FilterControlledPermanent filterDevoured;
|
||||
private final FilterPermanent filterDevoured;
|
||||
|
||||
public DevourEffect(int devourFactor, FilterControlledPermanent filterDevoured) {
|
||||
public DevourEffect(int devourFactor, FilterPermanent filterDevoured) {
|
||||
super(Duration.EndOfGame, Outcome.Detriment);
|
||||
this.devourFactor = devourFactor;
|
||||
this.filterDevoured = filterDevoured;
|
||||
|
|
@ -81,11 +80,8 @@ public class DevourEffect extends ReplacementEffectImpl {
|
|||
if (creature == null || controller == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FilterControlledPermanent filter = new FilterControlledPermanent(filterDevoured.getMessage() + "s to devour");
|
||||
for (Predicate predicate : filterDevoured.getPredicates()) {
|
||||
filter.add(predicate);
|
||||
}
|
||||
FilterPermanent filter = filterDevoured.copy();
|
||||
filter.setMessage(filterDevoured.getMessage() + "s (to devour)");
|
||||
filter.add(AnotherPredicate.instance);
|
||||
|
||||
Target target = new TargetSacrifice(1, Integer.MAX_VALUE, filter);
|
||||
|
|
@ -142,9 +138,9 @@ public class DevourEffect extends ReplacementEffectImpl {
|
|||
text += devourFactor;
|
||||
}
|
||||
|
||||
text += " <i>(As this enters the battlefield, you may sacrifice any number of "
|
||||
text += " <i>(As this enters, you may sacrifice any number of "
|
||||
+ filterMessage + "s. "
|
||||
+ "This creature enters the battlefield with ";
|
||||
+ "This creature enters with ";
|
||||
|
||||
if (devourFactor == Integer.MAX_VALUE) {
|
||||
text += "X +1/+1 counters on it for each of those creatures";
|
||||
|
|
|
|||
|
|
@ -35,13 +35,14 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl {
|
|||
* existing creature types.
|
||||
*/
|
||||
|
||||
protected Token token;
|
||||
protected CardType retainType; // if null, loses previous types
|
||||
protected boolean loseAbilities = false;
|
||||
protected boolean loseEquipmentType = false;
|
||||
protected DynamicValue power = null;
|
||||
protected DynamicValue toughness = null;
|
||||
protected boolean durationRuleAtStart; // put duration rule at the start of the rules text rather than the end
|
||||
private final Token token;
|
||||
private final CardType retainType; // if null, loses previous types
|
||||
private boolean loseAbilities = false;
|
||||
private boolean loseEquipmentType = false;
|
||||
private boolean keepCreatureSubtypes;
|
||||
private DynamicValue power = null;
|
||||
private DynamicValue toughness = null;
|
||||
private boolean durationRuleAtStart; // put duration rule at the start of the rules text rather than the end
|
||||
|
||||
/**
|
||||
* @param token Token as blueprint for creature to become
|
||||
|
|
@ -49,20 +50,11 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl {
|
|||
* @param duration Duration for the effect
|
||||
*/
|
||||
public BecomesCreatureSourceEffect(Token token, CardType retainType, Duration duration) {
|
||||
this(token, retainType, duration, (retainType == CardType.PLANESWALKER || retainType == CardType.CREATURE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param token Token as blueprint for creature to become
|
||||
* @param retainType If null, permanent loses its previous types, otherwise retains types with appropriate text
|
||||
* @param duration Duration for the effect
|
||||
* @param durationRuleAtStart for text rule generation
|
||||
*/
|
||||
public BecomesCreatureSourceEffect(Token token, CardType retainType, Duration duration, boolean durationRuleAtStart) {
|
||||
super(duration, Outcome.BecomeCreature);
|
||||
this.token = token;
|
||||
this.retainType = retainType;
|
||||
this.durationRuleAtStart = durationRuleAtStart;
|
||||
this.keepCreatureSubtypes = (retainType == CardType.ENCHANTMENT); // default usage, override if needed
|
||||
this.durationRuleAtStart = (retainType == CardType.PLANESWALKER || retainType == CardType.CREATURE);
|
||||
setText();
|
||||
this.addDependencyType(DependencyType.BecomeCreature);
|
||||
}
|
||||
|
|
@ -73,6 +65,7 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl {
|
|||
this.retainType = effect.retainType;
|
||||
this.loseAbilities = effect.loseAbilities;
|
||||
this.loseEquipmentType = effect.loseEquipmentType;
|
||||
this.keepCreatureSubtypes = effect.keepCreatureSubtypes;
|
||||
if (effect.power != null) {
|
||||
this.power = effect.power.copy();
|
||||
}
|
||||
|
|
@ -124,7 +117,7 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl {
|
|||
if (loseEquipmentType) {
|
||||
permanent.removeSubType(game, SubType.EQUIPMENT);
|
||||
}
|
||||
if (retainType == CardType.CREATURE || retainType == CardType.ARTIFACT) {
|
||||
if (!keepCreatureSubtypes) {
|
||||
permanent.removeAllCreatureTypes(game);
|
||||
}
|
||||
permanent.copySubTypesFrom(game, token);
|
||||
|
|
@ -191,6 +184,16 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source becomes a creature "in addition to its other types".
|
||||
* Not needed when retainType is ENCHANTMENT, which sets this true by default.
|
||||
*/
|
||||
public BecomesCreatureSourceEffect withKeepCreatureSubtypes(boolean keepCreatureSubtypes) {
|
||||
this.keepCreatureSubtypes = keepCreatureSubtypes;
|
||||
setText();
|
||||
return this;
|
||||
}
|
||||
|
||||
public BecomesCreatureSourceEffect withDurationRuleAtStart(boolean durationRuleAtStart) {
|
||||
this.durationRuleAtStart = durationRuleAtStart;
|
||||
setText();
|
||||
|
|
@ -205,7 +208,7 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl {
|
|||
}
|
||||
sb.append("{this} becomes a ");
|
||||
sb.append(token.getDescription());
|
||||
if (retainType == CardType.ENCHANTMENT) {
|
||||
if (keepCreatureSubtypes) {
|
||||
sb.append(" in addition to its other types");
|
||||
}
|
||||
if (!duration.toString().isEmpty() && !durationRuleAtStart) {
|
||||
|
|
|
|||
|
|
@ -14,24 +14,28 @@ import mage.players.Player;
|
|||
* @author notgreat
|
||||
*/
|
||||
public class LookAtOpponentFaceDownCreaturesAnyTimeEffect extends ContinuousEffectImpl {
|
||||
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("face-down creatures you don't control");
|
||||
|
||||
static {
|
||||
filter.add(FaceDownPredicate.instance);
|
||||
filter.add(TargetController.NOT_YOU.getControllerPredicate());
|
||||
}
|
||||
private final FilterCreaturePermanent filter;
|
||||
|
||||
public LookAtOpponentFaceDownCreaturesAnyTimeEffect() {
|
||||
this(Duration.WhileOnBattlefield);
|
||||
}
|
||||
|
||||
public LookAtOpponentFaceDownCreaturesAnyTimeEffect(Duration duration) {
|
||||
this(duration, TargetController.NOT_YOU);
|
||||
}
|
||||
|
||||
public LookAtOpponentFaceDownCreaturesAnyTimeEffect(Duration duration, TargetController targetController) {
|
||||
super(duration, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit);
|
||||
staticText = (duration.toString().isEmpty() ? "" : duration.toString() + ", ") + "you may look at face-down creatures you don't control any time";
|
||||
staticText = makeText(duration, targetController);
|
||||
filter = new FilterCreaturePermanent();
|
||||
filter.add(FaceDownPredicate.instance);
|
||||
filter.add(targetController.getControllerPredicate());
|
||||
}
|
||||
|
||||
protected LookAtOpponentFaceDownCreaturesAnyTimeEffect(final LookAtOpponentFaceDownCreaturesAnyTimeEffect effect) {
|
||||
super(effect);
|
||||
this.filter = effect.filter.copy();
|
||||
}
|
||||
|
||||
//Based on LookAtTopCardOfLibraryAnyTimeEffect
|
||||
|
|
@ -56,4 +60,22 @@ public class LookAtOpponentFaceDownCreaturesAnyTimeEffect extends ContinuousEffe
|
|||
public LookAtOpponentFaceDownCreaturesAnyTimeEffect copy() {
|
||||
return new LookAtOpponentFaceDownCreaturesAnyTimeEffect(this);
|
||||
}
|
||||
|
||||
private static String makeText(Duration duration, TargetController targetController) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (!duration.toString().isEmpty()) {
|
||||
sb.append(duration);
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append("you may look at face-down creatures ");
|
||||
switch (targetController) {
|
||||
case NOT_YOU:
|
||||
sb.append("you don't control ");
|
||||
break;
|
||||
case OPPONENT:
|
||||
sb.append("your opponents control ");
|
||||
}
|
||||
sb.append("any time");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import mage.abilities.dynamicvalue.common.StaticValue;
|
|||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
|
@ -21,25 +22,28 @@ public class LookTargetHandChooseDiscardEffect extends OneShotEffect {
|
|||
|
||||
private final boolean upTo;
|
||||
private final DynamicValue numberToDiscard;
|
||||
private final FilterCard filter;
|
||||
|
||||
public LookTargetHandChooseDiscardEffect() {
|
||||
this(false, 1);
|
||||
}
|
||||
|
||||
public LookTargetHandChooseDiscardEffect(boolean upTo, int numberToDiscard) {
|
||||
this(upTo, StaticValue.get(numberToDiscard));
|
||||
this(upTo, StaticValue.get(numberToDiscard), numberToDiscard == 1 ? StaticFilters.FILTER_CARD : StaticFilters.FILTER_CARD_CARDS);
|
||||
}
|
||||
|
||||
public LookTargetHandChooseDiscardEffect(boolean upTo, DynamicValue numberToDiscard) {
|
||||
public LookTargetHandChooseDiscardEffect(boolean upTo, DynamicValue numberToDiscard, FilterCard filter) {
|
||||
super(Outcome.Discard);
|
||||
this.upTo = upTo;
|
||||
this.numberToDiscard = numberToDiscard;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
protected LookTargetHandChooseDiscardEffect(final LookTargetHandChooseDiscardEffect effect) {
|
||||
super(effect);
|
||||
this.upTo = effect.upTo;
|
||||
this.numberToDiscard = effect.numberToDiscard;
|
||||
this.filter = effect.filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -56,7 +60,7 @@ public class LookTargetHandChooseDiscardEffect extends OneShotEffect {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
TargetCard target = new TargetCardInHand(upTo ? 0 : num, num, num > 1 ? StaticFilters.FILTER_CARD_CARDS : StaticFilters.FILTER_CARD);
|
||||
TargetCard target = new TargetCardInHand(upTo ? 0 : num, num, filter);
|
||||
if (controller.choose(Outcome.Discard, player.getHand(), target, source, game)) {
|
||||
player.discard(new CardsImpl(target.getTargets()), false, source, game);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package mage.abilities.hint.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.common.CitysBlessingCondition;
|
||||
import mage.abilities.hint.ConditionHint;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.game.Game;
|
||||
import mage.game.command.Dungeon;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public enum CurrentDungeonHint implements Hint {
|
||||
|
||||
instance;
|
||||
private static final ConditionHint hint = new ConditionHint(CitysBlessingCondition.instance, "You have city's blessing");
|
||||
|
||||
@Override
|
||||
public String getText(Game game, Ability ability) {
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Dungeon dungeon = game.getPlayerDungeon(ability.getControllerId());
|
||||
if (dungeon == null) {
|
||||
return "Current dungeon: not yet entered";
|
||||
}
|
||||
|
||||
String dungeonInfo = "Current dungeon: " + dungeon.getLogName();
|
||||
if (dungeon.getCurrentRoom() != null) {
|
||||
dungeonInfo += ", room: " + dungeon.getCurrentRoom().getName();
|
||||
}
|
||||
|
||||
return dungeonInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hint copy() {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
|
||||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.common.DevourEffect;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
|
||||
/**
|
||||
* 502.82. Devour
|
||||
|
|
@ -45,12 +44,12 @@ import mage.filter.common.FilterControlledPermanent;
|
|||
*/
|
||||
public class DevourAbility extends SimpleStaticAbility {
|
||||
|
||||
private static final FilterControlledPermanent filterCreature = new FilterControlledCreaturePermanent("creature");
|
||||
private static final FilterPermanent filterCreature = new FilterControlledCreaturePermanent("creature");
|
||||
|
||||
// Integer.MAX_VALUE is a special value
|
||||
// for "devour X, where X is the number of devored permanents"
|
||||
// see DevourEffect for the full details.
|
||||
public static DevourAbility DevourX() {
|
||||
public static DevourAbility devourX() {
|
||||
return new DevourAbility(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +57,7 @@ public class DevourAbility extends SimpleStaticAbility {
|
|||
this(devourFactor, filterCreature);
|
||||
}
|
||||
|
||||
public DevourAbility(int devourFactor, FilterControlledPermanent filterDevoured) {
|
||||
public DevourAbility(int devourFactor, FilterPermanent filterDevoured) {
|
||||
super(Zone.ALL, new DevourEffect(devourFactor, filterDevoured));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,33 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.BeginningOfEndStepTriggeredAbility;
|
||||
import mage.abilities.common.EntersBattlefieldAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.condition.common.SourceHasCounterCondition;
|
||||
import mage.abilities.costs.AlternativeSourceCostsImpl;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.decorator.ConditionalOneShotEffect;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.common.AddContinuousEffectToGame;
|
||||
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||
import mage.abilities.effects.common.counter.RemoveCounterSourceEffect;
|
||||
import mage.constants.*;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* TODO: Implement this
|
||||
* "Impending N–[cost]" is a keyword that represents multiple abilities.
|
||||
* The official rules are as follows:
|
||||
* (a) You may choose to pay [cost] rather than pay this spell's mana cost.
|
||||
* (b) If you chose to pay this spell's impending cost, it enters the battlefield with N time counters on it.
|
||||
* (c) As long as this permanent has a time counter on it, if it was cast for its impending cost, it's not a creature.
|
||||
* (d) At the beginning of your end step, if this permanent was cast for its impending cost, remove a time counter from it. Then if it has no time counters on it, it loses impending.
|
||||
*
|
||||
* @author TheElk801
|
||||
*/
|
||||
|
|
@ -16,14 +37,24 @@ public class ImpendingAbility extends AlternativeSourceCostsImpl {
|
|||
private static final String IMPENDING_REMINDER = "If you cast this spell for its impending cost, " +
|
||||
"it enters with %s time counters and isn't a creature until the last is removed. " +
|
||||
"At the beginning of your end step, remove a time counter from it.";
|
||||
private static final Condition counterCondition = new SourceHasCounterCondition(CounterType.TIME, 0, 0);
|
||||
|
||||
public ImpendingAbility(String manaString) {
|
||||
this(manaString, 4);
|
||||
}
|
||||
|
||||
public ImpendingAbility(String manaString, int amount) {
|
||||
super(IMPENDING_KEYWORD + ' ' + amount, String.format(IMPENDING_REMINDER, CardUtil.numberToText(amount)), manaString);
|
||||
public ImpendingAbility(int amount, String manaString) {
|
||||
super(IMPENDING_KEYWORD + ' ' + amount, String.format(IMPENDING_REMINDER, CardUtil.numberToText(amount)), new ManaCostsImpl<>(manaString), IMPENDING_KEYWORD);
|
||||
this.setRuleAtTheTop(true);
|
||||
this.addSubAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(
|
||||
new AddCountersSourceEffect(CounterType.TIME.createInstance(amount)), ImpendingCondition.instance, ""
|
||||
), "").setRuleVisible(false));
|
||||
this.addSubAbility(new SimpleStaticAbility(new ImpendingAbilityTypeEffect()).setRuleVisible(false));
|
||||
Ability ability = new BeginningOfEndStepTriggeredAbility(
|
||||
new RemoveCounterSourceEffect(CounterType.TIME.createInstance()),
|
||||
TargetController.YOU, ImpendingCondition.instance, false
|
||||
);
|
||||
ability.addEffect(new ConditionalOneShotEffect(
|
||||
new AddContinuousEffectToGame(new ImpendingAbilityRemoveEffect()),
|
||||
counterCondition, "Then if it has no time counters on it, it loses impending"
|
||||
));
|
||||
this.addSubAbility(ability.setRuleVisible(false));
|
||||
}
|
||||
|
||||
private ImpendingAbility(final ImpendingAbility ability) {
|
||||
|
|
@ -35,12 +66,81 @@ public class ImpendingAbility extends AlternativeSourceCostsImpl {
|
|||
return new ImpendingAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(Ability source, Game game) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String getActivationKey() {
|
||||
return getActivationKey(IMPENDING_KEYWORD);
|
||||
}
|
||||
}
|
||||
|
||||
enum ImpendingCondition implements Condition {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return CardUtil.checkSourceCostsTagExists(game, source, ImpendingAbility.getActivationKey());
|
||||
}
|
||||
}
|
||||
|
||||
class ImpendingAbilityTypeEffect extends ContinuousEffectImpl {
|
||||
|
||||
ImpendingAbilityTypeEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment);
|
||||
staticText = "As long as this permanent has a time counter on it, if it was cast for its impending cost, it's not a creature.";
|
||||
}
|
||||
|
||||
private ImpendingAbilityTypeEffect(final ImpendingAbilityTypeEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImpendingAbilityTypeEffect copy() {
|
||||
return new ImpendingAbilityTypeEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
if (!ImpendingCondition.instance.apply(game, source)) {
|
||||
return false;
|
||||
}
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (permanent.getCounters(game).getCount(CounterType.TIME) < 1) {
|
||||
return false;
|
||||
}
|
||||
permanent.removeCardType(game, CardType.CREATURE);
|
||||
permanent.removeAllCreatureTypes(game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ImpendingAbilityRemoveEffect extends ContinuousEffectImpl {
|
||||
|
||||
ImpendingAbilityRemoveEffect() {
|
||||
super(Duration.Custom, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.LoseAbility);
|
||||
}
|
||||
|
||||
private ImpendingAbilityRemoveEffect(final ImpendingAbilityRemoveEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImpendingAbilityRemoveEffect copy() {
|
||||
return new ImpendingAbilityRemoveEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (permanent == null) {
|
||||
discard();
|
||||
return false;
|
||||
}
|
||||
permanent.removeAbilities(
|
||||
permanent
|
||||
.getAbilities(game)
|
||||
.stream()
|
||||
.filter(ImpendingAbility.class::isInstance)
|
||||
.collect(Collectors.toList()),
|
||||
source.getSourceId(), game
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ package mage.constants;
|
|||
* @author LevelX2
|
||||
*/
|
||||
public enum MageObjectType {
|
||||
ABILITY_STACK("Ability on the Stack", false, false, false),
|
||||
ABILITY_STACK_FROM_CARD("Ability on the Stack", false, false, false),
|
||||
ABILITY_STACK_FROM_TOKEN("Ability on the Stack", false, false, true),
|
||||
CARD("Card", false, true, false),
|
||||
COPY_CARD("Copy of a Card", false, true, false),
|
||||
TOKEN("Token", true, true, true),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package mage.designations;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.hint.common.CurrentDungeonHint;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Controllable;
|
||||
|
|
@ -113,6 +114,7 @@ class InitiativeVentureTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
InitiativeVentureTriggeredAbility() {
|
||||
super(Zone.ALL, new InitiativeUndercityEffect());
|
||||
addHint(CurrentDungeonHint.instance);
|
||||
}
|
||||
|
||||
private InitiativeVentureTriggeredAbility(final InitiativeVentureTriggeredAbility ability) {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,9 @@ public class FilterCard extends FilterObject<Card> {
|
|||
throw new UnsupportedOperationException("You may not modify a locked filter");
|
||||
}
|
||||
|
||||
// verify check
|
||||
checkPredicateIsSuitableForCardFilter(predicate);
|
||||
Predicates.makeSurePredicateCompatibleWithFilter(predicate, Card.class);
|
||||
|
||||
extraPredicates.add(predicate);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import mage.constants.SubType;
|
|||
import mage.filter.predicate.ObjectSourcePlayer;
|
||||
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
|
|
@ -12,7 +13,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author North
|
||||
|
|
@ -64,6 +64,10 @@ public class FilterPermanent extends FilterObject<Permanent> implements FilterIn
|
|||
if (isLockedFilter()) {
|
||||
throw new UnsupportedOperationException("You may not modify a locked filter");
|
||||
}
|
||||
|
||||
// verify check
|
||||
Predicates.makeSurePredicateCompatibleWithFilter(predicate, Permanent.class);
|
||||
|
||||
extraPredicates.add(predicate);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import mage.abilities.Ability;
|
|||
import mage.filter.predicate.ObjectSourcePlayer;
|
||||
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
|
|
@ -36,6 +37,10 @@ public class FilterPlayer extends FilterImpl<Player> {
|
|||
if (isLockedFilter()) {
|
||||
throw new UnsupportedOperationException("You may not modify a locked filter");
|
||||
}
|
||||
|
||||
// verify check
|
||||
Predicates.makeSurePredicateCompatibleWithFilter(predicate, Player.class);
|
||||
|
||||
extraPredicates.add(predicate);
|
||||
return this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
package mage.filter;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.cards.Card;
|
||||
import mage.filter.predicate.ObjectSourcePlayer;
|
||||
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -43,6 +46,11 @@ public class FilterStackObject extends FilterObject<StackObject> {
|
|||
if (isLockedFilter()) {
|
||||
throw new UnsupportedOperationException("You may not modify a locked filter");
|
||||
}
|
||||
|
||||
// verify check
|
||||
// Spell implements Card interface, so it can use some default predicates like owner
|
||||
Predicates.makeSurePredicateCompatibleWithFilter(predicate, StackObject.class, Spell.class, Card.class);
|
||||
|
||||
extraPredicates.add(predicate);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.filter.predicate;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package mage.filter.predicate;
|
|||
|
||||
import mage.game.Game;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
|
@ -11,7 +13,7 @@ import java.util.List;
|
|||
*
|
||||
* <p>All methods returns serializable predicates as long as they're given serializable parameters.</p>
|
||||
*
|
||||
* @author North
|
||||
* @author North, JayDi85
|
||||
*/
|
||||
public final class Predicates {
|
||||
|
||||
|
|
@ -246,4 +248,48 @@ public final class Predicates {
|
|||
extraPredicates.forEach(p -> collectAllComponents(p, res));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify check: try to find filters usage
|
||||
* Example use case: Player predicate was used for Permanent filter
|
||||
* Example error: java.lang.ClassCastException: mage.game.permanent.PermanentToken cannot be cast to mage.players.Player
|
||||
*/
|
||||
public static void makeSurePredicateCompatibleWithFilter(Predicate predicate, Class... compatibleClasses) {
|
||||
List<Predicate> list = new ArrayList<>();
|
||||
Predicates.collectAllComponents(predicate, list);
|
||||
list.forEach(p -> {
|
||||
Class predicateGenericParamClass = findGenericParam(predicate);
|
||||
if (predicateGenericParamClass == null) {
|
||||
throw new IllegalArgumentException("Somthing wrong. Can't find predicate's generic param for " + predicate.getClass());
|
||||
}
|
||||
if (Arrays.stream(compatibleClasses).anyMatch(f -> predicateGenericParamClass.isAssignableFrom(f))) {
|
||||
// predicate is fine
|
||||
} else {
|
||||
// How-to fix: use correct predicates (same type, e.g. getControllerPredicate() instead getPlayerPredicate())
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Wrong code usage: predicate [%s] with generic param [%s] can't be added to filter, allow only %s",
|
||||
predicate.getClass(),
|
||||
predicateGenericParamClass,
|
||||
Arrays.toString(compatibleClasses)
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Class findGenericParam(Predicate predicate) {
|
||||
Type[] interfaces = predicate.getClass().getGenericInterfaces();
|
||||
for (Type type : interfaces) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
|
||||
if (actualTypeArguments.length > 0) {
|
||||
Type actualType = actualTypeArguments[0];
|
||||
if (actualType instanceof Class) {
|
||||
return (Class) actualType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
63
Mage/src/main/java/mage/game/FakeGame.java
Normal file
63
Mage/src/main/java/mage/game/FakeGame.java
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package mage.game;
|
||||
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.match.MatchType;
|
||||
import mage.game.mulligan.MulliganType;
|
||||
|
||||
/**
|
||||
* Fake game for tests and data check, do nothing.
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class FakeGame extends GameImpl {
|
||||
|
||||
private int numPlayers;
|
||||
|
||||
public FakeGame() {
|
||||
super(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 60, 20, 7);
|
||||
}
|
||||
|
||||
public FakeGame(final FakeGame game) {
|
||||
super(game);
|
||||
this.numPlayers = game.numPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatchType getGameType() {
|
||||
return new FakeGameType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumPlayers() {
|
||||
return numPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FakeGame copy() {
|
||||
return new FakeGame(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FakeGameType extends MatchType {
|
||||
|
||||
public FakeGameType() {
|
||||
this.name = "Test Game Type";
|
||||
this.maxPlayers = 10;
|
||||
this.minPlayers = 3;
|
||||
this.numTeams = 0;
|
||||
this.useAttackOption = true;
|
||||
this.useRange = true;
|
||||
this.sideboardingAllowed = true;
|
||||
}
|
||||
|
||||
protected FakeGameType(final FakeGameType matchType) {
|
||||
super(matchType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FakeGameType copy() {
|
||||
return new FakeGameType(this);
|
||||
}
|
||||
}
|
||||
21
Mage/src/main/java/mage/game/FakeMatch.java
Normal file
21
Mage/src/main/java/mage/game/FakeMatch.java
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package mage.game;
|
||||
|
||||
import mage.game.match.MatchImpl;
|
||||
import mage.game.match.MatchOptions;
|
||||
|
||||
/**
|
||||
* Fake match for tests and data check, do nothing.
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class FakeMatch extends MatchImpl {
|
||||
|
||||
public FakeMatch() {
|
||||
super(new MatchOptions("fake match", "fake game type", true, 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startGame() throws GameException {
|
||||
throw new IllegalStateException("Can't start fake match");
|
||||
}
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
|
|||
|
||||
Dungeon getDungeon(UUID objectId);
|
||||
|
||||
Dungeon getPlayerDungeon(UUID objectId);
|
||||
Dungeon getPlayerDungeon(UUID playerId);
|
||||
|
||||
UUID getControllerId(UUID objectId);
|
||||
|
||||
|
|
@ -456,7 +456,12 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
|
|||
|
||||
Dungeon addDungeon(Dungeon dungeon, UUID playerId);
|
||||
|
||||
void ventureIntoDungeon(UUID playerId, boolean undercity);
|
||||
/**
|
||||
* Enter to dungeon or go to next room
|
||||
*
|
||||
* @param isEnterToUndercity - enter to Undercity instead choose a new dungeon
|
||||
*/
|
||||
void ventureIntoDungeon(UUID playerId, boolean isEnterToUndercity);
|
||||
|
||||
void temptWithTheRing(UUID playerId);
|
||||
|
||||
|
|
|
|||
|
|
@ -560,14 +560,14 @@ public abstract class GameImpl implements Game {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void ventureIntoDungeon(UUID playerId, boolean undercity) {
|
||||
public void ventureIntoDungeon(UUID playerId, boolean isEnterToUndercity) {
|
||||
if (playerId == null) {
|
||||
return;
|
||||
}
|
||||
if (replaceEvent(GameEvent.getEvent(GameEvent.EventType.VENTURE, playerId, null, playerId))) {
|
||||
return;
|
||||
}
|
||||
this.getOrCreateDungeon(playerId, undercity).moveToNextRoom(playerId, this);
|
||||
this.getOrCreateDungeon(playerId, isEnterToUndercity).moveToNextRoom(playerId, this);
|
||||
fireEvent(GameEvent.getEvent(GameEvent.EventType.VENTURED, playerId, null, playerId));
|
||||
}
|
||||
|
||||
|
|
@ -584,6 +584,9 @@ public abstract class GameImpl implements Game {
|
|||
return emblem;
|
||||
}
|
||||
TheRingEmblem newEmblem = new TheRingEmblem(playerId);
|
||||
|
||||
// TODO: add image info
|
||||
|
||||
state.addCommandObject(newEmblem);
|
||||
return newEmblem;
|
||||
}
|
||||
|
|
@ -1971,7 +1974,9 @@ public abstract class GameImpl implements Game {
|
|||
ability.setSourceId(newEmblem.getId());
|
||||
}
|
||||
|
||||
state.addCommandObject(newEmblem); // TODO: generate image for emblem here?
|
||||
// image info setup in setSourceObject
|
||||
|
||||
state.addCommandObject(newEmblem);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1999,6 +2004,9 @@ public abstract class GameImpl implements Game {
|
|||
for (Ability ability : newPlane.getAbilities()) {
|
||||
ability.setSourceId(newPlane.getId());
|
||||
}
|
||||
|
||||
// image info setup in setSourceObject
|
||||
|
||||
state.addCommandObject(newPlane);
|
||||
informPlayers("You have planeswalked to " + newPlane.getLogName());
|
||||
|
||||
|
|
@ -2020,6 +2028,7 @@ public abstract class GameImpl implements Game {
|
|||
@Override
|
||||
public Dungeon addDungeon(Dungeon dungeon, UUID playerId) {
|
||||
dungeon.setControllerId(playerId);
|
||||
dungeon.setSourceObject();
|
||||
state.addCommandObject(dungeon);
|
||||
return dungeon;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import mage.abilities.effects.ContinuousEffect;
|
|||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.hint.HintUtils;
|
||||
import mage.cards.FrameStyle;
|
||||
import mage.cards.repository.TokenInfo;
|
||||
import mage.cards.repository.TokenRepository;
|
||||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceHintType;
|
||||
import mage.choices.ChoiceImpl;
|
||||
|
|
@ -87,6 +89,11 @@ public class Dungeon extends CommandObjectImpl {
|
|||
}
|
||||
|
||||
public void moveToNextRoom(UUID playerId, Game game) {
|
||||
Dungeon dungeon = game.getPlayerDungeon(playerId);
|
||||
if (dungeon == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentRoom == null) {
|
||||
currentRoom = dungeonRooms.get(0);
|
||||
} else {
|
||||
|
|
@ -94,7 +101,7 @@ public class Dungeon extends CommandObjectImpl {
|
|||
}
|
||||
Player player = game.getPlayer(getControllerId());
|
||||
if (player != null) {
|
||||
game.informPlayers(player.getLogName() + " has entered " + currentRoom.getName());
|
||||
game.informPlayers(player.getLogName() + " has entered " + currentRoom.getName() + " (dungeon: " + dungeon.getLogName() + ")");
|
||||
}
|
||||
game.fireEvent(GameEvent.getEvent(
|
||||
GameEvent.EventType.ROOM_ENTERED, currentRoom.getId(), null, playerId
|
||||
|
|
@ -139,14 +146,14 @@ public class Dungeon extends CommandObjectImpl {
|
|||
choice.setChoices(dungeonNames);
|
||||
player.choose(Outcome.Neutral, choice, game);
|
||||
if (choice.getChoice() != null) {
|
||||
return createDungeon(choice.getChoice());
|
||||
return createDungeon(choice.getChoice(), true);
|
||||
} else {
|
||||
// on disconnect
|
||||
return createDungeon("Tomb of Annihilation");
|
||||
return createDungeon("Tomb of Annihilation", true);
|
||||
}
|
||||
}
|
||||
|
||||
public static Dungeon createDungeon(String name) {
|
||||
public static Dungeon createDungeon(String name, boolean isNameMustExists) {
|
||||
switch (name) {
|
||||
case "Tomb of Annihilation":
|
||||
return new TombOfAnnihilationDungeon();
|
||||
|
|
@ -155,7 +162,26 @@ public class Dungeon extends CommandObjectImpl {
|
|||
case "Dungeon of the Mad Mage":
|
||||
return new DungeonOfTheMadMageDungeon();
|
||||
default:
|
||||
throw new UnsupportedOperationException("A dungeon should have been chosen");
|
||||
if (isNameMustExists) {
|
||||
throw new UnsupportedOperationException("A dungeon should have been chosen");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setSourceObject() {
|
||||
// choose set code due source
|
||||
TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForClass(this.getClass().getName(), null);
|
||||
if (foundInfo != null) {
|
||||
this.setExpansionSetCode(foundInfo.getSetCode());
|
||||
this.setUsesVariousArt(false);
|
||||
this.setCardNumber("");
|
||||
this.setImageFileName(""); // use default
|
||||
this.setImageNumber(foundInfo.getImageNumber());
|
||||
} else {
|
||||
// how-to fix: add dungeon to the tokens-database
|
||||
throw new IllegalArgumentException("Wrong code usage: can't find token info for the dungeon: " + this.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,11 @@ public class DungeonRoom {
|
|||
}
|
||||
|
||||
public DungeonRoom chooseNextRoom(UUID playerId, Game game) {
|
||||
Dungeon dungeon = game.getPlayerDungeon(playerId);
|
||||
if (dungeon == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (nextRooms.size()) {
|
||||
case 0:
|
||||
return null;
|
||||
|
|
@ -90,8 +95,8 @@ public class DungeonRoom {
|
|||
return null;
|
||||
}
|
||||
return player.chooseUse(
|
||||
Outcome.Neutral, "Choose which room to go to",
|
||||
null, room1.name, room2.name, null, game
|
||||
Outcome.Neutral, "Choose which room to go to in",
|
||||
"dungeon: " + dungeon.getLogName(), room1.name, room2.name, null, game
|
||||
) ? room1 : room2;
|
||||
default:
|
||||
throw new UnsupportedOperationException("there shouldn't be more than two rooms to go to");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.target;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
|
|
@ -110,7 +109,7 @@ public class TargetSpell extends TargetObject {
|
|||
private boolean canBeChosen(StackObject stackObject, UUID sourceControllerId, Ability source, Game game) {
|
||||
return stackObject instanceof Spell
|
||||
&& game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId())
|
||||
&& filter.match(stackObject, sourceControllerId, source, game);
|
||||
&& canTarget(sourceControllerId, stackObject.getId(), source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue