Reworking effects that allow controlling combat (WIP) (#8159)

* reworked effects that allow controlling combat

* [AFC] Implemented Berserker's Frenzy

* [AFC] updated Berserker's Frenzy to roll correctly
This commit is contained in:
Evan Kranzler 2021-10-10 10:25:10 -04:00 committed by GitHub
parent f5177097cd
commit f7e821be2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 366 additions and 492 deletions

View file

@ -0,0 +1,107 @@
package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility;
import mage.abilities.condition.Condition;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.RollDieWithResultTableEffect;
import mage.abilities.effects.common.combat.BlocksIfAbleTargetEffect;
import mage.abilities.effects.common.combat.ChooseBlockersEffect;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.CardsImpl;
import mage.constants.*;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTargets;
import mage.watchers.common.ControlCombatRedundancyWatcher;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BerserkersFrenzy extends CardImpl {
private static final Hint hint = new ConditionHint(BerserkersFrenzyCondition.instance, "Can be cast");
public BerserkersFrenzy(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}");
// Cast this spell only before combat or during combat before blockers are declared.
this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(
null, null, BerserkersFrenzyCondition.instance,
"Cast this spell only before combat or during combat before blockers are declared"
).addHint(hint));
// Roll two d20 and ignore the lower roll.
RollDieWithResultTableEffect effect = new RollDieWithResultTableEffect(
20, "roll two d20 and ignore the lower roll", StaticValue.get(0), 1
);
// 1-14 | Choose any number of creatures. They block this turn if able.
effect.addTableEntry(1, 14, new BerserkersFrenzyEffect());
// 15-20 | You choose which creatures block this turn and how those creatures block.
effect.addTableEntry(15, 20, new ChooseBlockersEffect(Duration.EndOfTurn));
this.getSpellAbility().addEffect(effect);
this.getSpellAbility().addWatcher(new ControlCombatRedundancyWatcher());
}
private BerserkersFrenzy(final BerserkersFrenzy card) {
super(card);
}
@Override
public BerserkersFrenzy copy() {
return new BerserkersFrenzy(this);
}
}
enum BerserkersFrenzyCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
if (game.getPhase().getType() == TurnPhase.COMBAT) {
return game.getStep().getType().isBefore(PhaseStep.DECLARE_BLOCKERS);
}
return !game.getTurn().isDeclareAttackersStepStarted();
}
}
class BerserkersFrenzyEffect extends OneShotEffect {
BerserkersFrenzyEffect() {
super(Outcome.Benefit);
staticText = "choose any number of creatures. They block this turn if able";
}
private BerserkersFrenzyEffect(final BerserkersFrenzyEffect effect) {
super(effect);
}
@Override
public BerserkersFrenzyEffect copy() {
return new BerserkersFrenzyEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
TargetPermanent target = new TargetCreaturePermanent(0, Integer.MAX_VALUE);
target.setNotTarget(true);
player.choose(outcome, target, source.getSourceId(), game);
game.addEffect(new BlocksIfAbleTargetEffect(Duration.EndOfTurn)
.setTargetPointer(new FixedTargets(new CardsImpl(target.getTargets()), game)), source);
return true;
}
}

View file

@ -1,17 +1,14 @@
package mage.cards.b;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LoseLifeTargetEffect;
import mage.abilities.effects.common.combat.BlocksIfAbleAllEffect;
import mage.abilities.effects.common.combat.ChooseBlockersEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
@ -19,12 +16,12 @@ import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.watchers.common.ChooseBlockersRedundancyWatcher;
import mage.watchers.common.ControlCombatRedundancyWatcher;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class BrutalHordechief extends CardImpl {
@ -36,7 +33,7 @@ public final class BrutalHordechief extends CardImpl {
}
public BrutalHordechief(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}");
this.subtype.add(SubType.ORC, SubType.WARRIOR);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
@ -45,10 +42,11 @@ public final class BrutalHordechief extends CardImpl {
this.addAbility(new BrutalHordechiefTriggeredAbility());
// {3}{R/W}{R/W}: Creatures your opponents control block this turn if able, and you choose how those creatures block.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BlocksIfAbleAllEffect(filter, Duration.EndOfTurn), new ManaCostsImpl("{3}{R/W}{R/W}"));
ability.addEffect(new BrutalHordechiefChooseBlockersEffect());
ability.addWatcher(new ChooseBlockersRedundancyWatcher());
ability.addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect());
Ability ability = new SimpleActivatedAbility(
new BlocksIfAbleAllEffect(filter, Duration.EndOfTurn), new ManaCostsImpl<>("{3}{R/W}{R/W}")
);
ability.addEffect(new ChooseBlockersEffect(Duration.EndOfTurn).setText("and you choose how those creatures block"));
ability.addWatcher(new ControlCombatRedundancyWatcher());
this.addAbility(ability);
}
@ -60,32 +58,6 @@ public final class BrutalHordechief extends CardImpl {
public BrutalHordechief copy() {
return new BrutalHordechief(this);
}
private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect {
ChooseBlockersRedundancyWatcherIncrementEffect() {
super(Outcome.Neutral);
}
ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class);
if (watcher != null) {
watcher.increment();
return true;
}
return false;
}
@Override
public ChooseBlockersRedundancyWatcherIncrementEffect copy() {
return new ChooseBlockersRedundancyWatcherIncrementEffect(this);
}
}
}
class BrutalHordechiefTriggeredAbility extends TriggeredAbilityImpl {
@ -112,9 +84,9 @@ class BrutalHordechiefTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent source = game.getPermanent(event.getSourceId());
if (source != null && source.isControlledBy(controllerId)) {
if (source != null && source.isControlledBy(getControllerId())) {
UUID defendingPlayerId = game.getCombat().getDefendingPlayerId(event.getSourceId(), game);
this.getEffects().get(0).setTargetPointer(new FixedTarget(defendingPlayerId));
this.getEffects().setTargetPointer(new FixedTarget(defendingPlayerId));
return true;
}
return false;
@ -125,50 +97,3 @@ class BrutalHordechiefTriggeredAbility extends TriggeredAbilityImpl {
return "Whenever a creature you control attacks, defending player loses 1 life and you gain 1 life.";
}
}
class BrutalHordechiefChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl {
public BrutalHordechiefChooseBlockersEffect() {
super(Duration.EndOfTurn, Outcome.Benefit, false, false);
staticText = "You choose which creatures block this turn and how those creatures block";
}
public BrutalHordechiefChooseBlockersEffect(final BrutalHordechiefChooseBlockersEffect effect) {
super(effect);
}
@Override
public BrutalHordechiefChooseBlockersEffect copy() {
return new BrutalHordechiefChooseBlockersEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARING_BLOCKERS;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class);
if(watcher == null){
return false;
}
watcher.decrement();
if (watcher.copyCountApply > 0) {
game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply");
return false;
}
watcher.copyCountApply = watcher.copyCount;
Player blockController = game.getPlayer(source.getControllerId());
if (blockController != null) {
game.getCombat().selectBlockers(blockController, source, game);
return true;
}
return false;
}
}

View file

@ -26,7 +26,7 @@ public final class DivinersPortent extends CardImpl {
// Roll a d20 and add the number of cards in your hand.
RollDieWithResultTableEffect effect = new RollDieWithResultTableEffect(
20, "roll a d20 and add the number " +
"of cards in your hand", CardsInControllerHandCount.instance
"of cards in your hand", CardsInControllerHandCount.instance, 0
);
this.getSpellAbility().addEffect(effect);

View file

@ -1,20 +1,20 @@
package mage.cards.m;
import java.util.*;
import mage.abilities.Ability;
import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility;
import mage.abilities.condition.common.BeforeAttackersAreDeclaredCondition;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.RequirementEffect;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.effects.common.combat.AttacksIfAbleTargetEffect;
import mage.abilities.effects.common.combat.CantAttackTargetEffect;
import mage.abilities.effects.common.combat.ChooseBlockersEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
@ -23,11 +23,13 @@ import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import mage.watchers.Watcher;
import mage.watchers.common.ChooseBlockersRedundancyWatcher;
import mage.watchers.common.ControlCombatRedundancyWatcher;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
*
* @author L_J
*/
public final class MasterWarcraft extends CardImpl {
@ -36,20 +38,19 @@ public final class MasterWarcraft extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R/W}{R/W}");
// Cast Master Warcraft only before attackers are declared.
this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, BeforeAttackersAreDeclaredCondition.instance, "Cast this spell only before attackers are declared"));
this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(
null, null, BeforeAttackersAreDeclaredCondition.instance,
"Cast this spell only before attackers are declared"
));
// You choose which creatures attack this turn.
this.getSpellAbility().addEffect(new MasterWarcraftChooseAttackersEffect());
// You choose which creatures block this turn and how those creatures block.
this.getSpellAbility().addEffect(new MasterWarcraftChooseBlockersEffect());
this.getSpellAbility().addEffect(new ChooseBlockersEffect(Duration.EndOfTurn).concatBy("<br>"));
// (only the last resolved Master Warcraft spell's effects apply)
this.getSpellAbility().addWatcher(new MasterWarcraftCastWatcher());
this.getSpellAbility().addEffect(new MasterWarcraftCastWatcherIncrementEffect());
this.getSpellAbility().addWatcher(new ChooseBlockersRedundancyWatcher());
this.getSpellAbility().addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect());
this.getSpellAbility().addWatcher(new ControlCombatRedundancyWatcher());
}
private MasterWarcraft(final MasterWarcraft card) {
@ -60,73 +61,22 @@ public final class MasterWarcraft extends CardImpl {
public MasterWarcraft copy() {
return new MasterWarcraft(this);
}
private class MasterWarcraftCastWatcherIncrementEffect extends OneShotEffect {
MasterWarcraftCastWatcherIncrementEffect() {
super(Outcome.Neutral);
}
MasterWarcraftCastWatcherIncrementEffect(final MasterWarcraftCastWatcherIncrementEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
MasterWarcraftCastWatcher watcher = game.getState().getWatcher(MasterWarcraftCastWatcher.class);
if (watcher != null) {
watcher.increment();
return true;
}
return false;
}
@Override
public MasterWarcraftCastWatcherIncrementEffect copy() {
return new MasterWarcraftCastWatcherIncrementEffect(this);
}
}
private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect {
ChooseBlockersRedundancyWatcherIncrementEffect() {
super(Outcome.Neutral);
}
ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class);
if (watcher != null) {
watcher.increment();
return true;
}
return false;
}
@Override
public ChooseBlockersRedundancyWatcherIncrementEffect copy() {
return new ChooseBlockersRedundancyWatcherIncrementEffect(this);
}
}
}
class MasterWarcraftChooseAttackersEffect extends ContinuousRuleModifyingEffectImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures that will attack this combat (creatures not chosen won't attack this combat)");
static {
filter.add(TargetController.ACTIVE.getControllerPredicate());
}
public MasterWarcraftChooseAttackersEffect() {
MasterWarcraftChooseAttackersEffect() {
super(Duration.EndOfTurn, Outcome.Benefit, false, false);
staticText = "You choose which creatures attack this turn";
}
public MasterWarcraftChooseAttackersEffect(final MasterWarcraftChooseAttackersEffect effect) {
private MasterWarcraftChooseAttackersEffect(final MasterWarcraftChooseAttackersEffect effect) {
super(effect);
}
@ -145,131 +95,54 @@ class MasterWarcraftChooseAttackersEffect extends ContinuousRuleModifyingEffectI
return event.getType() == GameEvent.EventType.DECLARING_ATTACKERS;
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
ControlCombatRedundancyWatcher.addAttackingController(source.getControllerId(), duration, game);
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
MasterWarcraftCastWatcher watcher = game.getState().getWatcher(MasterWarcraftCastWatcher.class);
if(watcher == null){
return false;
}
watcher.decrement();
if (watcher.copyCountApply > 0) {
if (!ControlCombatRedundancyWatcher.checkAttackingController(source.getControllerId(), game)) {
game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply");
return false;
}
watcher.copyCountApply = watcher.copyCount;
Player controller = game.getPlayer(source.getControllerId());
Player attackingPlayer = game.getPlayer(game.getCombat().getAttackingPlayerId());
if (controller != null && attackingPlayer != null && !attackingPlayer.getAvailableAttackers(game).isEmpty()) {
Target target = new TargetCreaturePermanent(0, Integer.MAX_VALUE, filter, true);
if (controller.chooseTarget(Outcome.Benefit, target, source, game)) {
for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), source.getSourceId(), game)) {
// Choose creatures that will be attacking this combat
if (target.getTargets().contains(permanent.getId())) {
RequirementEffect effect = new AttacksIfAbleTargetEffect(Duration.EndOfCombat);
effect.setText("");
effect.setTargetPointer(new FixedTarget(permanent, game));
game.addEffect(effect, source);
game.informPlayers(controller.getLogName() + " has decided that " + permanent.getLogName() + " attacks this combat if able");
// All other creatures can't attack (unless they must attack)
} else {
boolean hasToAttack = false;
for (Map.Entry<RequirementEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRequirementEffects(permanent, false, game).entrySet()) {
RequirementEffect effect2 = entry.getKey();
if (effect2.mustAttack(game)) {
hasToAttack = true;
}
}
if (!hasToAttack) {
RestrictionEffect effect = new CantAttackTargetEffect(Duration.EndOfCombat);
effect.setText("");
effect.setTargetPointer(new FixedTarget(permanent, game));
game.addEffect(effect, source);
}
if (controller == null || attackingPlayer == null || attackingPlayer.getAvailableAttackers(game).isEmpty()) {
return false; // the attack declaration resumes for the active player as normal
}
Target target = new TargetCreaturePermanent(0, Integer.MAX_VALUE, filter, true);
if (!controller.chooseTarget(Outcome.Benefit, target, source, game)) {
return false; // the attack declaration resumes for the active player as normal
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), source.getSourceId(), game)) {
// Choose creatures that will be attacking this combat
if (target.getTargets().contains(permanent.getId())) {
RequirementEffect effect = new AttacksIfAbleTargetEffect(Duration.EndOfCombat);
effect.setText("");
effect.setTargetPointer(new FixedTarget(permanent, game));
game.addEffect(effect, source);
game.informPlayers(controller.getLogName() + " has decided that " + permanent.getLogName() + " attacks this combat if able");
// All other creatures can't attack (unless they must attack)
} else {
boolean hasToAttack = false;
for (Map.Entry<RequirementEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRequirementEffects(permanent, false, game).entrySet()) {
RequirementEffect effect2 = entry.getKey();
if (effect2.mustAttack(game)) {
hasToAttack = true;
}
}
if (!hasToAttack) {
RestrictionEffect effect = new CantAttackTargetEffect(Duration.EndOfCombat);
effect.setText("");
effect.setTargetPointer(new FixedTarget(permanent, game));
game.addEffect(effect, source);
}
}
}
return false; // the attack declaration resumes for the active player as normal
}
}
class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl {
public MasterWarcraftChooseBlockersEffect() {
super(Duration.EndOfTurn, Outcome.Benefit, false, false);
staticText = "You choose which creatures block this turn and how those creatures block";
}
public MasterWarcraftChooseBlockersEffect(final MasterWarcraftChooseBlockersEffect effect) {
super(effect);
}
@Override
public MasterWarcraftChooseBlockersEffect copy() {
return new MasterWarcraftChooseBlockersEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARING_BLOCKERS;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class);
if(watcher == null){
return false;
}
watcher.decrement();
if (watcher.copyCountApply > 0) {
game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply");
return false;
}
watcher.copyCountApply = watcher.copyCount;
Player blockController = game.getPlayer(source.getControllerId());
if (blockController != null) {
game.getCombat().selectBlockers(blockController, source, game);
return true;
}
return false;
}
}
class MasterWarcraftCastWatcher extends Watcher {
public int copyCount = 0;
public int copyCountApply = 0;
public MasterWarcraftCastWatcher() {
super(WatcherScope.GAME);
}
@Override
public void reset() {
copyCount = 0;
copyCountApply = 0;
}
@Override
public void watch(GameEvent event, Game game) {
}
public void increment() {
copyCount++;
copyCountApply = copyCount;
}
public void decrement() {
if (copyCountApply > 0) {
copyCountApply--;
}
}
}

View file

@ -1,6 +1,5 @@
package mage.cards.m;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility;
import mage.abilities.condition.CompoundCondition;
@ -8,25 +7,23 @@ import mage.abilities.condition.Condition;
import mage.abilities.condition.common.BeforeBlockersAreDeclaredCondition;
import mage.abilities.condition.common.IsPhaseCondition;
import mage.abilities.condition.common.MyTurnCondition;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
import mage.abilities.effects.common.RemoveFromCombatTargetEffect;
import mage.abilities.effects.common.UntapTargetEffect;
import mage.abilities.effects.common.combat.ChooseBlockersEffect;
import mage.abilities.hint.ConditionHint;
import mage.abilities.hint.Hint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TurnPhase;
import mage.game.Game;
import mage.game.combat.CombatGroup;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.watchers.common.ChooseBlockersRedundancyWatcher;
import mage.watchers.common.ControlCombatRedundancyWatcher;
import java.util.UUID;
@ -35,21 +32,26 @@ import java.util.UUID;
*/
public final class Melee extends CardImpl {
private static final Condition condition = new CompoundCondition(
BeforeBlockersAreDeclaredCondition.instance,
new IsPhaseCondition(TurnPhase.COMBAT),
MyTurnCondition.instance
);
private static final Hint hint = new ConditionHint(condition, "Can be cast");
public Melee(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}");
// Cast Melee only during your turn and only during combat before blockers are declared.
Condition condition = new CompoundCondition(BeforeBlockersAreDeclaredCondition.instance,
new IsPhaseCondition(TurnPhase.COMBAT),
MyTurnCondition.instance);
this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, condition, "Cast this spell only during your turn and only during combat before blockers are declared")
.addHint(new ConditionHint(condition, "Can cast melee (it's combat phase on your turn)")));
this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(
null, null, condition,
"Cast this spell only during your turn and only during combat before blockers are declared"
).addHint(hint));
// You choose which creatures block this combat and how those creatures block.
// (only the last resolved Melee spell's blocking effect applies)
this.getSpellAbility().addEffect(new MeleeChooseBlockersEffect());
this.getSpellAbility().addWatcher(new ChooseBlockersRedundancyWatcher());
this.getSpellAbility().addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect());
this.getSpellAbility().addEffect(new ChooseBlockersEffect(Duration.EndOfCombat));
this.getSpellAbility().addWatcher(new ControlCombatRedundancyWatcher());
// Whenever a creature attacks and isn't blocked this combat, untap it and remove it from combat.
this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new MeleeTriggeredAbility()));
@ -63,82 +65,6 @@ public final class Melee extends CardImpl {
public Melee copy() {
return new Melee(this);
}
private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect {
ChooseBlockersRedundancyWatcherIncrementEffect() {
super(Outcome.Neutral);
}
ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class);
if (watcher != null) {
watcher.increment();
return true;
}
return false;
}
@Override
public ChooseBlockersRedundancyWatcherIncrementEffect copy() {
return new ChooseBlockersRedundancyWatcherIncrementEffect(this);
}
}
}
class MeleeChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl {
public MeleeChooseBlockersEffect() {
super(Duration.EndOfCombat, Outcome.Benefit, false, false);
staticText = "You choose which creatures block this combat and how those creatures block";
}
public MeleeChooseBlockersEffect(final MeleeChooseBlockersEffect effect) {
super(effect);
}
@Override
public MeleeChooseBlockersEffect copy() {
return new MeleeChooseBlockersEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARING_BLOCKERS;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class);
if (watcher == null) {
return false;
}
watcher.decrement();
watcher.copyCount--;
if (watcher.copyCountApply > 0) {
game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply");
this.discard();
return false;
}
watcher.copyCountApply = watcher.copyCount;
Player blockController = game.getPlayer(source.getControllerId());
if (blockController != null) {
game.getCombat().selectBlockers(blockController, source, game);
return true;
}
this.discard();
return false;
}
}
class MeleeTriggeredAbility extends DelayedTriggeredAbility {

View file

@ -1,19 +1,17 @@
package mage.cards.o;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.combat.ChooseBlockersEffect;
import mage.abilities.keyword.FirstStrikeAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.watchers.common.ChooseBlockersRedundancyWatcher;
import mage.watchers.common.ControlCombatRedundancyWatcher;
import java.util.UUID;
/**
* @author noxx
@ -49,9 +47,8 @@ public final class OdricMasterTactician extends CardImpl {
class OdricMasterTacticianTriggeredAbility extends TriggeredAbilityImpl {
public OdricMasterTacticianTriggeredAbility() {
super(Zone.BATTLEFIELD, new OdricMasterTacticianChooseBlockersEffect());
this.addWatcher(new ChooseBlockersRedundancyWatcher());
this.addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect());
super(Zone.BATTLEFIELD, new ChooseBlockersEffect(Duration.EndOfCombat));
this.addWatcher(new ControlCombatRedundancyWatcher());
}
public OdricMasterTacticianTriggeredAbility(final OdricMasterTacticianTriggeredAbility ability) {
@ -72,80 +69,4 @@ class OdricMasterTacticianTriggeredAbility extends TriggeredAbilityImpl {
public boolean checkTrigger(GameEvent event, Game game) {
return game.getCombat().getAttackers().size() >= 4 && game.getCombat().getAttackers().contains(this.sourceId);
}
private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect {
ChooseBlockersRedundancyWatcherIncrementEffect() {
super(Outcome.Neutral);
}
ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class);
if (watcher != null) {
watcher.increment();
return true;
}
return false;
}
@Override
public ChooseBlockersRedundancyWatcherIncrementEffect copy() {
return new ChooseBlockersRedundancyWatcherIncrementEffect(this);
}
}
}
class OdricMasterTacticianChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl {
public OdricMasterTacticianChooseBlockersEffect() {
super(Duration.EndOfCombat, Outcome.Benefit, false, false);
staticText = "Whenever {this} and at least three other creatures attack, you choose which creatures block this combat and how those creatures block";
}
public OdricMasterTacticianChooseBlockersEffect(final OdricMasterTacticianChooseBlockersEffect effect) {
super(effect);
}
@Override
public OdricMasterTacticianChooseBlockersEffect copy() {
return new OdricMasterTacticianChooseBlockersEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARING_BLOCKERS;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class);
if (watcher == null) {
return false;
}
watcher.decrement();
watcher.copyCount--;
if (watcher.copyCountApply > 0) {
game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply");
this.discard();
return false;
}
watcher.copyCountApply = watcher.copyCount;
Player blockController = game.getPlayer(source.getControllerId());
if (blockController != null) {
game.getCombat().selectBlockers(blockController, source, game);
return true;
}
this.discard();
return false;
}
}

View file

@ -41,7 +41,7 @@ public final class Revivify extends CardImpl {
// Roll a d20 and add the number of creature cards in your graveyard that were put there from the battlefield this turn.
RollDieWithResultTableEffect effect = new RollDieWithResultTableEffect(
20, "roll a d20 and add the number of creature cards " +
"in your graveyard that were put there from the battlefield this turn", xValue
"in your graveyard that were put there from the battlefield this turn", xValue, 0
);
this.getSpellAbility().addEffect(effect);
this.getSpellAbility().addWatcher(new CardsPutIntoGraveyardWatcher());

View file

@ -42,6 +42,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet {
cards.add(new SetCardInfo("Bedevil", 179, Rarity.RARE, mage.cards.b.Bedevil.class));
cards.add(new SetCardInfo("Behemoth Sledge", 180, Rarity.UNCOMMON, mage.cards.b.BehemothSledge.class));
cards.add(new SetCardInfo("Belt of Giant Strength", 38, Rarity.RARE, mage.cards.b.BeltOfGiantStrength.class));
cards.add(new SetCardInfo("Berserker's Frenzy", 29, Rarity.RARE, mage.cards.b.BerserkersFrenzy.class));
cards.add(new SetCardInfo("Bituminous Blast", 181, Rarity.UNCOMMON, mage.cards.b.BituminousBlast.class));
cards.add(new SetCardInfo("Bogardan Hellkite", 115, Rarity.MYTHIC, mage.cards.b.BogardanHellkite.class));
cards.add(new SetCardInfo("Bojuka Bog", 226, Rarity.COMMON, mage.cards.b.BojukaBog.class));

View file

@ -27,6 +27,7 @@ public class RollDieWithResultTableEffect extends OneShotEffect {
private final String prefixText;
private final List<TableEntry> resultsTable = new ArrayList<>();
private final DynamicValue modifier;
private final int toIgnore;
public RollDieWithResultTableEffect() {
this(20);
@ -37,14 +38,15 @@ public class RollDieWithResultTableEffect extends OneShotEffect {
}
public RollDieWithResultTableEffect(int sides, String prefixText) {
this(sides, prefixText, StaticValue.get(0));
this(sides, prefixText, StaticValue.get(0), 0);
}
public RollDieWithResultTableEffect(int sides, String prefixText, DynamicValue modifier) {
public RollDieWithResultTableEffect(int sides, String prefixText, DynamicValue modifier, int toIgnore) {
super(Outcome.Benefit);
this.sides = sides;
this.prefixText = prefixText;
this.modifier = modifier;
this.toIgnore = toIgnore;
}
protected RollDieWithResultTableEffect(final RollDieWithResultTableEffect effect) {
@ -55,6 +57,7 @@ public class RollDieWithResultTableEffect extends OneShotEffect {
this.resultsTable.add(tableEntry.copy());
}
this.modifier = effect.modifier.copy();
this.toIgnore = effect.toIgnore;
}
@Override
@ -68,7 +71,9 @@ public class RollDieWithResultTableEffect extends OneShotEffect {
if (player == null) {
return false;
}
int result = player.rollDice(outcome, source, game, sides) + modifier.calculate(game, source, this);
int result = player.rollDice(
outcome, source, game, sides, 1 + toIgnore, toIgnore
).get(0) + modifier.calculate(game, source, this);
this.applyResult(result, game, source);
return true;
}

View file

@ -0,0 +1,80 @@
package mage.abilities.effects.common.combat;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.watchers.common.ControlCombatRedundancyWatcher;
/**
* @author L_J, TheElk801
*/
public class ChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl {
public ChooseBlockersEffect(Duration duration) {
super(duration, Outcome.Benefit, false, false);
}
private ChooseBlockersEffect(final ChooseBlockersEffect effect) {
super(effect);
}
@Override
public ChooseBlockersEffect copy() {
return new ChooseBlockersEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARING_BLOCKERS;
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
ControlCombatRedundancyWatcher.addBlockingController(source.getControllerId(), this.duration, game);
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (!ControlCombatRedundancyWatcher.checkBlockingController(source.getControllerId(), game)) {
game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply");
return false;
}
Player blockController = game.getPlayer(source.getControllerId());
if (blockController != null) {
game.getCombat().selectBlockers(blockController, source, game);
return true;
}
return false;
}
@Override
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
StringBuilder sb = new StringBuilder("you choose which creatures block this ");
switch (duration) {
case EndOfTurn:
sb.append("turn");
break;
case EndOfCombat:
sb.append("combat");
break;
default:
throw new IllegalArgumentException("duration type not supported");
}
sb.append(" and how those creatures block");
return sb.toString();
}
}

View file

@ -1,42 +0,0 @@
package mage.watchers.common;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.watchers.Watcher;
/**
* @author L_J
*/
public class ChooseBlockersRedundancyWatcher extends Watcher { // workaround for solving timestamp issues regarding "you choose which creatures block and how those creatures block" effects
public int copyCount = 0;
public int copyCountApply = 0;
public ChooseBlockersRedundancyWatcher() {
super(WatcherScope.GAME);
}
@Override
public void reset() {
super.reset();
copyCount = 0;
copyCountApply = 0;
}
@Override
public void watch(GameEvent event, Game game) {
}
public void increment() {
copyCount++;
copyCountApply = copyCount;
}
public void decrement() {
if (copyCountApply > 0) {
copyCountApply--;
}
}
}

View file

@ -0,0 +1,78 @@
package mage.watchers.common;
import mage.constants.Duration;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.watchers.Watcher;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author L_J
*/
public class ControlCombatRedundancyWatcher extends Watcher { // workaround for solving timestamp issues regarding "you choose which creatures block and how those creatures block" effects
private static final class PlayerDuration {
private final Duration duration;
private final UUID playerId;
private PlayerDuration(Duration duration, UUID playerId) {
this.duration = duration;
this.playerId = playerId;
}
private boolean isCombat() {
return duration == Duration.EndOfCombat;
}
private boolean isPlayer(UUID playerId) {
return playerId.equals(this.playerId);
}
}
private final List<PlayerDuration> attackingControllers = new ArrayList<>();
private final List<PlayerDuration> blockingControllers = new ArrayList<>();
public ControlCombatRedundancyWatcher() {
super(WatcherScope.GAME);
}
@Override
public void reset() {
super.reset();
attackingControllers.clear();
blockingControllers.clear();
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.END_COMBAT_STEP_POST) {
attackingControllers.removeIf(PlayerDuration::isCombat);
blockingControllers.removeIf(PlayerDuration::isCombat);
}
}
public static void addAttackingController(UUID playerId, Duration duration, Game game) {
ControlCombatRedundancyWatcher watcher = game.getState().getWatcher(ControlCombatRedundancyWatcher.class);
watcher.attackingControllers.add(0, new PlayerDuration(duration, playerId));
}
public static void addBlockingController(UUID playerId, Duration duration, Game game) {
ControlCombatRedundancyWatcher watcher = game.getState().getWatcher(ControlCombatRedundancyWatcher.class);
watcher.blockingControllers.add(0, new PlayerDuration(duration, playerId));
}
public static boolean checkAttackingController(UUID playerId, Game game) {
ControlCombatRedundancyWatcher watcher = game.getState().getWatcher(ControlCombatRedundancyWatcher.class);
return !watcher.attackingControllers.isEmpty() && watcher.attackingControllers.get(0).isPlayer(playerId);
}
public static boolean checkBlockingController(UUID playerId, Game game) {
ControlCombatRedundancyWatcher watcher = game.getState().getWatcher(ControlCombatRedundancyWatcher.class);
return !watcher.blockingControllers.isEmpty() && watcher.blockingControllers.get(0).isPlayer(playerId);
}
}