mirror of
https://github.com/magefree/mage.git
synced 2025-12-26 05:22:02 -08:00
Reworking goad effects (ready for review) (#8034)
* changing goad to designation, refactored goad effects to be continuous * [AFC] Implemented Vengeful Ancestor * reworked effects which goad an attached creature * updated goading implementation * updated combat with new goad logic * some more changes, added a test * another fix * update to test, still fails * added more failing tests * more failing tests * added additional goad check * small fix to two tests (still failing * added a regular combat test (passes and fails randomly) * fixed bug in computer player random selection * some changes to how TargetDefender is handled * removed unnecessary class * more combat fixes, tests pass now * removed tests which no longer work due to combat changes * small merge fix * [NEC] Implemented Komainu Battle Armor * [NEC] Implemented Kaima, the Fractured Calm * [NEC] added all variants
This commit is contained in:
parent
5725873aeb
commit
4591ac07cc
30 changed files with 812 additions and 438 deletions
|
|
@ -1,6 +1,5 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.TriggeredAbilityImpl;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.constants.SetTargetPointer;
|
||||
|
|
@ -11,9 +10,11 @@ import mage.game.Game;
|
|||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class AttacksAllTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
|
@ -74,18 +75,15 @@ public class AttacksAllTriggeredAbility extends TriggeredAbilityImpl {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
getEffects().setValue("attacker", permanent);
|
||||
switch (setTargetPointer) {
|
||||
case PERMANENT:
|
||||
for (Effect effect : getEffects()) {
|
||||
effect.setTargetPointer(new FixedTarget(permanent, game));
|
||||
}
|
||||
getEffects().setTargetPointer(new FixedTarget(permanent, game));
|
||||
break;
|
||||
case PLAYER:
|
||||
UUID playerId = controller ? permanent.getControllerId() : game.getCombat().getDefendingPlayerId(permanent.getId(), game);
|
||||
if (playerId != null) {
|
||||
for (Effect effect : getEffects()) {
|
||||
effect.setTargetPointer(new FixedTarget(playerId));
|
||||
}
|
||||
getEffects().setTargetPointer(new FixedTarget(playerId));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -101,10 +99,8 @@ public class AttacksAllTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
@Override
|
||||
public String getTriggerPhrase() {
|
||||
return "Whenever " + (filter.getMessage().startsWith("an") ? "" : "a ")
|
||||
+ filter.getMessage() + " attacks"
|
||||
+ (attacksYouOrYourPlaneswalker ? " you or a planeswalker you control" : "")
|
||||
+ ", " ;
|
||||
return "Whenever " + CardUtil.addArticle(filter.getMessage()) + " attacks"
|
||||
+ (attacksYouOrYourPlaneswalker ? " you or a planeswalker you control" : "") + ", ";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.StaticAbility;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.RestrictionEffect;
|
||||
import mage.abilities.effects.common.combat.AttacksIfAbleAttachedEffect;
|
||||
import mage.constants.AttachmentType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class GoadAttachedAbility extends StaticAbility {
|
||||
|
||||
public GoadAttachedAbility(Effect... effects) {
|
||||
super(Zone.BATTLEFIELD, null);
|
||||
for (Effect effect : effects) {
|
||||
this.addEffect(effect);
|
||||
}
|
||||
this.addEffect(new AttacksIfAbleAttachedEffect(
|
||||
Duration.WhileOnBattlefield, AttachmentType.AURA
|
||||
).setText((getEffects().size() > 1 ? ", " : " ") + "and is goaded. <i>(It attacks each combat if able"));
|
||||
this.addEffect(new GoadAttackEffect());
|
||||
}
|
||||
|
||||
private GoadAttachedAbility(final GoadAttachedAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoadAttachedAbility copy() {
|
||||
return new GoadAttachedAbility(this);
|
||||
}
|
||||
}
|
||||
|
||||
class GoadAttackEffect extends RestrictionEffect {
|
||||
|
||||
GoadAttackEffect() {
|
||||
super(Duration.WhileOnBattlefield);
|
||||
staticText = "and attacks a player other than you if able.)</i>";
|
||||
}
|
||||
|
||||
private GoadAttackEffect(final GoadAttackEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoadAttackEffect copy() {
|
||||
return new GoadAttackEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(Permanent permanent, Ability source, Game game) {
|
||||
Permanent attachment = game.getPermanent(source.getSourceId());
|
||||
return attachment != null && attachment.getAttachedTo() != null
|
||||
&& permanent.getId().equals(attachment.getAttachedTo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) {
|
||||
if (defenderId == null
|
||||
|| game.getState().getPlayersInRange(attacker.getControllerId(), game).size() == 2) { // just 2 players left, so it may attack you
|
||||
return true;
|
||||
}
|
||||
// A planeswalker controlled by the controller is the defender
|
||||
if (game.getPermanent(defenderId) != null) {
|
||||
return !game.getPermanent(defenderId).getControllerId().equals(source.getControllerId());
|
||||
}
|
||||
// The controller is the defender
|
||||
return !defenderId.equals(source.getControllerId());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package mage.abilities.effects.common.combat;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.RestrictionEffect;
|
||||
import mage.constants.Duration;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class CantAttackControllerDueToGoadEffect extends RestrictionEffect {
|
||||
|
||||
public CantAttackControllerDueToGoadEffect(Duration duration) {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public CantAttackControllerDueToGoadEffect(final CantAttackControllerDueToGoadEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CantAttackControllerDueToGoadEffect copy() {
|
||||
return new CantAttackControllerDueToGoadEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(Permanent permanent, Ability source, Game game) {
|
||||
return this.getTargetPointer().getTargets(game, source).contains(permanent.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) {
|
||||
if (defenderId == null
|
||||
|| game.getState().getPlayersInRange(attacker.getControllerId(), game).size() == 2) { // just 2 players left, so it may attack you
|
||||
return true;
|
||||
}
|
||||
// A planeswalker controlled by the controller is the defender
|
||||
if (game.getPermanent(defenderId) != null) {
|
||||
return !game.getPermanent(defenderId).getControllerId().equals(source.getControllerId());
|
||||
}
|
||||
// The controller is the defender
|
||||
return !defenderId.equals(source.getControllerId());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package mage.abilities.effects.common.combat;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Layer;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubLayer;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class GoadAttachedEffect extends ContinuousEffectImpl {
|
||||
|
||||
public GoadAttachedEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment);
|
||||
staticText = "and is goaded";
|
||||
}
|
||||
|
||||
private GoadAttachedEffect(final GoadAttachedEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
Permanent attached = game.getPermanent(permanent.getAttachedTo());
|
||||
if (attached == null) {
|
||||
return false;
|
||||
}
|
||||
attached.addGoadingPlayer(source.getControllerId());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoadAttachedEffect copy() {
|
||||
return new GoadAttachedEffect(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,19 +2,19 @@ package mage.abilities.effects.common.combat;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Layer;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubLayer;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class GoadTargetEffect extends OneShotEffect {
|
||||
public class GoadTargetEffect extends ContinuousEffectImpl {
|
||||
|
||||
/**
|
||||
* 701.36. Goad
|
||||
|
|
@ -24,10 +24,10 @@ public class GoadTargetEffect extends OneShotEffect {
|
|||
* each combat if able and attacks a player other than that player if able.
|
||||
*/
|
||||
public GoadTargetEffect() {
|
||||
super(Outcome.Detriment);
|
||||
super(Duration.UntilYourNextTurn, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment);
|
||||
}
|
||||
|
||||
public GoadTargetEffect(final GoadTargetEffect effect) {
|
||||
private GoadTargetEffect(final GoadTargetEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
|
|
@ -37,26 +37,22 @@ public class GoadTargetEffect extends OneShotEffect {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
public void init(Ability source, Game game) {
|
||||
super.init(source, game);
|
||||
Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source));
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (targetCreature != null && controller != null) {
|
||||
// TODO: Allow goad to target controller, current AttacksIfAbleTargetEffect does not support it
|
||||
// https://github.com/magefree/mage/issues/5283
|
||||
/*
|
||||
If the creature doesn’t meet any of the above exceptions and can attack, it must attack a player other than
|
||||
the controller of the spell or ability that goaded it if able. If the creature can’t attack any of those
|
||||
players but could otherwise attack, it must attack an opposing planeswalker (controlled by any opponent)
|
||||
or the player that goaded it. (2016-08-23)
|
||||
*/
|
||||
ContinuousEffect effect = new AttacksIfAbleTargetEffect(Duration.UntilYourNextTurn);
|
||||
effect.setTargetPointer(new FixedTarget(getTargetPointer().getFirst(game, source), game));
|
||||
game.addEffect(effect, source);
|
||||
effect = new CantAttackControllerDueToGoadEffect(Duration.UntilYourNextTurn); // remember current controller
|
||||
effect.setTargetPointer(new FixedTarget(getTargetPointer().getFirst(game, source), game));
|
||||
game.addEffect(effect, source);
|
||||
game.informPlayers(controller.getLogName() + " is goading " + targetCreature.getLogName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source));
|
||||
if (targetCreature == null) {
|
||||
return false;
|
||||
}
|
||||
targetCreature.addGoadingPlayer(source.getControllerId());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -440,65 +440,76 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
for (Permanent creature : player.getAvailableAttackers(game)) {
|
||||
boolean mustAttack = false;
|
||||
Set<UUID> defendersForcedToAttack = new HashSet<>();
|
||||
|
||||
// check if a creature has to attack
|
||||
for (Map.Entry<RequirementEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, false, game).entrySet()) {
|
||||
RequirementEffect effect = entry.getKey();
|
||||
if (effect.mustAttack(game)
|
||||
&& checkAttackRestrictions(player, game)) { // needed for Goad Effect
|
||||
if (creature.getGoadingPlayers().isEmpty()) {
|
||||
// check if a creature has to attack
|
||||
for (Map.Entry<RequirementEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, false, game).entrySet()) {
|
||||
RequirementEffect effect = entry.getKey();
|
||||
if (!effect.mustAttack(game)) {
|
||||
continue;
|
||||
}
|
||||
mustAttack = true;
|
||||
for (Ability ability : entry.getValue()) {
|
||||
UUID defenderId = effect.mustAttackDefender(ability, game);
|
||||
if (defenderId != null) {
|
||||
if (defenders.contains(defenderId)) {
|
||||
defendersForcedToAttack.add(defenderId);
|
||||
}
|
||||
if (defenderId != null && defenders.contains(defenderId)) {
|
||||
defendersForcedToAttack.add(defenderId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if creature is goaded then we start with assumption that it needs to attack any player
|
||||
mustAttack = true;
|
||||
defendersForcedToAttack.addAll(defenders);
|
||||
}
|
||||
if (mustAttack) {
|
||||
// check which defenders the forced to attack creature can attack without paying a cost
|
||||
Set<UUID> defendersCostlessAttackable = new HashSet<>(defenders);
|
||||
for (UUID defenderId : defenders) {
|
||||
if (game.getContinuousEffects().checkIfThereArePayCostToAttackBlockEffects(
|
||||
new DeclareAttackerEvent(defenderId, creature.getId(), creature.getControllerId()), game)) {
|
||||
defendersCostlessAttackable.remove(defenderId);
|
||||
defendersForcedToAttack.remove(defenderId);
|
||||
}
|
||||
}
|
||||
// force attack only if a defender can be attacked without paying a cost
|
||||
if (!defendersCostlessAttackable.isEmpty()) {
|
||||
creaturesForcedToAttack.put(creature.getId(), defendersForcedToAttack);
|
||||
// No need to attack a special defender
|
||||
if (defendersForcedToAttack.isEmpty()) {
|
||||
if (defendersCostlessAttackable.size() == 1) {
|
||||
player.declareAttacker(creature.getId(), defendersCostlessAttackable.iterator().next(), game, false);
|
||||
} else {
|
||||
TargetDefender target = new TargetDefender(defendersCostlessAttackable, creature.getId());
|
||||
target.setRequired(true);
|
||||
target.setTargetName("planeswalker 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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (defendersForcedToAttack.size() == 1) {
|
||||
player.declareAttacker(creature.getId(), defendersForcedToAttack.iterator().next(), game, false);
|
||||
} else {
|
||||
TargetDefender target = new TargetDefender(defendersForcedToAttack, creature.getId());
|
||||
target.setRequired(true);
|
||||
target.setTargetName("planeswalker 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mustAttack) {
|
||||
continue;
|
||||
}
|
||||
// check which defenders the forced to attack creature can attack without paying a cost
|
||||
Set<UUID> defendersCostlessAttackable = new HashSet<>(defenders);
|
||||
for (UUID defenderId : defenders) {
|
||||
if (game.getContinuousEffects().checkIfThereArePayCostToAttackBlockEffects(
|
||||
new DeclareAttackerEvent(defenderId, creature.getId(), creature.getControllerId()), game
|
||||
)) {
|
||||
defendersCostlessAttackable.remove(defenderId);
|
||||
defendersForcedToAttack.remove(defenderId);
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<RestrictionEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRestrictionEffects(creature, game).entrySet()) {
|
||||
if (entry
|
||||
.getValue()
|
||||
.stream()
|
||||
.anyMatch(ability -> entry.getKey().canAttack(
|
||||
creature, defenderId, ability, game, false
|
||||
))) {
|
||||
continue;
|
||||
}
|
||||
defendersCostlessAttackable.remove(defenderId);
|
||||
defendersForcedToAttack.remove(defenderId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if creature can attack someone other than a player that goaded them
|
||||
// then they attack one of those players, otherwise they attack any player
|
||||
if (!defendersForcedToAttack.stream().allMatch(creature.getGoadingPlayers()::contains)) {
|
||||
defendersForcedToAttack.removeAll(creature.getGoadingPlayers());
|
||||
}
|
||||
// force attack only if a defender can be attacked without paying a cost
|
||||
if (defendersCostlessAttackable.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
creaturesForcedToAttack.put(creature.getId(), defendersForcedToAttack);
|
||||
// No need to attack a special defender
|
||||
Set<UUID> defendersToChooseFrom = defendersForcedToAttack.isEmpty() ? defendersCostlessAttackable : defendersForcedToAttack;
|
||||
if (defendersToChooseFrom.size() == 1) {
|
||||
player.declareAttacker(creature.getId(), defendersToChooseFrom.iterator().next(), game, false);
|
||||
continue;
|
||||
}
|
||||
TargetDefender target = new TargetDefender(defendersToChooseFrom, creature.getId());
|
||||
target.setRequired(true);
|
||||
target.setTargetName("planeswalker 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -529,28 +540,30 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
for (Map.Entry<RestrictionEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRestrictionEffects(attackingCreature, game).entrySet()) {
|
||||
RestrictionEffect effect = entry.getKey();
|
||||
for (Ability ability : entry.getValue()) {
|
||||
if (!effect.canAttackCheckAfter(numberAttackers, ability, game, true)) {
|
||||
MageObject sourceObject = ability.getSourceObject(game);
|
||||
if (attackingPlayer.isHuman()) {
|
||||
attackingPlayer.resetPlayerPassedActions();
|
||||
game.informPlayer(attackingPlayer, attackingCreature.getIdName() + " can't attack this way (" + (sourceObject == null ? "null" : sourceObject.getIdName()) + ')');
|
||||
return false;
|
||||
} else {
|
||||
// remove attacking creatures for AI that are not allowed to attack
|
||||
// can create possible not allowed attack scenarios, but not sure how to solve this
|
||||
for (CombatGroup combatGroup : this.getGroups()) {
|
||||
if (combatGroup.getAttackers().contains(attackingCreatureId)) {
|
||||
attackerToRemove = attackingCreatureId;
|
||||
}
|
||||
}
|
||||
check = true; // do the check again
|
||||
if (numberOfChecks > 50) {
|
||||
logger.error("Seems to be an AI declare attacker lock (reached 50 check iterations) " + (sourceObject == null ? "null" : sourceObject.getIdName()));
|
||||
return true; // break the check
|
||||
}
|
||||
continue Check;
|
||||
}
|
||||
if (effect.canAttackCheckAfter(numberAttackers, ability, game, true)) {
|
||||
continue;
|
||||
}
|
||||
MageObject sourceObject = ability.getSourceObject(game);
|
||||
if (attackingPlayer.isHuman()) {
|
||||
attackingPlayer.resetPlayerPassedActions();
|
||||
game.informPlayer(attackingPlayer, attackingCreature.getIdName() + " can't attack this way (" + (sourceObject == null ? "null" : sourceObject.getIdName()) + ')');
|
||||
return false;
|
||||
}
|
||||
// remove attacking creatures for AI that are not allowed to attack
|
||||
// can create possible not allowed attack scenarios, but not sure how to solve this
|
||||
if (this.getGroups()
|
||||
.stream()
|
||||
.map(CombatGroup::getAttackers)
|
||||
.flatMap(Collection::stream)
|
||||
.anyMatch(attackingCreatureId::equals)) {
|
||||
attackerToRemove = attackingCreatureId;
|
||||
}
|
||||
check = true; // do the check again
|
||||
if (numberOfChecks > 50) {
|
||||
logger.error("Seems to be an AI declare attacker lock (reached 50 check iterations) " + (sourceObject == null ? "null" : sourceObject.getIdName()));
|
||||
return true; // break the check
|
||||
}
|
||||
continue Check;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1300,21 +1313,20 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
@SuppressWarnings("deprecation")
|
||||
public boolean declareAttacker(UUID creatureId, UUID defenderId, UUID playerId, Game game) {
|
||||
Permanent attacker = game.getPermanent(creatureId);
|
||||
if (attacker != null) {
|
||||
if (!game.replaceEvent(new DeclareAttackerEvent(defenderId, creatureId, playerId))) {
|
||||
if (addAttackerToCombat(creatureId, defenderId, game)) {
|
||||
if (!attacker.hasAbility(VigilanceAbility.getInstance(), game)
|
||||
&& !attacker.hasAbility(JohanVigilanceAbility.getInstance(), game)) {
|
||||
if (!attacker.isTapped()) {
|
||||
attacker.setTapped(true);
|
||||
attackersTappedByAttack.add(attacker.getId());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (attacker == null
|
||||
|| game.replaceEvent(new DeclareAttackerEvent(defenderId, creatureId, playerId))
|
||||
|| !addAttackerToCombat(creatureId, defenderId, game)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
if (attacker.hasAbility(VigilanceAbility.getInstance(), game)
|
||||
|| attacker.hasAbility(JohanVigilanceAbility.getInstance(), game)) {
|
||||
return true;
|
||||
}
|
||||
if (!attacker.isTapped()) {
|
||||
attacker.setTapped(true);
|
||||
attackersTappedByAttack.add(attacker.getId());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean addAttackerToCombat(UUID attackerId, UUID defenderId, Game game) {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,10 @@ public interface Permanent extends Card, Controllable {
|
|||
*/
|
||||
boolean setClassLevel(int classLevel);
|
||||
|
||||
void addGoadingPlayer(UUID playerId);
|
||||
|
||||
Set<UUID> getGoadingPlayers();
|
||||
|
||||
void setCardNumber(String cid);
|
||||
|
||||
void setExpansionSetCode(String expansionSetCode);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
protected boolean manifested = false;
|
||||
protected boolean morphed = false;
|
||||
protected int classLevel = 1;
|
||||
protected final Set<UUID> goadingPlayers = new HashSet<>();
|
||||
protected UUID originalControllerId;
|
||||
protected UUID controllerId;
|
||||
protected UUID beforeResetControllerId;
|
||||
|
|
@ -165,6 +166,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.monstrous = permanent.monstrous;
|
||||
this.renowned = permanent.renowned;
|
||||
this.classLevel = permanent.classLevel;
|
||||
this.goadingPlayers.addAll(permanent.goadingPlayers);
|
||||
this.pairedPermanent = permanent.pairedPermanent;
|
||||
this.bandedCards.addAll(permanent.bandedCards);
|
||||
this.timesLoyaltyUsed = permanent.timesLoyaltyUsed;
|
||||
|
|
@ -209,6 +211,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.minBlockedBy = 1;
|
||||
this.maxBlockedBy = 0;
|
||||
this.copy = false;
|
||||
this.goadingPlayers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1345,14 +1348,10 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
}
|
||||
//20101001 - 508.1c
|
||||
if (defenderId == null) {
|
||||
boolean oneCanBeAttacked = false;
|
||||
for (UUID defenderToCheckId : game.getCombat().getDefenders()) {
|
||||
if (canAttackCheckRestrictionEffects(defenderToCheckId, game)) {
|
||||
oneCanBeAttacked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!oneCanBeAttacked) {
|
||||
if (game.getCombat()
|
||||
.getDefenders()
|
||||
.stream()
|
||||
.noneMatch(defenderToCheckId -> canAttackCheckRestrictionEffects(defenderToCheckId, game))) {
|
||||
return false;
|
||||
}
|
||||
} else if (!canAttackCheckRestrictionEffects(defenderId, game)) {
|
||||
|
|
@ -1582,6 +1581,16 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGoadingPlayer(UUID playerId) {
|
||||
this.goadingPlayers.add(playerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getGoadingPlayers() {
|
||||
return goadingPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPairedCard(MageObjectReference pairedCard) {
|
||||
this.pairedPermanent = pairedCard;
|
||||
|
|
|
|||
|
|
@ -2625,10 +2625,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null
|
||||
&& attacker.canAttack(defenderId, game)
|
||||
&& attacker.isControlledBy(playerId)) {
|
||||
if (!game.getCombat().declareAttacker(attackerId, defenderId, playerId, game)) {
|
||||
game.undo(playerId);
|
||||
}
|
||||
&& attacker.isControlledBy(playerId)
|
||||
&& !game.getCombat().declareAttacker(attackerId, defenderId, playerId, game)) {
|
||||
game.undo(playerId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ public class TargetDefender extends TargetImpl {
|
|||
this.filter = new FilterPlaneswalkerOrPlayer(defenders);
|
||||
this.targetName = filter.getMessage();
|
||||
this.attackerId = attackerId;
|
||||
this.notTarget = true;
|
||||
}
|
||||
|
||||
public TargetDefender(final TargetDefender target) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package mage.util;
|
|||
import java.awt.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by IGOUDT on 5-9-2016.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue