mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
Ready for Review: Implementing Battles (#10156)
* add types and subtypes * add startingDefense attribute * [MOM] Implement Invasion of Ravnica / Guildpact Paragon * fix two small errors * refactor various instances of "any target" * fully implement defense counters * battles can now be attacked * [MOM] Implement Invasion of Dominaria / Serra Faithkeeper * [MOM] Implement Invasion of Innistrad / Deluge of the Dead * [MOM] Implement Invasion of Kaladesh / Aetherwing, Golden-Scale Flagship * [MOM] Implement Invasion of Kamigawa / Rooftop Saboteurs * [MOM] Implement Invasion of Karsus / Refraction Elemental * [MOM] Implement Invasion of Tolvada / The Broken Sky * simplify battle info ability * fix verify failure * some more fixes for attacking battles * [MOM] Implement Invasion of Kaldheim / Pyre of the World Tree * [MOM] Implement Invasion of Lorwyn / Winnowing Forces * [MOM] Implement Invasion of Moag / Bloomwielder Dryads * [MOM] Implement Invasion of Shandalar / Leyline Surge * [MOM] Implement Invasion of Belenon / Belenon War Anthem * [MOM] Implement Invasion of Pyrulea / Gargantuan Slabhorn * [MOM] Implement Invasion of Vryn / Overloaded Mage-Ring * [MOM] Implement Marshal of Zhalfir * [MOM] Implement Sunfall * implement protectors for sieges * partially implement siege defeated trigger * fix verify failure * some updates to blocking * [MOM] Implement Invasion of Mercadia / Kyren Flamewright * [MOM] Implement Invasion of Theros / Ephara, Ever-Sheltering * [MOM] Implement Invasion of Ulgrotha / Grandmother Ravi Sengir * [MOM] Implement Invasion of Xerex / Vertex Paladin * add initial battle test * fix verify failure * [MOM] Implement Invasion of Amonkhet / Lazotep Convert * [MOM] update spoiler * update how protectors are chosen * update text * battles can't block * add control change test * rename battle test for duel * add multiplayer test * [MOM] Implement Invasion of Alara / Awaken the Maelstrom * [MOM] Implement Invasion of Eldraine * [MOM] Implement Invasion of Ergamon / Truga Cliffhanger * [MOM] Implement Invasion of Ixalan / Belligerent Regisaur * battles now cast transformed (this is super hacky but we need to refactor TDFCs anyway) * add TODO * add ignore for randomly failing test * a few small fixes * add defense to MtgJsonCard (unused like loyalty) * implement ProtectorIdPredicate * small fixes
This commit is contained in:
parent
edf1cff8a8
commit
947351932b
129 changed files with 4057 additions and 1087 deletions
|
|
@ -116,6 +116,10 @@ public interface MageObject extends MageItem, Serializable, Copyable<MageObject>
|
|||
|
||||
void setStartingLoyalty(int startingLoyalty);
|
||||
|
||||
int getStartingDefense();
|
||||
|
||||
void setStartingDefense(int startingDefense);
|
||||
|
||||
// memory object copy (not mtg)
|
||||
@Override
|
||||
MageObject copy();
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public abstract class MageObjectImpl implements MageObject {
|
|||
protected MageInt power;
|
||||
protected MageInt toughness;
|
||||
protected int startingLoyalty = -1; // -2 means X, -1 means none, 0 and up is normal
|
||||
protected int startingDefense = -1; // -2 means X, -1 means none, 0 and up is normal
|
||||
protected boolean copy;
|
||||
protected MageObject copyFrom; // copied card INFO (used to call original adjusters)
|
||||
|
||||
|
|
@ -69,6 +70,7 @@ public abstract class MageObjectImpl implements MageObject {
|
|||
power = object.power.copy();
|
||||
toughness = object.toughness.copy();
|
||||
startingLoyalty = object.startingLoyalty;
|
||||
startingDefense = object.startingDefense;
|
||||
abilities = object.abilities.copy();
|
||||
this.cardType.addAll(object.cardType);
|
||||
this.subtype.copyFrom(object.subtype);
|
||||
|
|
@ -176,6 +178,16 @@ public abstract class MageObjectImpl implements MageObject {
|
|||
this.startingLoyalty = startingLoyalty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartingDefense() {
|
||||
return startingDefense;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartingDefense(int startingDefense) {
|
||||
this.startingDefense = startingDefense;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectColor getColor() {
|
||||
return color;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package mage.abilities;
|
|||
|
||||
import mage.MageIdentifier;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.common.EntersBattlefieldAbility;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.costs.*;
|
||||
import mage.abilities.costs.common.PayLifeCost;
|
||||
|
|
@ -19,7 +18,6 @@ import mage.abilities.hint.Hint;
|
|||
import mage.abilities.icon.CardIcon;
|
||||
import mage.abilities.mana.ActivatedManaAbilityImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.SplitCard;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.command.Dungeon;
|
||||
|
|
@ -437,7 +435,7 @@ public abstract class AbilityImpl implements Ability {
|
|||
|
||||
case FLASHBACK:
|
||||
case MADNESS:
|
||||
case DISTURB:
|
||||
case TRANSFORMED:
|
||||
// from Snapcaster Mage:
|
||||
// If you cast a spell from a graveyard using its flashback ability, you can't pay other alternative costs
|
||||
// (such as that of Foil). (2018-12-07)
|
||||
|
|
|
|||
147
Mage/src/main/java/mage/abilities/common/SiegeAbility.java
Normal file
147
Mage/src/main/java/mage/abilities/common/SiegeAbility.java
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.ApprovingObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.keyword.TransformAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.*;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.players.Player;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class SiegeAbility extends StaticAbility {
|
||||
|
||||
public SiegeAbility() {
|
||||
super(Zone.ALL, null);
|
||||
this.addSubAbility(new TransformAbility());
|
||||
this.addSubAbility(new SiegeDefeatedTriggeredAbility());
|
||||
}
|
||||
|
||||
private SiegeAbility(final SiegeAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SiegeAbility copy() {
|
||||
return new SiegeAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "<i>(As a Siege enters, choose an opponent to protect it. You and others " +
|
||||
"can attack it. When it's defeated, exile it, then cast it transformed.)</i>";
|
||||
}
|
||||
}
|
||||
|
||||
class SiegeDefeatedTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
SiegeDefeatedTriggeredAbility() {
|
||||
super(Zone.BATTLEFIELD, new SiegeDefeatedEffect());
|
||||
this.setRuleVisible(false);
|
||||
}
|
||||
|
||||
private SiegeDefeatedTriggeredAbility(final SiegeDefeatedTriggeredAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SiegeDefeatedTriggeredAbility copy() {
|
||||
return new SiegeDefeatedTriggeredAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.COUNTERS_REMOVED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
Permanent permanent = getSourcePermanentOrLKI(game);
|
||||
return permanent != null
|
||||
&& permanent.getCounters(game).getCount(CounterType.DEFENSE) == 0
|
||||
&& event.getTargetId().equals(this.getSourceId())
|
||||
&& event.getData().equals("defense") && event.getAmount() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "When the last defense counter is removed from this permanent, exile it, " +
|
||||
"then you may cast it transformed without paying its mana cost.";
|
||||
}
|
||||
}
|
||||
|
||||
class SiegeDefeatedEffect extends OneShotEffect {
|
||||
|
||||
SiegeDefeatedEffect() {
|
||||
super(Outcome.Benefit);
|
||||
}
|
||||
|
||||
private SiegeDefeatedEffect(final SiegeDefeatedEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SiegeDefeatedEffect copy() {
|
||||
return new SiegeDefeatedEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (player == null || permanent == null) {
|
||||
return false;
|
||||
}
|
||||
Card card = permanent.getMainCard();
|
||||
player.moveCards(permanent, Zone.EXILED, source, game);
|
||||
if (card == null || card.getSecondFaceSpellAbility() == null) {
|
||||
return true;
|
||||
}
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + card.getSecondCardFace().getId(), Boolean.TRUE);
|
||||
if (player.cast(card.getSecondFaceSpellAbility(), game, true, new ApprovingObject(source, game))) {
|
||||
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId(), Boolean.TRUE);
|
||||
game.addEffect(new SiegeTransformEffect().setTargetPointer(new FixedTarget(card, game)), source);
|
||||
}
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + card.getSecondCardFace().getId(), null);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class SiegeTransformEffect extends ContinuousEffectImpl {
|
||||
|
||||
public SiegeTransformEffect() {
|
||||
super(Duration.WhileOnStack, Layer.CopyEffects_1, SubLayer.CopyEffects_1a, Outcome.BecomeCreature);
|
||||
}
|
||||
|
||||
private SiegeTransformEffect(final SiegeTransformEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SiegeTransformEffect copy() {
|
||||
return new SiegeTransformEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Spell spell = game.getSpell(getTargetPointer().getFirst(game, source));
|
||||
if (spell == null || spell.getCard().getSecondCardFace() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// simulate another side as new card (another code part in spell constructor)
|
||||
TransformAbility.transformCardSpellDynamic(spell, spell.getCard().getSecondCardFace(), game);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -134,6 +134,7 @@ public class CopyEffect extends ContinuousEffectImpl {
|
|||
permanent.getPower().setModifiedBaseValue(copyFromObject.getPower().getModifiedBaseValue());
|
||||
permanent.getToughness().setModifiedBaseValue(copyFromObject.getToughness().getModifiedBaseValue());
|
||||
permanent.setStartingLoyalty(copyFromObject.getStartingLoyalty());
|
||||
permanent.setStartingDefense(copyFromObject.getStartingDefense());
|
||||
if (copyFromObject instanceof Permanent) {
|
||||
Permanent targetPermanent = (Permanent) copyFromObject;
|
||||
permanent.setTransformed(targetPermanent.isTransformed());
|
||||
|
|
@ -199,8 +200,9 @@ public class CopyEffect extends ContinuousEffectImpl {
|
|||
return applier;
|
||||
}
|
||||
|
||||
public void setApplier(CopyApplier applier) {
|
||||
public CopyEffect setApplier(CopyApplier applier) {
|
||||
this.applier = applier;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class DisturbAbility extends SpellAbility {
|
|||
this.setCardName(card.getSecondCardFace().getName() + " with Disturb");
|
||||
this.zone = Zone.GRAVEYARD;
|
||||
this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
|
||||
this.spellAbilityCastMode = SpellAbilityCastMode.DISTURB;
|
||||
this.spellAbilityCastMode = SpellAbilityCastMode.TRANSFORMED;
|
||||
|
||||
this.manaCost = manaCost;
|
||||
this.getManaCosts().clear();
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ public class MoreThanMeetsTheEyeAbility extends SpellAbility {
|
|||
// getSecondFaceSpellAbility() already verified that second face exists
|
||||
this.setCardName(card.getSecondCardFace().getName() + " with Disturb");
|
||||
this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
|
||||
this.spellAbilityCastMode = SpellAbilityCastMode.DISTURB;
|
||||
this.spellAbilityCastMode = SpellAbilityCastMode.TRANSFORMED;
|
||||
|
||||
this.manaCost = manaCost;
|
||||
this.getManaCosts().clear();
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ public class TransformAbility extends SimpleStaticAbility {
|
|||
permanent.getPower().setModifiedBaseValue(sourceCard.getPower().getValue());
|
||||
permanent.getToughness().setModifiedBaseValue(sourceCard.getToughness().getValue());
|
||||
permanent.setStartingLoyalty(sourceCard.getStartingLoyalty());
|
||||
permanent.setStartingDefense(sourceCard.getStartingDefense());
|
||||
}
|
||||
|
||||
public static Card transformCardSpellStatic(Card mainSide, Card otherSide, Game game) {
|
||||
|
|
|
|||
|
|
@ -614,12 +614,17 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
|||
@Override
|
||||
public final Card getSecondCardFace() {
|
||||
// init card side on first call
|
||||
if (secondSideCardClazz == null && secondSideCard == null) {
|
||||
if (secondSideCardClazz == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (secondSideCard == null) {
|
||||
secondSideCard = initSecondSideCard(secondSideCardClazz);
|
||||
if (secondSideCard != null && secondSideCard.getSpellAbility() != null) {
|
||||
secondSideCard.getSpellAbility().setSourceId(this.getId());
|
||||
secondSideCard.getSpellAbility().setSpellAbilityType(SpellAbilityType.BASE_ALTERNATE);
|
||||
secondSideCard.getSpellAbility().setSpellAbilityCastMode(SpellAbilityCastMode.TRANSFORMED);
|
||||
}
|
||||
}
|
||||
|
||||
return secondSideCard;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ public class CardInfo {
|
|||
@DatabaseField
|
||||
protected String startingLoyalty;
|
||||
@DatabaseField
|
||||
protected String startingDefense;
|
||||
@DatabaseField
|
||||
protected int manaValue;
|
||||
@DatabaseField(dataType = DataType.ENUM_STRING)
|
||||
protected Rarity rarity;
|
||||
|
|
@ -226,7 +228,8 @@ public class CardInfo {
|
|||
}
|
||||
|
||||
// Starting loyalty
|
||||
this.startingLoyalty = CardUtil.convertStartingLoyalty(card.getStartingLoyalty());
|
||||
this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty());
|
||||
this.startingDefense = CardUtil.convertLoyaltyOrDefense(card.getStartingDefense());
|
||||
}
|
||||
|
||||
public Card getCard() {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public enum SpellAbilityCastMode {
|
|||
MADNESS("Madness"),
|
||||
FLASHBACK("Flashback"),
|
||||
BESTOW("Bestow"),
|
||||
DISTURB("Disturb");
|
||||
TRANSFORMED("Transformed");
|
||||
|
||||
private final String text;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ public enum SubType {
|
|||
ARCANE("Arcane", SubTypeSet.SpellType),
|
||||
LESSON("Lesson", SubTypeSet.SpellType),
|
||||
TRAP("Trap", SubTypeSet.SpellType),
|
||||
|
||||
// Battle subtypes
|
||||
SIEGE("Siege", SubTypeSet.BattleType),
|
||||
|
||||
// 205.3i: Lands have their own unique set of subtypes; these subtypes are called land types.
|
||||
// Of that list, Forest, Island, Mountain, Plains, and Swamp are the basic land types.
|
||||
FOREST("Forest", SubTypeSet.BasicLandType),
|
||||
|
|
@ -623,6 +627,8 @@ public enum SubType {
|
|||
return mageObject.isPlaneswalker(game);
|
||||
case SpellType:
|
||||
return mageObject.isInstantOrSorcery(game);
|
||||
case BattleType:
|
||||
return mageObject.isBattle(game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -637,12 +643,10 @@ public enum SubType {
|
|||
|
||||
public static Set<SubType> getPlaneswalkerTypes() {
|
||||
return subTypeSetMap.get(SubTypeSet.PlaneswalkerType);
|
||||
|
||||
}
|
||||
|
||||
public static Set<SubType> getCreatureTypes() {
|
||||
return subTypeSetMap.get(SubTypeSet.CreatureType);
|
||||
|
||||
}
|
||||
|
||||
public static Set<SubType> getBasicLands() {
|
||||
|
|
@ -653,6 +657,10 @@ public enum SubType {
|
|||
return landTypes;
|
||||
}
|
||||
|
||||
public static Set<SubType> getBattleTypes() {
|
||||
return subTypeSetMap.get(SubTypeSet.BattleType);
|
||||
}
|
||||
|
||||
public static Set<SubType> getBySubTypeSet(SubTypeSet subTypeSet) {
|
||||
return subTypeSetMap.get(subTypeSet);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.constants;
|
||||
|
||||
public enum SubTypeSet {
|
||||
BattleType,
|
||||
CreatureType,
|
||||
SpellType,
|
||||
BasicLandType(true),
|
||||
|
|
|
|||
|
|
@ -219,6 +219,15 @@ public abstract class Designation implements MageObject {
|
|||
public void setStartingLoyalty(int startingLoyalty) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartingDefense() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartingDefense(int startingDefense) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getZoneChangeCounter(Game game) {
|
||||
return 1; // Emblems can't move zones until now so return always 1
|
||||
|
|
|
|||
|
|
@ -230,6 +230,12 @@ public final class StaticFilters {
|
|||
FILTER_CARD_A_PERMANENT.setLockedFilter(true);
|
||||
}
|
||||
|
||||
public static final FilterPermanentCard FILTER_CARD_PERMANENTS = new FilterPermanentCard("permanent cards");
|
||||
|
||||
static {
|
||||
FILTER_CARD_PERMANENTS.setLockedFilter(true);
|
||||
}
|
||||
|
||||
public static final FilterPermanent FILTER_PERMANENT = new FilterPermanent();
|
||||
|
||||
static {
|
||||
|
|
|
|||
32
Mage/src/main/java/mage/filter/common/FilterAnyTarget.java
Normal file
32
Mage/src/main/java/mage/filter/common/FilterAnyTarget.java
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package mage.filter.common;
|
||||
|
||||
import mage.constants.CardType;
|
||||
import mage.filter.predicate.Predicates;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class FilterAnyTarget extends FilterPermanentOrPlayer {
|
||||
|
||||
public FilterAnyTarget() {
|
||||
this("any target");
|
||||
}
|
||||
|
||||
public FilterAnyTarget(String name) {
|
||||
super(name);
|
||||
this.permanentFilter.add(Predicates.or(
|
||||
CardType.CREATURE.getPredicate(),
|
||||
CardType.PLANESWALKER.getPredicate(),
|
||||
CardType.BATTLE.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
public FilterAnyTarget(final FilterAnyTarget filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterAnyTarget copy() {
|
||||
return new FilterAnyTarget(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,8 @@
|
|||
package mage.filter.common;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.filter.predicate.Predicates;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* If you add predicate to permanentFilter then it will be applied to planeswalker too
|
||||
*
|
||||
|
|
@ -19,25 +11,14 @@ import java.util.stream.Collectors;
|
|||
public class FilterCreaturePlayerOrPlaneswalker extends FilterPermanentOrPlayer {
|
||||
|
||||
public FilterCreaturePlayerOrPlaneswalker() {
|
||||
this("any target");
|
||||
this("creature, player, or planeswalker");
|
||||
}
|
||||
|
||||
public FilterCreaturePlayerOrPlaneswalker(String name) {
|
||||
this(name, (SubType) null);
|
||||
}
|
||||
|
||||
public FilterCreaturePlayerOrPlaneswalker(String name, SubType... andCreatureTypes) {
|
||||
super(name);
|
||||
List<Predicate<MageObject>> allCreaturePredicates = Arrays.stream(andCreatureTypes)
|
||||
.filter(Objects::nonNull)
|
||||
.map(SubType::getPredicate)
|
||||
.collect(Collectors.toList());
|
||||
allCreaturePredicates.add(0, CardType.CREATURE.getPredicate());
|
||||
Predicate<MageObject> planeswalkerPredicate = CardType.PLANESWALKER.getPredicate();
|
||||
|
||||
this.permanentFilter.add(Predicates.or(
|
||||
Predicates.and(allCreaturePredicates),
|
||||
planeswalkerPredicate
|
||||
CardType.CREATURE.getPredicate(),
|
||||
CardType.PLANESWALKER.getPredicate()
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
46
Mage/src/main/java/mage/filter/common/FilterDefender.java
Normal file
46
Mage/src/main/java/mage/filter/common/FilterDefender.java
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package mage.filter.common;
|
||||
|
||||
import mage.constants.CardType;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.other.PlayerIdPredicate;
|
||||
import mage.filter.predicate.permanent.ControllerIdPredicate;
|
||||
import mage.filter.predicate.permanent.PermanentIdPredicate;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class FilterDefender extends FilterPermanentOrPlayer {
|
||||
|
||||
public FilterDefender(Set<UUID> defenders) {
|
||||
super("player, planeswalker, or battle to attack");
|
||||
this.permanentFilter.add(Predicates.or(
|
||||
CardType.PLANESWALKER.getPredicate(),
|
||||
CardType.BATTLE.getPredicate()
|
||||
));
|
||||
this.permanentFilter.add(Predicates.or(
|
||||
defenders
|
||||
.stream()
|
||||
.map(PermanentIdPredicate::new)
|
||||
.collect(Collectors.toList())
|
||||
));
|
||||
this.playerFilter.add(Predicates.or(
|
||||
defenders
|
||||
.stream()
|
||||
.map(PlayerIdPredicate::new)
|
||||
.collect(Collectors.toList())
|
||||
));
|
||||
}
|
||||
|
||||
private FilterDefender(final FilterDefender filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterDefender copy() {
|
||||
return new FilterDefender(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
|
||||
package mage.filter.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.filter.FilterImpl;
|
||||
import mage.filter.FilterPlayer;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.filter.predicate.other.PlayerIdPredicate;
|
||||
import mage.filter.predicate.permanent.ControllerIdPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class FilterPlaneswalkerOrPlayer extends FilterImpl<Object> {
|
||||
|
||||
protected final FilterPlaneswalkerPermanent planeswalkerFilter;
|
||||
protected final FilterPlayer playerFilter;
|
||||
|
||||
public FilterPlaneswalkerOrPlayer(Set<UUID> defenders) {
|
||||
super("planeswalker or player");
|
||||
|
||||
List<Predicate<Permanent>> permanentPredicates = new ArrayList<>();
|
||||
for (UUID defenderId : defenders) {
|
||||
permanentPredicates.add(new ControllerIdPredicate(defenderId));
|
||||
}
|
||||
planeswalkerFilter = new FilterPlaneswalkerPermanent();
|
||||
planeswalkerFilter.add(Predicates.or(permanentPredicates));
|
||||
|
||||
List<Predicate<Player>> playerPredicates = new ArrayList<>();
|
||||
for (UUID defenderId : defenders) {
|
||||
playerPredicates.add(new PlayerIdPredicate(defenderId));
|
||||
}
|
||||
playerFilter = new FilterPlayer();
|
||||
playerFilter.add(Predicates.or(playerPredicates));
|
||||
}
|
||||
|
||||
public FilterPlaneswalkerOrPlayer(final FilterPlaneswalkerOrPlayer filter) {
|
||||
super(filter);
|
||||
this.planeswalkerFilter = filter.planeswalkerFilter.copy();
|
||||
this.playerFilter = filter.playerFilter.copy();
|
||||
}
|
||||
|
||||
public FilterPlaneswalkerPermanent getFilterPermanent() {
|
||||
return this.planeswalkerFilter;
|
||||
}
|
||||
|
||||
public FilterPlayer getFilterPlayer() {
|
||||
return this.playerFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkObjectClass(Object object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(Object o, Game game) {
|
||||
if (o instanceof Player) {
|
||||
return playerFilter.match((Player) o, game);
|
||||
} else if (o instanceof Permanent) {
|
||||
return planeswalkerFilter.match((Permanent) o, game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterPlaneswalkerOrPlayer copy() {
|
||||
return new FilterPlaneswalkerOrPlayer(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ public enum ProtectedByOpponentPredicate implements ObjectSourcePlayerPredicate<
|
|||
|
||||
@Override
|
||||
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
|
||||
// TODO: Implement this
|
||||
return false;
|
||||
return game.getOpponents(input.getPlayerId()).contains(input.getObject().getProtectorId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ public class ProtectorIdPredicate implements Predicate<Permanent> {
|
|||
|
||||
@Override
|
||||
public boolean apply(Permanent input, Game game) {
|
||||
// TODO: implement this on battles branch
|
||||
return false;
|
||||
return input.isProtectedBy(protectorId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import mage.game.permanent.Permanent;
|
|||
import mage.game.permanent.PermanentCard;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.SpellStack;
|
||||
import mage.game.stack.StackAbility;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.game.turn.Phase;
|
||||
import mage.game.turn.Step;
|
||||
|
|
@ -2498,6 +2499,32 @@ public abstract class GameImpl implements Game {
|
|||
somethingHappened = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (perm.isBattle(this)) {
|
||||
if (perm
|
||||
.getCounters(this)
|
||||
.getCount(CounterType.DEFENSE) == 0
|
||||
&& this.getStack()
|
||||
.stream()
|
||||
.filter(StackAbility.class::isInstance)
|
||||
.filter(stackObject -> stackObject.getStackAbility() instanceof TriggeredAbilityImpl)
|
||||
.map(StackObject::getSourceId)
|
||||
.noneMatch(perm.getId()::equals)
|
||||
&& this.state
|
||||
.getTriggered(perm.getControllerId())
|
||||
.stream()
|
||||
.filter(TriggeredAbility.class::isInstance)
|
||||
.map(Ability::getSourceId)
|
||||
.noneMatch(perm.getId()::equals)) {
|
||||
if (movePermanentToGraveyardWithInfo(perm)) {
|
||||
somethingHappened = true;
|
||||
}
|
||||
} else if (this.getPlayer(perm.getProtectorId()) == null || perm.isControlledBy(perm.getProtectorId())) {
|
||||
perm.chooseProtector(this, null);
|
||||
somethingHappened = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (perm.isLegendary() && perm.legendRuleApplies()) {
|
||||
legendary.add(perm);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import mage.abilities.keyword.VigilanceAbility;
|
|||
import mage.abilities.keyword.special.JohanVigilanceAbility;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterBattlePermanent;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.filter.common.FilterCreatureForCombatBlock;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
|
|
@ -21,6 +23,7 @@ import mage.filter.predicate.mageobject.AbilityPredicate;
|
|||
import mage.filter.predicate.mageobject.NamePredicate;
|
||||
import mage.filter.predicate.permanent.AttackingSameNotBandedPredicate;
|
||||
import mage.filter.predicate.permanent.PermanentIdPredicate;
|
||||
import mage.filter.predicate.permanent.ProtectedByOpponentPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.*;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
|
@ -45,6 +48,12 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
private static final Logger logger = Logger.getLogger(Combat.class);
|
||||
|
||||
private static FilterCreatureForCombatBlock filterBlockers = new FilterCreatureForCombatBlock();
|
||||
private static final FilterPermanent filterBattles = new FilterBattlePermanent();
|
||||
|
||||
static {
|
||||
filterBattles.add(ProtectedByOpponentPredicate.instance);
|
||||
}
|
||||
|
||||
// There are effects that let creatures assigns combat damage equal to its toughness rather than its power
|
||||
private boolean useToughnessForDamage;
|
||||
private final List<FilterCreaturePermanent> useToughnessForDamageFilters = new ArrayList<>();
|
||||
|
|
@ -213,10 +222,12 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
if (playerToAttack != null) {
|
||||
possibleDefenders = new HashSet<>();
|
||||
for (UUID objectId : defenders) {
|
||||
Permanent planeswalker = game.getPermanent(objectId);
|
||||
if (planeswalker != null && planeswalker.isControlledBy(playerToAttack)) {
|
||||
if (playerToAttack.equals(objectId)) {
|
||||
possibleDefenders.add(objectId);
|
||||
} else if (playerToAttack.equals(objectId)) {
|
||||
continue;
|
||||
}
|
||||
Permanent permanent = game.getPermanent(objectId);
|
||||
if (permanent != null && permanent.canBeAttacked(creatureId, playerToAttack, game)) {
|
||||
possibleDefenders.add(objectId);
|
||||
}
|
||||
}
|
||||
|
|
@ -231,8 +242,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
addAttackerToCombat(creatureId, possibleDefenders.iterator().next(), game);
|
||||
return true;
|
||||
} else {
|
||||
TargetDefender target = new TargetDefender(possibleDefenders, creatureId);
|
||||
target.setNotTarget(true);
|
||||
TargetDefender target = new TargetDefender(possibleDefenders);
|
||||
target.setRequired(true);
|
||||
player.chooseTarget(Outcome.Damage, target, null, game);
|
||||
if (target.getFirstTarget() != null) {
|
||||
|
|
@ -357,8 +367,8 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, attackingPlayerId, attackingPlayerId))
|
||||
|| (!canBand && !canBandWithOther)
|
||||
|| !player.chooseUse(Outcome.Benefit,
|
||||
(isBanded ? "Band " + attacker.getLogName()
|
||||
+ " with another " : "Form a band with " + attacker.getLogName() + " and an ")
|
||||
(isBanded ? "Band " + attacker.getLogName()
|
||||
+ " with another " : "Form a band with " + attacker.getLogName() + " and an ")
|
||||
+ "attacking creature?", null, game)) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -511,9 +521,9 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
player.declareAttacker(creature.getId(), defendersToChooseFrom.iterator().next(), game, false);
|
||||
continue;
|
||||
}
|
||||
TargetDefender target = new TargetDefender(defendersToChooseFrom, creature.getId());
|
||||
TargetDefender target = new TargetDefender(defendersToChooseFrom);
|
||||
target.setRequired(true);
|
||||
target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack (must attack effect)");
|
||||
target.setTargetName("permanent or player for " + creature.getLogName() + " to attack (must attack effect)");
|
||||
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
|
||||
player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
|
||||
}
|
||||
|
|
@ -595,7 +605,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
* Handle the blocker selection process
|
||||
*
|
||||
* @param blockController player that controls how to block, if null the
|
||||
* defender is the controller
|
||||
* defender is the controller
|
||||
* @param game
|
||||
*/
|
||||
public void selectBlockers(Player blockController, Ability source, Game game) {
|
||||
|
|
@ -605,34 +615,35 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
Player controller;
|
||||
for (UUID defenderId : getPlayerDefenders(game)) {
|
||||
Player defender = game.getPlayer(defenderId);
|
||||
if (defender != null) {
|
||||
boolean choose = true;
|
||||
if (blockController == null) {
|
||||
controller = defender;
|
||||
} else {
|
||||
controller = blockController;
|
||||
if (defender == null) {
|
||||
continue;
|
||||
}
|
||||
boolean choose = true;
|
||||
if (blockController == null) {
|
||||
controller = defender;
|
||||
} else {
|
||||
controller = blockController;
|
||||
}
|
||||
while (choose) {
|
||||
controller.selectBlockers(source, game, defenderId);
|
||||
if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) {
|
||||
return;
|
||||
}
|
||||
while (choose) {
|
||||
controller.selectBlockers(source, game, defenderId);
|
||||
if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) {
|
||||
return;
|
||||
}
|
||||
if (!game.getCombat().checkBlockRestrictions(defender, game)) {
|
||||
if (controller.isHuman()) { // only human player can decide to do the block in another way
|
||||
continue;
|
||||
}
|
||||
}
|
||||
choose = !game.getCombat().checkBlockRequirementsAfter(defender, controller, game);
|
||||
if (!choose) {
|
||||
choose = !game.getCombat().checkBlockRestrictionsAfter(defender, controller, game);
|
||||
if (!game.getCombat().checkBlockRestrictions(defender, game)) {
|
||||
if (controller.isHuman()) { // only human player can decide to do the block in another way
|
||||
continue;
|
||||
}
|
||||
}
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId));
|
||||
choose = !game.getCombat().checkBlockRequirementsAfter(defender, controller, game);
|
||||
if (!choose) {
|
||||
choose = !game.getCombat().checkBlockRestrictionsAfter(defender, controller, game);
|
||||
}
|
||||
}
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, defenderId, defenderId));
|
||||
|
||||
// add info about attacker blocked by blocker to the game log
|
||||
if (!game.isSimulation()) {
|
||||
game.getCombat().logBlockerInfo(defender, game);
|
||||
}
|
||||
// add info about attacker blocked by blocker to the game log
|
||||
if (!game.isSimulation()) {
|
||||
game.getCombat().logBlockerInfo(defender, game);
|
||||
}
|
||||
}
|
||||
// tool to catch the bug about flyers blocked by non flyers or intimidate blocked by creatures with other colors
|
||||
|
|
@ -1259,6 +1270,9 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
for (UUID playerId : getAttackablePlayers(game)) {
|
||||
addDefender(playerId, game);
|
||||
}
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filterBattles, attackingPlayerId, game)) {
|
||||
defenders.add(permanent.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public List<UUID> getAttackablePlayers(Game game) {
|
||||
|
|
@ -1351,7 +1365,17 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
if (attacker == null) {
|
||||
return false;
|
||||
}
|
||||
CombatGroup newGroup = new CombatGroup(defenderId, defender != null, defender != null ? defender.getControllerId() : defenderId);
|
||||
UUID defendingPlayerId;
|
||||
if (defender == null) {
|
||||
defendingPlayerId = defenderId;
|
||||
} else if (defender.isPlaneswalker(game)) {
|
||||
defendingPlayerId = defender.getControllerId();
|
||||
} else if (defender.isBattle(game)) {
|
||||
defendingPlayerId = defender.getProtectorId();
|
||||
} else {
|
||||
defendingPlayerId = null;
|
||||
}
|
||||
CombatGroup newGroup = new CombatGroup(defenderId, defender != null, defendingPlayerId);
|
||||
newGroup.attackers.add(attackerId);
|
||||
attacker.setAttacking(true);
|
||||
groups.add(newGroup);
|
||||
|
|
@ -1413,7 +1437,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
* @param playerId
|
||||
* @param game
|
||||
* @param solveBanding check whether also add creatures banded with
|
||||
* attackerId
|
||||
* attackerId
|
||||
*/
|
||||
public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game, boolean solveBanding) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
|
|
@ -1453,11 +1477,11 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean removePlaneswalkerFromCombat(UUID planeswalkerId, Game game) {
|
||||
public boolean removeDefendingPermanentFromCombat(UUID permanentId, Game game) {
|
||||
boolean result = false;
|
||||
for (CombatGroup group : groups) {
|
||||
if (group.getDefenderId() != null && group.getDefenderId().equals(planeswalkerId)) {
|
||||
group.removeAttackedPlaneswalker(planeswalkerId);
|
||||
if (group.getDefenderId() != null && group.getDefenderId().equals(permanentId)) {
|
||||
group.removeAttackedPermanent(permanentId);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1465,31 +1489,32 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
}
|
||||
|
||||
public boolean removeFromCombat(UUID creatureId, Game game, boolean withEvent) {
|
||||
boolean result = false;
|
||||
Permanent creature = game.getPermanent(creatureId);
|
||||
if (creature != null) {
|
||||
if (withEvent) {
|
||||
creature.setAttacking(false);
|
||||
creature.setBlocking(0);
|
||||
}
|
||||
for (CombatGroup group : groups) {
|
||||
for (UUID attackerId : group.attackers) {
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null) {
|
||||
attacker.removeBandedCard(creatureId);
|
||||
}
|
||||
if (creature == null) {
|
||||
return false;
|
||||
}
|
||||
boolean result = false;
|
||||
if (withEvent) {
|
||||
creature.setAttacking(false);
|
||||
creature.setBlocking(0);
|
||||
}
|
||||
for (CombatGroup group : groups) {
|
||||
for (UUID attackerId : group.attackers) {
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null) {
|
||||
attacker.removeBandedCard(creatureId);
|
||||
}
|
||||
result |= group.remove(creatureId);
|
||||
}
|
||||
for (CombatGroup blockingGroup : getBlockingGroups()) {
|
||||
result |= blockingGroup.remove(creatureId);
|
||||
}
|
||||
creature.clearBandedCards();
|
||||
blockingGroups.remove(creatureId);
|
||||
if (result && withEvent) {
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.REMOVED_FROM_COMBAT, creatureId, null, null));
|
||||
game.informPlayers(creature.getLogName() + " removed from combat");
|
||||
}
|
||||
result |= group.remove(creatureId);
|
||||
}
|
||||
for (CombatGroup blockingGroup : getBlockingGroups()) {
|
||||
result |= blockingGroup.remove(creatureId);
|
||||
}
|
||||
creature.clearBandedCards();
|
||||
blockingGroups.remove(creatureId);
|
||||
if (result && withEvent) {
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.REMOVED_FROM_COMBAT, creatureId, null, null));
|
||||
game.informPlayers(creature.getLogName() + " removed from combat");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1540,15 +1565,6 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
return null;
|
||||
}
|
||||
|
||||
// public int totalUnblockedDamage(Game game) {
|
||||
// int total = 0;
|
||||
// for (CombatGroup group : groups) {
|
||||
// if (group.getBlockers().isEmpty()) {
|
||||
// total += group.totalAttackerDamage(game);
|
||||
// }
|
||||
// }
|
||||
// return total;
|
||||
// }
|
||||
public boolean attacksAlone() {
|
||||
return (groups.size() == 1 && groups.get(0).getAttackers().size() == 1);
|
||||
}
|
||||
|
|
@ -1557,24 +1573,9 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
return groups.isEmpty() || getAttackers().isEmpty();
|
||||
}
|
||||
|
||||
public boolean isAttacked(UUID defenderId, Game game) {
|
||||
for (CombatGroup group : groups) {
|
||||
if (group.getDefenderId().equals(defenderId)) {
|
||||
return true;
|
||||
}
|
||||
if (group.defenderIsPlaneswalker) {
|
||||
Permanent permanent = game.getPermanent(group.getDefenderId());
|
||||
if (permanent.isControlledBy(defenderId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isPlaneswalkerAttacked(UUID defenderId, Game game) {
|
||||
for (CombatGroup group : groups) {
|
||||
if (group.defenderIsPlaneswalker) {
|
||||
if (group.isDefenderIsPermanent()) {
|
||||
Permanent permanent = game.getPermanent(group.getDefenderId());
|
||||
if (permanent.isControlledBy(defenderId)) {
|
||||
return true;
|
||||
|
|
@ -1589,14 +1590,12 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
* @return uuid of defending player or planeswalker
|
||||
*/
|
||||
public UUID getDefenderId(UUID attackerId) {
|
||||
UUID defenderId = null;
|
||||
for (CombatGroup group : groups) {
|
||||
if (group.getAttackers().contains(attackerId)) {
|
||||
defenderId = group.getDefenderId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return defenderId;
|
||||
return groups
|
||||
.stream()
|
||||
.filter(group -> group.getAttackers().contains(attackerId))
|
||||
.map(CombatGroup::getDefenderId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1608,40 +1607,32 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
* @return
|
||||
*/
|
||||
public UUID getDefendingPlayerId(UUID attackingCreatureId, Game game) {
|
||||
UUID defenderId = null;
|
||||
for (CombatGroup group : groups) {
|
||||
if (group.getAttackers().contains(attackingCreatureId)) {
|
||||
defenderId = group.getDefenderId();
|
||||
if (group.defenderIsPlaneswalker) {
|
||||
Permanent permanent = game.getPermanentOrLKIBattlefield(defenderId);
|
||||
if (permanent != null) {
|
||||
defenderId = permanent.getControllerId();
|
||||
} else {
|
||||
defenderId = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return defenderId;
|
||||
return groups
|
||||
.stream()
|
||||
.filter(group -> group.getAttackers().contains(attackingCreatureId))
|
||||
.map(CombatGroup::getDefendingPlayerId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public Set<UUID> getPlayerDefenders(Game game) {
|
||||
return getPlayerDefenders(game, true);
|
||||
}
|
||||
|
||||
public Set<UUID> getPlayerDefenders(Game game, boolean includePlaneswalkers) {
|
||||
public Set<UUID> getPlayerDefenders(Game game, boolean includePermanents) {
|
||||
Set<UUID> playerDefenders = new HashSet<>();
|
||||
for (CombatGroup group : groups) {
|
||||
if (group.defenderIsPlaneswalker && !includePlaneswalkers) {
|
||||
if (group.isDefenderIsPermanent() && !includePermanents) {
|
||||
continue;
|
||||
}
|
||||
if (group.defenderIsPlaneswalker) {
|
||||
if (group.isDefenderIsPermanent()) {
|
||||
Permanent permanent = game.getPermanent(group.getDefenderId());
|
||||
if (permanent != null) {
|
||||
playerDefenders.add(permanent.getControllerId());
|
||||
} else {
|
||||
if (permanent == null) {
|
||||
playerDefenders.add(group.getDefendingPlayerId());
|
||||
} else if (permanent.isPlaneswalker(game)) {
|
||||
playerDefenders.add(permanent.getControllerId());
|
||||
} else if (permanent.isBattle(game)) {
|
||||
playerDefenders.add(permanent.getProtectorId());
|
||||
}
|
||||
} else {
|
||||
playerDefenders.add(group.getDefenderId());
|
||||
|
|
|
|||
|
|
@ -33,16 +33,16 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
protected boolean blocked;
|
||||
protected UUID defenderId; // planeswalker or player
|
||||
protected UUID defendingPlayerId;
|
||||
protected boolean defenderIsPlaneswalker;
|
||||
protected boolean defenderIsPermanent;
|
||||
|
||||
/**
|
||||
* @param defenderId the player that controls the defending permanents
|
||||
* @param defenderIsPlaneswalker is the defending permanent a planeswalker
|
||||
* @param defendingPlayerId regular controller of the defending permanents
|
||||
* @param defenderId the player that controls the defending permanents
|
||||
* @param defenderIsPermanent is the defender a permanent
|
||||
* @param defendingPlayerId regular controller of the defending permanents
|
||||
*/
|
||||
public CombatGroup(UUID defenderId, boolean defenderIsPlaneswalker, UUID defendingPlayerId) {
|
||||
public CombatGroup(UUID defenderId, boolean defenderIsPermanent, UUID defendingPlayerId) {
|
||||
this.defenderId = defenderId;
|
||||
this.defenderIsPlaneswalker = defenderIsPlaneswalker;
|
||||
this.defenderIsPermanent = defenderIsPermanent;
|
||||
this.defendingPlayerId = defendingPlayerId;
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
this.blocked = group.blocked;
|
||||
this.defenderId = group.defenderId;
|
||||
this.defendingPlayerId = group.defendingPlayerId;
|
||||
this.defenderIsPlaneswalker = group.defenderIsPlaneswalker;
|
||||
this.defenderIsPermanent = group.defenderIsPermanent;
|
||||
}
|
||||
|
||||
public boolean hasFirstOrDoubleStrike(Game game) {
|
||||
|
|
@ -217,7 +217,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
permanent.applyDamage(game);
|
||||
}
|
||||
}
|
||||
if (defenderIsPlaneswalker) {
|
||||
if (defenderIsPermanent) {
|
||||
Permanent permanent = game.getPermanent(defenderId);
|
||||
if (permanent != null) {
|
||||
permanent.applyDamage(game);
|
||||
|
|
@ -545,46 +545,44 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do damage to attacked player or planeswalker
|
||||
*
|
||||
* @param attacker
|
||||
* @param amount
|
||||
* @param game
|
||||
* @param damageToDefenderController excess damage to defender's controller (example: trample over planeswalker)
|
||||
*/
|
||||
private void defenderDamage(Permanent attacker, int amount, Game game, boolean damageToDefenderController) {
|
||||
UUID affectedDefenderId = this.defenderId;
|
||||
if (damageToDefenderController) {
|
||||
affectedDefenderId = game.getControllerId(this.defenderId);
|
||||
}
|
||||
UUID affectedDefenderId = damageToDefenderController ? game.getControllerId(this.defenderId) : this.defenderId;
|
||||
|
||||
// on planeswalker
|
||||
Permanent planeswalker = game.getPermanent(affectedDefenderId);
|
||||
if (planeswalker != null) {
|
||||
// apply excess damage from "trample over planeswaslkers" ability (example: Thrasta, Tempest's Roar)
|
||||
if (hasTrampleOverPlaneswalkers(attacker)) {
|
||||
int lethalDamage = planeswalker.getLethalDamage(attacker.getId(), game);
|
||||
if (lethalDamage >= amount) {
|
||||
// normal damage
|
||||
planeswalker.markDamage(amount, attacker.getId(), null, game, true, true);
|
||||
} else {
|
||||
// damage with excess (additional damage to planeswalker's controller)
|
||||
planeswalker.markDamage(lethalDamage, attacker.getId(), null, game, true, true);
|
||||
amount -= lethalDamage;
|
||||
if (amount > 0) {
|
||||
defenderDamage(attacker, amount, game, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// normal damage
|
||||
planeswalker.markDamage(amount, attacker.getId(), null, game, true, true);
|
||||
Permanent permanent = game.getPermanent(affectedDefenderId);
|
||||
if (permanent == null) {// on player
|
||||
Player defender = game.getPlayer(affectedDefenderId);
|
||||
if (defender != null) {
|
||||
defender.damage(amount, attacker.getId(), null, game, true, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// on player
|
||||
Player defender = game.getPlayer(affectedDefenderId);
|
||||
if (defender != null) {
|
||||
defender.damage(amount, attacker.getId(), null, game, true, true);
|
||||
// apply excess damage from "trample over planeswaslkers" ability (example: Thrasta, Tempest's Roar)
|
||||
if (permanent.isPlaneswalker(game) && hasTrampleOverPlaneswalkers(attacker)) {
|
||||
int lethalDamage = permanent.getLethalDamage(attacker.getId(), game);
|
||||
if (lethalDamage >= amount) {
|
||||
// normal damage
|
||||
permanent.markDamage(amount, attacker.getId(), null, game, true, true);
|
||||
} else {
|
||||
// damage with excess (additional damage to permanent's controller)
|
||||
permanent.markDamage(lethalDamage, attacker.getId(), null, game, true, true);
|
||||
amount -= lethalDamage;
|
||||
if (amount > 0) {
|
||||
defenderDamage(attacker, amount, game, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// normal damage
|
||||
permanent.markDamage(amount, attacker.getId(), null, game, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -717,20 +715,12 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
game.informPlayers(sb.toString());
|
||||
}
|
||||
|
||||
public int totalAttackerDamage(Game game) {
|
||||
int total = 0;
|
||||
for (UUID attackerId : attackers) {
|
||||
total += getDamageValueFromPermanent(game.getPermanent(attackerId), game);
|
||||
}
|
||||
return total;
|
||||
public boolean isDefenderIsPermanent() {
|
||||
return defenderIsPermanent;
|
||||
}
|
||||
|
||||
public boolean isDefenderIsPlaneswalker() {
|
||||
return defenderIsPlaneswalker;
|
||||
}
|
||||
|
||||
public boolean removeAttackedPlaneswalker(UUID planeswalkerId) {
|
||||
if (defenderIsPlaneswalker && defenderId.equals(planeswalkerId)) {
|
||||
public boolean removeAttackedPermanent(UUID permanentId) {
|
||||
if (defenderIsPermanent && defenderId.equals(permanentId)) {
|
||||
defenderId = null;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -874,36 +864,36 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
|
||||
public boolean changeDefenderPostDeclaration(UUID newDefenderId, Game game) {
|
||||
if (!defenderId.equals(newDefenderId)) {
|
||||
for (UUID attackerId : attackers) { // changing defender will remove a banded attacker from its current band
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null && attacker.getBandedCards() != null) {
|
||||
for (UUID bandedId : attacker.getBandedCards()) {
|
||||
Permanent banded = game.getPermanent(bandedId);
|
||||
if (banded != null) {
|
||||
banded.removeBandedCard(attackerId);
|
||||
}
|
||||
if (defenderId.equals(newDefenderId)) {
|
||||
return false;
|
||||
}
|
||||
for (UUID attackerId : attackers) { // changing defender will remove a banded attacker from its current band
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null && attacker.getBandedCards() != null) {
|
||||
for (UUID bandedId : attacker.getBandedCards()) {
|
||||
Permanent banded = game.getPermanent(bandedId);
|
||||
if (banded != null) {
|
||||
banded.removeBandedCard(attackerId);
|
||||
}
|
||||
}
|
||||
attacker.clearBandedCards();
|
||||
}
|
||||
Permanent permanent = game.getPermanent(newDefenderId);
|
||||
if (permanent != null) {
|
||||
defenderId = newDefenderId;
|
||||
defendingPlayerId = permanent.getControllerId();
|
||||
defenderIsPlaneswalker = true;
|
||||
return true;
|
||||
} else {
|
||||
Player defender = game.getPlayer(newDefenderId);
|
||||
if (defender != null) {
|
||||
defenderId = newDefenderId;
|
||||
defendingPlayerId = newDefenderId;
|
||||
defenderIsPlaneswalker = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
attacker.clearBandedCards();
|
||||
}
|
||||
return false;
|
||||
Permanent permanent = game.getPermanent(newDefenderId);
|
||||
if (permanent != null) {
|
||||
defenderId = newDefenderId;
|
||||
defendingPlayerId = permanent.isBattle(game) ? permanent.getProtectorId() : permanent.getControllerId();
|
||||
defenderIsPermanent = true;
|
||||
return true;
|
||||
}
|
||||
Player defender = game.getPlayer(newDefenderId);
|
||||
if (defender == null) {
|
||||
return false;
|
||||
}
|
||||
defenderId = newDefenderId;
|
||||
defendingPlayerId = newDefenderId;
|
||||
defenderIsPermanent = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@ import mage.game.events.ZoneChangeEvent;
|
|||
import mage.util.GameLog;
|
||||
import mage.util.SubTypes;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Commander implements CommandObject {
|
||||
|
||||
|
|
@ -272,6 +275,15 @@ public class Commander implements CommandObject {
|
|||
public void setStartingLoyalty(int startingLoyalty) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartingDefense() {
|
||||
return sourceObject.getStartingDefense();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartingDefense(int startingDefense) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
return sourceObject.getId();
|
||||
|
|
|
|||
|
|
@ -317,6 +317,15 @@ public class Dungeon implements CommandObject {
|
|||
public void setStartingLoyalty(int startingLoyalty) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartingDefense() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartingDefense(int startingDefense) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
return this.id;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public class Emblem implements CommandObject {
|
|||
}
|
||||
if (!availableImageSetCodes.isEmpty()) {
|
||||
if (expansionSetCodeForImage.equals("") || !availableImageSetCodes.contains(expansionSetCodeForImage)) {
|
||||
expansionSetCodeForImage = availableImageSetCodes.get(RandomUtil.nextInt(availableImageSetCodes.size()));
|
||||
expansionSetCodeForImage = availableImageSetCodes.get(RandomUtil.nextInt(availableImageSetCodes.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -233,6 +233,15 @@ public class Emblem implements CommandObject {
|
|||
public void setStartingLoyalty(int startingLoyalty) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartingDefense() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartingDefense(int startingDefense) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
return this.id;
|
||||
|
|
|
|||
|
|
@ -232,6 +232,15 @@ public class Plane implements CommandObject {
|
|||
public void setStartingLoyalty(int startingLoyalty) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartingDefense() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartingDefense(int startingDefense) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
return this.id;
|
||||
|
|
|
|||
|
|
@ -89,6 +89,14 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
Set<UUID> getGoadingPlayers();
|
||||
|
||||
void chooseProtector(Game game, Ability source);
|
||||
|
||||
void setProtectorId(UUID playerId);
|
||||
|
||||
UUID getProtectorId();
|
||||
|
||||
boolean isProtectedBy(UUID playerId);
|
||||
|
||||
void setCardNumber(String cid);
|
||||
|
||||
void setExpansionSetCode(String expansionSetCode);
|
||||
|
|
@ -292,6 +300,8 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
boolean canBlockAny(Game game);
|
||||
|
||||
boolean canBeAttacked(UUID attackerId, UUID playerToAttack, Game game);
|
||||
|
||||
/**
|
||||
* Checks by restriction effects if the permanent can use activated
|
||||
* abilities
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ public class PermanentCard extends PermanentImpl {
|
|||
power = card.getPower().copy();
|
||||
toughness = card.getToughness().copy();
|
||||
startingLoyalty = card.getStartingLoyalty();
|
||||
startingDefense = card.getStartingDefense();
|
||||
copyFromCard(card, game);
|
||||
// if temporary added abilities to the spell/card exist, you need to add it to the permanent derived from that card
|
||||
Abilities<Ability> otherAbilities = game.getState().getAllOtherAbilities(card.getId());
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import mage.constants.*;
|
|||
import mage.counters.Counter;
|
||||
import mage.counters.CounterType;
|
||||
import mage.counters.Counters;
|
||||
import mage.filter.FilterOpponent;
|
||||
import mage.game.Game;
|
||||
import mage.game.GameState;
|
||||
import mage.game.ZoneChangeInfo;
|
||||
|
|
@ -33,9 +34,10 @@ import mage.game.permanent.token.SquirrelToken;
|
|||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetCard;
|
||||
import mage.target.TargetPlayer;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.GameLog;
|
||||
import mage.util.RandomUtil;
|
||||
import mage.util.ThreadLocalStringBuilder;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
|
|
@ -75,6 +77,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
protected final Set<UUID> goadingPlayers = new HashSet<>();
|
||||
protected UUID originalControllerId;
|
||||
protected UUID controllerId;
|
||||
protected UUID protectorId = null;
|
||||
protected UUID beforeResetControllerId;
|
||||
protected int damage;
|
||||
protected boolean controlledFromStartOfControllerTurn;
|
||||
|
|
@ -174,6 +177,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.loyaltyActivationsAvailable = permanent.loyaltyActivationsAvailable;
|
||||
this.legendRuleApplies = permanent.legendRuleApplies;
|
||||
this.transformCount = permanent.transformCount;
|
||||
this.protectorId = permanent.protectorId;
|
||||
|
||||
this.morphed = permanent.morphed;
|
||||
this.manifested = permanent.manifested;
|
||||
|
|
@ -482,7 +486,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
|
||||
@Override
|
||||
public void setLoyaltyActivationsAvailable(int setActivations) {
|
||||
if(this.loyaltyActivationsAvailable < setActivations) {
|
||||
if (this.loyaltyActivationsAvailable < setActivations) {
|
||||
this.loyaltyActivationsAvailable = setActivations;
|
||||
}
|
||||
}
|
||||
|
|
@ -991,6 +995,15 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
removeCounters(CounterType.LOYALTY.getName(), countersToRemove, source, game);
|
||||
}
|
||||
}
|
||||
if (this.isBattle(game)) {
|
||||
int defense = getCounters(game).getCount(CounterType.DEFENSE);
|
||||
int countersToRemove = Math.min(actualDamage, defense);
|
||||
if (attacker != null && markDamage) {
|
||||
markDamage(CounterType.DEFENSE.createInstance(countersToRemove), attacker, false);
|
||||
} else {
|
||||
removeCounters(CounterType.DEFENSE.getName(), countersToRemove, source, game);
|
||||
}
|
||||
}
|
||||
DamagedEvent damagedEvent = new DamagedPermanentEvent(this.getId(), attackerId, this.getControllerId(), actualDamage, combat);
|
||||
damagedEvent.setExcess(actualDamage - lethal);
|
||||
game.fireEvent(damagedEvent);
|
||||
|
|
@ -1134,6 +1147,9 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
if (this.isPlaneswalker(game)) {
|
||||
lethal = Math.min(lethal, this.getCounters(game).getCount(CounterType.LOYALTY));
|
||||
}
|
||||
if (this.isBattle(game)) {
|
||||
lethal = Math.min(lethal, this.getCounters(game).getCount(CounterType.DEFENSE));
|
||||
}
|
||||
return lethal;
|
||||
}
|
||||
|
||||
|
|
@ -1196,6 +1212,18 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.addCounters(CounterType.LOYALTY.createInstance(countersToAdd), source, game);
|
||||
}
|
||||
}
|
||||
if (this.isBattle(game)) {
|
||||
int defense;
|
||||
if (this.getStartingDefense() == -2) {
|
||||
defense = source.getManaCostsToPay().getX();
|
||||
} else {
|
||||
defense = this.getStartingDefense();
|
||||
}
|
||||
if (defense > 0) {
|
||||
this.addCounters(CounterType.DEFENSE.createInstance(defense), source, game);
|
||||
}
|
||||
this.chooseProtector(game, source);
|
||||
}
|
||||
if (!fireEvent) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1381,8 +1409,23 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
return canAttackInPrinciple(defenderId, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeAttacked(UUID attackerId, UUID playerToAttack, Game game) {
|
||||
if (isPlaneswalker(game)) {
|
||||
return isControlledBy(playerToAttack);
|
||||
}
|
||||
if (isBattle(game)) {
|
||||
return isProtectedBy(playerToAttack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttackInPrinciple(UUID defenderId, Game game) {
|
||||
if (isBattle(game)) {
|
||||
// battles can never attack
|
||||
return false;
|
||||
}
|
||||
ApprovingObject approvingObject = game.getContinuousEffects().asThough(
|
||||
this.objectId, AsThoughEffectType.ATTACK_AS_HASTE, null, defenderId, game
|
||||
);
|
||||
|
|
@ -1422,7 +1465,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
|
||||
@Override
|
||||
public boolean canBlock(UUID attackerId, Game game) {
|
||||
if (tapped && null == game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game)) {
|
||||
if (tapped && game.getState().getContinuousEffects().asThough(this.getId(), AsThoughEffectType.BLOCK_TAPPED, null, this.getControllerId(), game) == null || isBattle(game)) {
|
||||
return false;
|
||||
}
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
|
|
@ -1528,7 +1571,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
return game.getCombat().removeFromCombat(objectId, game, withEvent);
|
||||
} else if (this.isPlaneswalker(game)) {
|
||||
if (game.getCombat().getDefenders().contains(getId())) {
|
||||
game.getCombat().removePlaneswalkerFromCombat(objectId, game);
|
||||
game.getCombat().removeDefendingPermanentFromCombat(objectId, game);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
@ -1629,6 +1672,40 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
return goadingPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void chooseProtector(Game game, Ability source) {
|
||||
Set<UUID> opponents = game.getOpponents(this.getControllerId());
|
||||
UUID protectorId;
|
||||
if (opponents.size() > 1) {
|
||||
TargetPlayer target = new TargetPlayer(new FilterOpponent("protector for " + getName()));
|
||||
target.setNotTarget(true);
|
||||
target.setRequired(true);
|
||||
game.getPlayer(getControllerId()).choose(Outcome.Neutral, target, source, game);
|
||||
protectorId = target.getFirstTarget();
|
||||
} else {
|
||||
protectorId = RandomUtil.randomFromCollection(opponents);
|
||||
}
|
||||
String protectorName = game.getPlayer(protectorId).getLogName();
|
||||
game.informPlayers(protectorName + " has been chosen to protect " + this.getLogName());
|
||||
this.addInfo("protector", "Protected by " + protectorName, game);
|
||||
this.setProtectorId(protectorId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtectorId(UUID protectorId) {
|
||||
this.protectorId = protectorId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getProtectorId() {
|
||||
return protectorId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProtectedBy(UUID playerId) {
|
||||
return protectorId != null && protectorId.equals(playerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPairedCard(MageObjectReference pairedCard) {
|
||||
this.pairedPermanent = pairedCard;
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ public class PermanentToken extends PermanentImpl {
|
|||
this.supertype.addAll(token.getSuperType());
|
||||
this.subtype.copyFrom(token.getSubtype(game));
|
||||
this.startingLoyalty = token.getStartingLoyalty();
|
||||
this.startingDefense = token.getStartingDefense();
|
||||
// workaround for entersTheBattlefield replacement effects
|
||||
if (this.abilities.containsClass(ChangelingAbility.class)) {
|
||||
this.subtype.setIsAllCreatureTypes(true);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ public class Spell extends StackObjectImpl implements Card {
|
|||
private boolean resolving = false;
|
||||
private UUID commandedBy = null; // for Word of Command
|
||||
private int startingLoyalty;
|
||||
private int startingDefense;
|
||||
|
||||
private ActivationManaAbilityStep currentActivatingManaAbilitiesStep = ActivationManaAbilityStep.BEFORE;
|
||||
|
||||
|
|
@ -73,7 +74,7 @@ public class Spell extends StackObjectImpl implements Card {
|
|||
Card affectedCard = card;
|
||||
|
||||
// TODO: must be removed after transform cards (one side) migrated to MDF engine (multiple sides)
|
||||
if (ability.getSpellAbilityCastMode() == SpellAbilityCastMode.DISTURB && affectedCard.getSecondCardFace() != null) {
|
||||
if (ability.getSpellAbilityCastMode() == SpellAbilityCastMode.TRANSFORMED && affectedCard.getSecondCardFace() != null) {
|
||||
// simulate another side as new card (another code part in continues effect from disturb ability)
|
||||
affectedCard = TransformAbility.transformCardSpellStatic(card, card.getSecondCardFace(), game);
|
||||
}
|
||||
|
|
@ -83,6 +84,7 @@ public class Spell extends StackObjectImpl implements Card {
|
|||
this.frameColor = affectedCard.getFrameColor(null).copy();
|
||||
this.frameStyle = affectedCard.getFrameStyle();
|
||||
this.startingLoyalty = affectedCard.getStartingLoyalty();
|
||||
this.startingDefense = affectedCard.getStartingDefense();
|
||||
this.id = ability.getId();
|
||||
this.zoneChangeCounter = affectedCard.getZoneChangeCounter(game); // sync card's ZCC with spell (copy spell settings)
|
||||
this.ability = ability;
|
||||
|
|
@ -134,6 +136,7 @@ public class Spell extends StackObjectImpl implements Card {
|
|||
this.currentActivatingManaAbilitiesStep = spell.currentActivatingManaAbilitiesStep;
|
||||
this.targetChanged = spell.targetChanged;
|
||||
this.startingLoyalty = spell.startingLoyalty;
|
||||
this.startingDefense = spell.startingDefense;
|
||||
}
|
||||
|
||||
public boolean activate(Game game, boolean noMana) {
|
||||
|
|
@ -641,6 +644,16 @@ public class Spell extends StackObjectImpl implements Card {
|
|||
this.startingLoyalty = startingLoyalty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartingDefense() {
|
||||
return startingDefense;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartingDefense(int startingDefense) {
|
||||
this.startingDefense = startingDefense;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getId() {
|
||||
return id;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import mage.abilities.effects.Effect;
|
|||
import mage.abilities.effects.Effects;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.icon.CardIcon;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.FrameStyle;
|
||||
import mage.constants.*;
|
||||
import mage.filter.predicate.mageobject.MageObjectReferencePredicate;
|
||||
|
|
@ -243,6 +242,15 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
|||
public void setStartingLoyalty(int startingLoyalty) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartingDefense() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStartingDefense(int startingDefense) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Zone getZone() {
|
||||
return this.ability.getZone();
|
||||
|
|
|
|||
|
|
@ -1,232 +1,32 @@
|
|||
package mage.target.common;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.Filter;
|
||||
import mage.filter.common.FilterCreaturePlayerOrPlaneswalker;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetImpl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.filter.common.FilterAnyTarget;
|
||||
|
||||
/**
|
||||
* @author JRHerlehy Created on 4/8/18.
|
||||
*/
|
||||
public class TargetAnyTarget extends TargetImpl {
|
||||
public class TargetAnyTarget extends TargetPermanentOrPlayer {
|
||||
|
||||
protected FilterCreaturePlayerOrPlaneswalker filter;
|
||||
private static final FilterAnyTarget filter = new FilterAnyTarget();
|
||||
|
||||
public TargetAnyTarget() {
|
||||
this(1, 1, new FilterCreaturePlayerOrPlaneswalker());
|
||||
this(1);
|
||||
}
|
||||
|
||||
public TargetAnyTarget(int numTargets) {
|
||||
this(numTargets, numTargets, new FilterCreaturePlayerOrPlaneswalker());
|
||||
this(numTargets, numTargets);
|
||||
}
|
||||
|
||||
public TargetAnyTarget(FilterCreaturePlayerOrPlaneswalker filter) {
|
||||
this(1, 1, filter);
|
||||
public TargetAnyTarget(int minNumTargets, int maxNumTargets) {
|
||||
super(minNumTargets, maxNumTargets, filter, false);
|
||||
}
|
||||
|
||||
public TargetAnyTarget(int numTargets, int maxNumTargets) {
|
||||
this(numTargets, maxNumTargets, new FilterCreaturePlayerOrPlaneswalker());
|
||||
}
|
||||
|
||||
public TargetAnyTarget(int minNumTargets, int maxNumTargets, FilterCreaturePlayerOrPlaneswalker filter) {
|
||||
this.minNumberOfTargets = minNumTargets;
|
||||
this.maxNumberOfTargets = maxNumTargets;
|
||||
this.zone = Zone.ALL;
|
||||
this.filter = filter;
|
||||
this.targetName = filter.getMessage();
|
||||
}
|
||||
|
||||
public TargetAnyTarget(final TargetAnyTarget target) {
|
||||
protected TargetAnyTarget(final TargetAnyTarget target) {
|
||||
super(target);
|
||||
this.filter = target.filter.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Game game) {
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
if (permanent != null) {
|
||||
return filter.match(permanent, game);
|
||||
}
|
||||
Player player = game.getPlayer(id);
|
||||
return filter.match(player, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Ability source, Game game) {
|
||||
return canTarget(source.getControllerId(), id, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) {
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
Player player = game.getPlayer(id);
|
||||
|
||||
if (source != null) {
|
||||
MageObject targetSource = game.getObject(source);
|
||||
if (permanent != null) {
|
||||
return permanent.canBeTargetedBy(targetSource, source.getControllerId(), game) && filter.match(permanent, source.getControllerId(), source, game);
|
||||
}
|
||||
if (player != null) {
|
||||
return player.canBeTargetedBy(targetSource, source.getControllerId(), game) && filter.match(player, game);
|
||||
}
|
||||
}
|
||||
|
||||
if (permanent != null) {
|
||||
return filter.match(permanent, game);
|
||||
}
|
||||
return filter.match(player, game);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are enough {@link Permanent} or {@link Player} that can
|
||||
* be chosen. Should only be used for Ability targets since this checks for
|
||||
* protection, shroud etc.
|
||||
*
|
||||
* @param sourceControllerId - controller of the target event source
|
||||
* @param source
|
||||
* @param game
|
||||
* @return - true if enough valid {@link Permanent} or {@link Player} exist
|
||||
*/
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
|
||||
int count = 0;
|
||||
|
||||
MageObject targetSource = game.getObject(source);
|
||||
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null && player.canBeTargetedBy(targetSource, sourceControllerId, game) && filter.match(player, game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) {
|
||||
if (permanent.canBeTargetedBy(targetSource, sourceControllerId, game) && filter.match(permanent, sourceControllerId, source, game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are enough {@link Permanent} or {@link Player} that can
|
||||
* be selected. Should not be used for Ability targets since this does not
|
||||
* check for protection, shroud etc.
|
||||
*
|
||||
* @param sourceControllerId - controller of the select event
|
||||
* @param game
|
||||
* @return - true if enough valid {@link Permanent} or {@link Player} exist
|
||||
*/
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Game game) {
|
||||
int count = 0;
|
||||
|
||||
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (filter.match(player, game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) {
|
||||
if (filter.match(permanent, sourceControllerId, null, game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
MageObject targetSource = game.getObject(source);
|
||||
|
||||
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null
|
||||
&& player.canBeTargetedBy(targetSource, sourceControllerId, game)
|
||||
&& filter.match(player, sourceControllerId, source, game)) {
|
||||
possibleTargets.add(playerId);
|
||||
}
|
||||
}
|
||||
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) {
|
||||
if (permanent.canBeTargetedBy(targetSource, sourceControllerId, game)
|
||||
&& filter.match(permanent, sourceControllerId, source, game)) {
|
||||
possibleTargets.add(permanent.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) {
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
|
||||
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (filter.match(player, game)) {
|
||||
possibleTargets.add(playerId);
|
||||
}
|
||||
}
|
||||
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter.getPermanentFilter(), sourceControllerId, game)) {
|
||||
if (filter.getPermanentFilter().match(permanent, sourceControllerId, null, game)) {
|
||||
possibleTargets.add(permanent.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTargetedName(Game game) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (UUID targetId : getTargets()) {
|
||||
Permanent permanent = game.getPermanent(targetId);
|
||||
if (permanent != null) {
|
||||
sb.append(permanent.getLogName()).append(' ');
|
||||
} else {
|
||||
Player player = game.getPlayer(targetId);
|
||||
if (player != null) {
|
||||
sb.append(player.getLogName()).append(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetAnyTarget copy() {
|
||||
return new TargetAnyTarget(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@ package mage.target.common;
|
|||
import mage.abilities.dynamicvalue.DynamicValue;
|
||||
import mage.abilities.dynamicvalue.common.StaticValue;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterCreaturePlayerOrPlaneswalker;
|
||||
import mage.filter.common.FilterAnyTarget;
|
||||
import mage.filter.common.FilterPermanentOrPlayer;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class TargetAnyTargetAmount extends TargetPermanentOrPlayerAmount {
|
||||
|
||||
private static final FilterCreaturePlayerOrPlaneswalker defaultFilter
|
||||
= new FilterCreaturePlayerOrPlaneswalker("targets");
|
||||
private static final FilterPermanentOrPlayer defaultFilter
|
||||
= new FilterAnyTarget("targets");
|
||||
|
||||
public TargetAnyTargetAmount(int amount) {
|
||||
this(amount, 0);
|
||||
|
|
|
|||
|
|
@ -1,210 +1,25 @@
|
|||
package mage.target.common;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.Filter;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterPlaneswalkerOrPlayer;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.TargetImpl;
|
||||
import mage.filter.common.FilterDefender;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class TargetDefender extends TargetImpl {
|
||||
public class TargetDefender extends TargetPermanentOrPlayer {
|
||||
|
||||
protected final FilterPlaneswalkerOrPlayer filter;
|
||||
protected final UUID attackerId;
|
||||
|
||||
public TargetDefender(Set<UUID> defenders, UUID attackerId) {
|
||||
this(1, 1, defenders, attackerId);
|
||||
public TargetDefender(Set<UUID> defenders) {
|
||||
super(1, 1, new FilterDefender(defenders), true);
|
||||
}
|
||||
|
||||
public TargetDefender(int numTargets, Set<UUID> defenders, UUID attackerId) {
|
||||
this(numTargets, numTargets, defenders, attackerId);
|
||||
}
|
||||
|
||||
public TargetDefender(int minNumTargets, int maxNumTargets, Set<UUID> defenders, UUID attackerId) {
|
||||
this.minNumberOfTargets = minNumTargets;
|
||||
this.maxNumberOfTargets = maxNumTargets;
|
||||
this.zone = Zone.ALL;
|
||||
this.filter = new FilterPlaneswalkerOrPlayer(defenders);
|
||||
this.targetName = filter.getMessage();
|
||||
this.attackerId = attackerId;
|
||||
this.notTarget = true;
|
||||
}
|
||||
|
||||
public TargetDefender(final TargetDefender target) {
|
||||
private TargetDefender(final TargetDefender target) {
|
||||
super(target);
|
||||
this.filter = target.filter.copy();
|
||||
this.attackerId = target.attackerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
|
||||
int count = 0;
|
||||
MageObject targetSource = game.getObject(source);
|
||||
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null
|
||||
&& (notTarget || player.canBeTargetedBy(targetSource, sourceControllerId, game))
|
||||
&& filter.match(player, game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) {
|
||||
if ((notTarget
|
||||
|| permanent.canBeTargetedBy(targetSource, sourceControllerId, game))
|
||||
&& filter.match(permanent, game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canChoose(UUID sourceControllerId, Game game) {
|
||||
int count = 0;
|
||||
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null
|
||||
&& filter.match(player, game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) {
|
||||
if (filter.match(permanent, game)) {
|
||||
count++;
|
||||
if (count >= this.minNumberOfTargets) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
|
||||
if (source == null) {
|
||||
return possibleTargets(sourceControllerId, game);
|
||||
}
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
MageObject targetSource = game.getObject(source);
|
||||
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null
|
||||
&& (notTarget
|
||||
|| player.canBeTargetedBy(targetSource, sourceControllerId, game))
|
||||
&& filter.match(player, game)) {
|
||||
possibleTargets.add(playerId);
|
||||
}
|
||||
}
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) {
|
||||
if ((notTarget
|
||||
|| permanent.canBeTargetedBy(targetSource, sourceControllerId, game))
|
||||
&& filter.match(permanent, game)) {
|
||||
possibleTargets.add(permanent.getId());
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> possibleTargets(UUID sourceControllerId, Game game) {
|
||||
Set<UUID> possibleTargets = new HashSet<>();
|
||||
for (UUID playerId : game.getState().getPlayersInRange(sourceControllerId, game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player != null
|
||||
&& filter.match(player, game)) {
|
||||
possibleTargets.add(playerId);
|
||||
}
|
||||
}
|
||||
for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, sourceControllerId, game)) {
|
||||
if (filter.match(permanent, game)) {
|
||||
possibleTargets.add(permanent.getId());
|
||||
}
|
||||
}
|
||||
return possibleTargets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTargetedName(Game game) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (UUID targetId : getTargets()) {
|
||||
Permanent permanent = game.getPermanent(targetId);
|
||||
if (permanent != null) {
|
||||
sb.append(permanent.getName()).append(' ');
|
||||
} else {
|
||||
Player player = game.getPlayer(targetId);
|
||||
sb.append(player.getLogName()).append(' ');
|
||||
}
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Game game) {
|
||||
Player player = game.getPlayer(id);
|
||||
if (player != null) {
|
||||
return filter.match(player, game);
|
||||
}
|
||||
Permanent permanent = game.getPermanent(id);
|
||||
return permanent != null
|
||||
&& filter.match(permanent, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID id, Ability source, Game game) {
|
||||
Player player = game.getPlayer(id);
|
||||
MageObject targetSource = game.getObject(attackerId);
|
||||
if (player != null) {
|
||||
return (notTarget
|
||||
|| player.canBeTargetedBy(targetSource, (source == null ? null : source.getControllerId()), game))
|
||||
&& filter.match(player, game);
|
||||
}
|
||||
Permanent permanent = game.getPermanent(id); // planeswalker
|
||||
if (permanent != null) {
|
||||
//Could be targeting due to combat decision to attack a player or planeswalker.
|
||||
UUID controllerId = null;
|
||||
if (source != null) {
|
||||
controllerId = source.getControllerId();
|
||||
}
|
||||
return (notTarget
|
||||
|| permanent.canBeTargetedBy(targetSource, controllerId, game))
|
||||
&& filter.match(permanent, game);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
|
||||
return canTarget(id, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetDefender copy() {
|
||||
return new TargetDefender(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1726,14 +1726,14 @@ public final class CardUtil {
|
|||
return i == null ? 1 : Integer.sum(i, 1);
|
||||
}
|
||||
|
||||
public static String convertStartingLoyalty(int startingLoyalty) {
|
||||
switch (startingLoyalty) {
|
||||
public static String convertLoyaltyOrDefense(int value) {
|
||||
switch (value) {
|
||||
case -2:
|
||||
return "X";
|
||||
case -1:
|
||||
return "";
|
||||
default:
|
||||
return "" + startingLoyalty;
|
||||
return "" + value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ public class CopyTokenFunction implements Function<Token, Card> {
|
|||
target.setPower(sourceObj.getPower().getBaseValue());
|
||||
target.setToughness(sourceObj.getToughness().getBaseValue());
|
||||
target.setStartingLoyalty(sourceObj.getStartingLoyalty());
|
||||
target.setStartingDefense(sourceObj.getStartingDefense());
|
||||
|
||||
return target;
|
||||
}
|
||||
|
|
@ -114,6 +115,7 @@ public class CopyTokenFunction implements Function<Token, Card> {
|
|||
target.setZoneChangeCounter(spell.getZoneChangeCounter(game), game);
|
||||
// Copy starting loyalty from spell (Ob Nixilis, the Adversary)
|
||||
target.setStartingLoyalty(spell.getStartingLoyalty());
|
||||
target.setStartingDefense(spell.getStartingDefense());
|
||||
} else {
|
||||
target.setZoneChangeCounter(source.getZoneChangeCounter(game), game);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue