mirror of
https://github.com/correl/mage.git
synced 2024-12-25 03:00:15 +00:00
Merge pull request #1 from htrajan/implement-zilortha
Implement zilortha
This commit is contained in:
commit
827843a77b
14 changed files with 373 additions and 50 deletions
|
@ -2434,7 +2434,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
|
||||
simulations = new TreeNode<>(combat);
|
||||
addBlockSimulations(blockers, simulations, game);
|
||||
combat.simulate();
|
||||
combat.simulate(game);
|
||||
|
||||
return getWorstSimulation(simulations);
|
||||
|
||||
|
@ -2452,7 +2452,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
TreeNode<CombatSimulator> child = new TreeNode<>(combat);
|
||||
node.addChild(child);
|
||||
addBlockSimulations(subList, child, game);
|
||||
combat.simulate();
|
||||
combat.simulate(game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
package mage.player.ai.simulators;
|
||||
|
||||
import mage.game.Game;
|
||||
|
@ -52,15 +50,15 @@ public class CombatGroupSimulator implements Serializable {
|
|||
return blocker.canBlock(attacker.id, game);
|
||||
}
|
||||
|
||||
public void simulateCombat() {
|
||||
public void simulateCombat(Game game) {
|
||||
unblockedDamage = 0;
|
||||
|
||||
if (hasFirstOrDoubleStrike())
|
||||
assignDamage(true);
|
||||
assignDamage(false);
|
||||
assignDamage(true, game);
|
||||
assignDamage(false, game);
|
||||
}
|
||||
|
||||
private void assignDamage(boolean first) {
|
||||
private void assignDamage(boolean first, Game game) {
|
||||
if (blockers.isEmpty()) {
|
||||
if (canDamage(attacker, first))
|
||||
unblockedDamage += attacker.power;
|
||||
|
@ -69,7 +67,7 @@ public class CombatGroupSimulator implements Serializable {
|
|||
CreatureSimulator blocker = blockers.get(0);
|
||||
if (canDamage(attacker, first)) {
|
||||
if (attacker.hasTrample) {
|
||||
int lethalDamage = blocker.getLethalDamage();
|
||||
int lethalDamage = blocker.getLethalDamage(game);
|
||||
if (attacker.power > lethalDamage) {
|
||||
blocker.damage += lethalDamage;
|
||||
unblockedDamage += attacker.power - lethalDamage;
|
||||
|
@ -87,7 +85,7 @@ public class CombatGroupSimulator implements Serializable {
|
|||
int damage = attacker.power;
|
||||
for (CreatureSimulator blocker: blockers) {
|
||||
if (damage > 0 && canDamage(attacker, first)) {
|
||||
int lethalDamage = blocker.getLethalDamage();
|
||||
int lethalDamage = blocker.getLethalDamage(game);
|
||||
if (damage > lethalDamage) {
|
||||
blocker.damage += lethalDamage;
|
||||
damage -= lethalDamage;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
package mage.player.ai.simulators;
|
||||
|
||||
import mage.counters.CounterType;
|
||||
|
@ -52,9 +50,9 @@ public class CombatSimulator implements Serializable {
|
|||
attackerId = null;
|
||||
}
|
||||
|
||||
public void simulate() {
|
||||
public void simulate(Game game) {
|
||||
for (CombatGroupSimulator group: groups) {
|
||||
group.simulateCombat();
|
||||
group.simulateCombat(game);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
|
||||
|
||||
package mage.player.ai.simulators;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
import mage.abilities.keyword.DoubleStrikeAbility;
|
||||
import mage.abilities.keyword.FirstStrikeAbility;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
@ -21,6 +23,7 @@ public class CreatureSimulator implements Serializable {
|
|||
public boolean hasFirstStrike;
|
||||
public boolean hasDoubleStrike;
|
||||
public boolean hasTrample;
|
||||
public Permanent permanent;
|
||||
|
||||
public CreatureSimulator(Permanent permanent) {
|
||||
this.id = permanent.getId();
|
||||
|
@ -30,13 +33,25 @@ public class CreatureSimulator implements Serializable {
|
|||
this.hasDoubleStrike = permanent.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId());
|
||||
this.hasFirstStrike = permanent.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId());
|
||||
this.hasTrample = permanent.getAbilities().containsKey(TrampleAbility.getInstance().getId());
|
||||
this.permanent = permanent;
|
||||
}
|
||||
|
||||
public boolean isDead() {
|
||||
return damage >= toughness;
|
||||
}
|
||||
|
||||
public int getLethalDamage() {
|
||||
return toughness - damage;
|
||||
public int getLethalDamage(Game game) {
|
||||
List<FilterCreaturePermanent> usePowerInsteadOfToughnessForDamageLethalityFilters = game.getState().getActivePowerInsteadOfToughnessForDamageLethalityFilters();
|
||||
/*
|
||||
* for handling Zilortha, Strength Incarnate:
|
||||
* 2020-04-17
|
||||
* Any time the game is checking whether damage is lethal or if a creature should be destroyed for having lethal damage marked on it, use the power of your creatures rather than their toughness to check the damage against. This includes being assigned trample damage, damage from Flame Spill, and so on.
|
||||
*/
|
||||
boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream()
|
||||
.anyMatch(filter -> filter.match(permanent, game));
|
||||
int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ?
|
||||
// Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it.
|
||||
Math.max(power, 1) : toughness;
|
||||
return Math.max(lethalDamageThreshold - damage, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import mage.target.common.TargetCreaturePermanent;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
import static mage.game.combat.CombatGroup.getLethalDamage;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
|
@ -62,7 +64,7 @@ class FlameSpillEffect extends OneShotEffect {
|
|||
if (permanent == null || sourceObject == null) {
|
||||
return false;
|
||||
}
|
||||
int lethal = Math.max(permanent.getToughness().getValue() - permanent.getDamage(), 0);
|
||||
int lethal = getLethalDamage(permanent, game);
|
||||
if (sourceObject.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) {
|
||||
lethal = Math.min(lethal, 1);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ import mage.target.common.TargetCreaturePermanent;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
import static mage.game.combat.CombatGroup.getLethalDamage;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
|
@ -85,7 +87,7 @@ class RamThroughEffect extends OneShotEffect {
|
|||
if (!myPermanent.getAbilities().containsKey(TrampleAbility.getInstance().getId())) {
|
||||
return anotherPermanent.damage(power, myPermanent.getId(), game, false, true) > 0;
|
||||
}
|
||||
int lethal = Math.max(anotherPermanent.getToughness().getValue() - anotherPermanent.getDamage(), 0);
|
||||
int lethal = getLethalDamage(anotherPermanent, game);
|
||||
if (myPermanent.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) {
|
||||
lethal = Math.min(lethal, 1);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ import mage.target.common.TargetCreaturePermanent;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
import static mage.game.combat.CombatGroup.getLethalDamage;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
|
@ -68,7 +70,7 @@ class SuperDuperDeathRayEffect extends OneShotEffect {
|
|||
if (permanent == null || sourceObject == null) {
|
||||
return false;
|
||||
}
|
||||
int lethal = Math.max(permanent.getToughness().getValue() - permanent.getDamage(), 0);
|
||||
int lethal = getLethalDamage(permanent, game);
|
||||
if (sourceObject.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) {
|
||||
lethal = Math.min(lethal, 1);
|
||||
}
|
||||
|
|
82
Mage.Sets/src/mage/cards/z/ZilorthaStrengthIncarnate.java
Normal file
82
Mage.Sets/src/mage/cards/z/ZilorthaStrengthIncarnate.java
Normal file
|
@ -0,0 +1,82 @@
|
|||
package mage.cards.z;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.permanent.ControllerIdPredicate;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author htrajan
|
||||
*/
|
||||
public final class ZilorthaStrengthIncarnate extends CardImpl {
|
||||
|
||||
public ZilorthaStrengthIncarnate(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}");
|
||||
this.addSuperType(SuperType.LEGENDARY);
|
||||
this.subtype.add(SubType.DINOSAUR);
|
||||
this.power = new MageInt(7);
|
||||
this.toughness = new MageInt(3);
|
||||
|
||||
// Trample
|
||||
this.addAbility(TrampleAbility.getInstance());
|
||||
|
||||
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ZilorthaStrengthIncarnateEffect()));
|
||||
}
|
||||
|
||||
private ZilorthaStrengthIncarnate(ZilorthaStrengthIncarnate card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card copy() {
|
||||
return new ZilorthaStrengthIncarnate(this);
|
||||
}
|
||||
}
|
||||
|
||||
class ZilorthaStrengthIncarnateEffect extends ContinuousEffectImpl {
|
||||
|
||||
ZilorthaStrengthIncarnateEffect() {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Benefit);
|
||||
staticText = "Lethal damage dealt to creatures you control is determined by their power rather than their toughness";
|
||||
}
|
||||
|
||||
private ZilorthaStrengthIncarnateEffect(ZilorthaStrengthIncarnateEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
|
||||
// Change the rule
|
||||
FilterCreaturePermanent filter = StaticFilters.FILTER_PERMANENT_CREATURE.copy();
|
||||
filter.add(new ControllerIdPredicate(source.getControllerId()));
|
||||
game.getState().addPowerInsteadOfToughnessForDamageLethalityFilter(source.getSourceId(), filter);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLayer(Layer layer) {
|
||||
return layer == Layer.RulesEffects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContinuousEffect copy() {
|
||||
return new ZilorthaStrengthIncarnateEffect(this);
|
||||
}
|
||||
}
|
|
@ -331,6 +331,7 @@ public final class IkoriaLairOfBehemoths extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Zagoth Mamba", 106, Rarity.UNCOMMON, mage.cards.z.ZagothMamba.class));
|
||||
cards.add(new SetCardInfo("Zagoth Triome", 259, Rarity.RARE, mage.cards.z.ZagothTriome.class));
|
||||
cards.add(new SetCardInfo("Zenith Flare", 217, Rarity.UNCOMMON, mage.cards.z.ZenithFlare.class));
|
||||
cards.add(new SetCardInfo("Zilortha, Strength Incarnate", 275, Rarity.MYTHIC, mage.cards.z.ZilorthaStrengthIncarnate.class));
|
||||
cards.add(new SetCardInfo("Zirda, the Dawnwaker", 233, Rarity.RARE, mage.cards.z.ZirdaTheDawnwaker.class));
|
||||
|
||||
cards.removeIf(setCardInfo -> mutateNames.contains(setCardInfo.getName())); // remove when mutate is implemented
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
package org.mage.test.cards.single.iko;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author htrajan
|
||||
*/
|
||||
public class ZilorthaStrengthIncarnateTest extends CardTestPlayerBase {
|
||||
|
||||
@Test
|
||||
public void testNotPresent_combatDamageResolvesLethalityAsNormal() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Drannith Healer");
|
||||
|
||||
attack(1, playerA, "Savai Sabertooth");
|
||||
block(1, playerB, "Drannith Healer", "Savai Sabertooth");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerA, "Savai Sabertooth", 1);
|
||||
assertGraveyardCount(playerB, "Drannith Healer", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPresent_combatDamageResolvesLethalityUsingPower() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Zilortha, Strength Incarnate");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Drannith Healer");
|
||||
|
||||
attack(1, playerA, "Savai Sabertooth");
|
||||
block(1, playerB, "Drannith Healer", "Savai Sabertooth");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerA, "Savai Sabertooth", 0);
|
||||
assertGraveyardCount(playerB, "Drannith Healer", 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* 2020-04-17
|
||||
* A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it.
|
||||
*/
|
||||
@Test
|
||||
public void testPresent_oneDamageRequiredToDestroyZeroPowerCreature() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Zilortha, Strength Incarnate");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Aegis Turtle");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Aegis Turtle");
|
||||
|
||||
attack(1, playerA, "Aegis Turtle");
|
||||
block(1, playerB, "Aegis Turtle", "Aegis Turtle");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerA, "Aegis Turtle", 0);
|
||||
assertGraveyardCount(playerB, "Aegis Turtle", 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotPresent_flameSpillResolvesAsNormal() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
|
||||
addCard(Zone.HAND, playerB, "Flame Spill");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Flame Spill", "Savai Sabertooth");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerA, "Savai Sabertooth", 1);
|
||||
assertGraveyardCount(playerB, "Flame Spill", 1);
|
||||
|
||||
assertLife(playerA, 17);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPresent_flameSpillResolvesUsingPower() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Zilortha, Strength Incarnate");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
|
||||
addCard(Zone.HAND, playerB, "Flame Spill");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Flame Spill", "Savai Sabertooth");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerA, "Savai Sabertooth", 1);
|
||||
assertGraveyardCount(playerB, "Flame Spill", 1);
|
||||
|
||||
assertLife(playerA, 19);
|
||||
}
|
||||
|
||||
/*
|
||||
* 2020-04-17
|
||||
* Because damage remains marked on a creature until the damage is removed as the turn ends, nonlethal damage dealt to a creature you control may become lethal if Zilortha enters or leaves the battlefield during that turn.
|
||||
*/
|
||||
@Test
|
||||
public void testPresent_leavesBattlefield_damageResolvesLethalityUsingPower_thenCheckedAgainstToughness() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Zilortha, Strength Incarnate");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Savai Sabertooth");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Drannith Healer");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3);
|
||||
addCard(Zone.HAND, playerB, "Murder");
|
||||
|
||||
attack(1, playerA, "Savai Sabertooth");
|
||||
block(1, playerB, "Drannith Healer", "Savai Sabertooth");
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Murder", "Zilortha, Strength Incarnate");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerA, "Zilortha, Strength Incarnate", 1);
|
||||
assertGraveyardCount(playerA, "Savai Sabertooth", 1);
|
||||
assertGraveyardCount(playerB, "Drannith Healer", 1);
|
||||
assertGraveyardCount(playerB, "Murder", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPresent_ownedByBothPlayers() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Zilortha, Strength Incarnate");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Maned Serval");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Zilortha, Strength Incarnate");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Maned Serval");
|
||||
|
||||
attack(1, playerA, "Maned Serval");
|
||||
block(1, playerB, "Maned Serval", "Maned Serval");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerA, "Maned Serval", 1);
|
||||
assertGraveyardCount(playerB, "Maned Serval", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbsent_entersBattlefield_damageResolvesLethalityUsingToughness_thenCheckedAgainstPower() {
|
||||
addCard(Zone.HAND, playerA, "Zilortha, Strength Incarnate");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Maned Serval");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Maned Serval");
|
||||
|
||||
attack(1, playerA, "Maned Serval");
|
||||
block(1, playerB, "Maned Serval", "Maned Serval");
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Zilortha, Strength Incarnate");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerA, "Maned Serval", 1);
|
||||
assertGraveyardCount(playerB, "Maned Serval", 0);
|
||||
}
|
||||
}
|
|
@ -485,4 +485,5 @@ public interface Game extends MageItem, Serializable {
|
|||
default Set<UUID> getCommandersIds(Player player) {
|
||||
return getCommandersIds(player, CommanderCardType.ANY);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import mage.filter.FilterCard;
|
|||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.mageobject.NamePredicate;
|
||||
import mage.filter.predicate.permanent.ControllerIdPredicate;
|
||||
import mage.game.combat.Combat;
|
||||
|
@ -1891,6 +1892,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
|
||||
List<Permanent> legendary = new ArrayList<>();
|
||||
List<Permanent> worldEnchantment = new ArrayList<>();
|
||||
List<FilterCreaturePermanent> usePowerInsteadOfToughnessForDamageLethalityFilters = getState().getActivePowerInsteadOfToughnessForDamageLethalityFilters();
|
||||
for (Permanent perm : getBattlefield().getAllActivePermanents()) {
|
||||
if (perm.isCreature()) {
|
||||
//20091005 - 704.5f
|
||||
|
@ -1900,10 +1902,21 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
continue;
|
||||
}
|
||||
} //20091005 - 704.5g/704.5h
|
||||
else if (perm.getToughness().getValue() <= perm.getDamage() || perm.isDeathtouched()) {
|
||||
if (perm.destroy(null, this, false)) {
|
||||
somethingHappened = true;
|
||||
continue;
|
||||
else {
|
||||
/*
|
||||
* for handling Zilortha, Strength Incarnate:
|
||||
* 2020-04-17: Any time the game is checking whether damage is lethal or if a creature should be destroyed for having lethal damage marked on it, use the power of your creatures rather than their toughness to check the damage against. This includes being assigned trample damage, damage from Flame Spill, and so on.
|
||||
*/
|
||||
boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream()
|
||||
.anyMatch(filter -> filter.match(perm, this));
|
||||
int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ?
|
||||
// Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it.
|
||||
Math.max(perm.getPower().getValue(), 1) : perm.getToughness().getValue();
|
||||
if (lethalDamageThreshold <= perm.getDamage() || perm.isDeathtouched()) {
|
||||
if (perm.destroy(null, this, false)) {
|
||||
somethingHappened = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (perm.getPairedCard() != null) {
|
||||
|
@ -3302,5 +3315,4 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
public Set<UUID> getCommandersIds(Player player, CommanderCardType commanderCardType) {
|
||||
return player.getCommandersIds();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import mage.cards.Card;
|
|||
import mage.cards.SplitCard;
|
||||
import mage.constants.Zone;
|
||||
import mage.designations.Designation;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.game.combat.Combat;
|
||||
import mage.game.combat.CombatGroup;
|
||||
import mage.game.command.Command;
|
||||
|
@ -38,6 +39,8 @@ import java.io.Serializable;
|
|||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* <p>
|
||||
|
@ -96,6 +99,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
private Map<UUID, Integer> zoneChangeCounter = new HashMap<>();
|
||||
private Map<UUID, Card> copiedCards = new HashMap<>();
|
||||
private int permanentOrderNumber;
|
||||
private Map<UUID, FilterCreaturePermanent> usePowerInsteadOfToughnessForDamageLethalityFilters = new HashMap<>();
|
||||
|
||||
private int applyEffectsCounter; // Upcounting number of each applyEffects execution
|
||||
|
||||
|
@ -175,6 +179,8 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
this.copiedCards.putAll(state.copiedCards);
|
||||
this.permanentOrderNumber = state.permanentOrderNumber;
|
||||
this.applyEffectsCounter = state.applyEffectsCounter;
|
||||
state.usePowerInsteadOfToughnessForDamageLethalityFilters.forEach((uuid, filter) ->
|
||||
this.usePowerInsteadOfToughnessForDamageLethalityFilters.put(uuid, filter.copy()));
|
||||
}
|
||||
|
||||
public void restoreForRollBack(GameState state) {
|
||||
|
@ -220,6 +226,8 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
this.copiedCards = state.copiedCards;
|
||||
this.permanentOrderNumber = state.permanentOrderNumber;
|
||||
this.applyEffectsCounter = state.applyEffectsCounter;
|
||||
state.usePowerInsteadOfToughnessForDamageLethalityFilters.forEach((uuid, filter) ->
|
||||
this.usePowerInsteadOfToughnessForDamageLethalityFilters.put(uuid, filter.copy()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1093,6 +1101,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
zones.clear();
|
||||
simultaneousEvents.clear();
|
||||
copiedCards.clear();
|
||||
usePowerInsteadOfToughnessForDamageLethalityFilters.clear();
|
||||
permanentOrderNumber = 0;
|
||||
}
|
||||
|
||||
|
@ -1209,4 +1218,15 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
return applyEffectsCounter;
|
||||
}
|
||||
|
||||
public void addPowerInsteadOfToughnessForDamageLethalityFilter(UUID source, FilterCreaturePermanent filter) {
|
||||
usePowerInsteadOfToughnessForDamageLethalityFilters.put(source, filter);
|
||||
}
|
||||
|
||||
public List<FilterCreaturePermanent> getActivePowerInsteadOfToughnessForDamageLethalityFilters() {
|
||||
return usePowerInsteadOfToughnessForDamageLethalityFilters.isEmpty() ? emptyList() : getBattlefield().getAllActivePermanents().stream()
|
||||
.map(Card::getId)
|
||||
.filter(usePowerInsteadOfToughnessForDamageLethalityFilters::containsKey)
|
||||
.map(usePowerInsteadOfToughnessForDamageLethalityFilters::get)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
|
||||
package mage.game.combat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.ControllerAssignCombatDamageToBlockersAbility;
|
||||
import mage.abilities.common.ControllerDivideCombatDamageAbility;
|
||||
|
@ -19,12 +15,17 @@ import mage.abilities.keyword.TrampleAbility;
|
|||
import mage.constants.AsThoughEffectType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.filter.StaticFilters;
|
||||
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.util.Copyable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
@ -271,12 +272,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
if (blocked && canDamage(attacker, first)) {
|
||||
int damage = getDamageValueFromPermanent(attacker, game);
|
||||
if (hasTrample(attacker)) {
|
||||
int lethalDamage;
|
||||
if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) {
|
||||
lethalDamage = 1;
|
||||
} else {
|
||||
lethalDamage = Math.max(blocker.getToughness().getValue() - blocker.getDamage(), 0);
|
||||
}
|
||||
int lethalDamage = getLethalDamage(blocker, attacker, game);
|
||||
if (lethalDamage >= damage) {
|
||||
blocker.markDamage(damage, attacker.getId(), game, true, true);
|
||||
} else {
|
||||
|
@ -325,12 +321,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
for (UUID blockerId : new ArrayList<>(blockerOrder)) { // prevent ConcurrentModificationException
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
if (blocker != null) {
|
||||
int lethalDamage;
|
||||
if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) {
|
||||
lethalDamage = 1;
|
||||
} else {
|
||||
lethalDamage = Math.max(blocker.getToughness().getValue() - blocker.getDamage(), 0);
|
||||
}
|
||||
int lethalDamage = getLethalDamage(blocker, attacker, game);
|
||||
if (lethalDamage >= damage) {
|
||||
if (!oldRuleDamage) {
|
||||
assigned.put(blockerId, damage);
|
||||
|
@ -521,12 +512,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
for (UUID attackerId : attackerOrder) {
|
||||
Permanent attacker = game.getPermanent(attackerId);
|
||||
if (attacker != null) {
|
||||
int lethalDamage;
|
||||
if (blocker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) {
|
||||
lethalDamage = 1;
|
||||
} else {
|
||||
lethalDamage = Math.max(attacker.getToughness().getValue() - attacker.getDamage(), 0);
|
||||
}
|
||||
int lethalDamage = getLethalDamage(attacker, blocker, game);
|
||||
if (lethalDamage >= damage) {
|
||||
if (!oldRuleDamage) {
|
||||
assigned.put(attackerId, damage);
|
||||
|
@ -936,4 +922,29 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int getLethalDamage(Permanent blocker, Permanent attacker, Game game) {
|
||||
int lethalDamage;
|
||||
if (attacker.getAbilities().containsKey(DeathtouchAbility.getInstance().getId())) {
|
||||
lethalDamage = 1;
|
||||
} else {
|
||||
lethalDamage = getLethalDamage(blocker, game);
|
||||
}
|
||||
return lethalDamage;
|
||||
}
|
||||
|
||||
public static int getLethalDamage(Permanent damagedPermanent, Game game) {
|
||||
List<FilterCreaturePermanent> usePowerInsteadOfToughnessForDamageLethalityFilters = game.getState().getActivePowerInsteadOfToughnessForDamageLethalityFilters();
|
||||
/*
|
||||
* for handling Zilortha, Strength Incarnate:
|
||||
* 2020-04-17
|
||||
* Any time the game is checking whether damage is lethal or if a creature should be destroyed for having lethal damage marked on it, use the power of your creatures rather than their toughness to check the damage against. This includes being assigned trample damage, damage from Flame Spill, and so on.
|
||||
*/
|
||||
boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream()
|
||||
.anyMatch(filter -> filter.match(damagedPermanent, game));
|
||||
int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ?
|
||||
// Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it.
|
||||
Math.max(damagedPermanent.getPower().getValue(), 1) : damagedPermanent.getToughness().getValue();
|
||||
return Math.max(lethalDamageThreshold - damagedPermanent.getDamage(), 0);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue