Merge branch 'master' into elven-ambush

This commit is contained in:
weirddan455 2020-12-18 20:42:35 -06:00 committed by GitHub
commit cda9a49830
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 422 additions and 17 deletions

View file

@ -0,0 +1,60 @@
package mage.cards.b;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.abilities.keyword.EquipAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicates;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class BeardedAxe extends CardImpl {
private static final FilterPermanent filter = new FilterControlledPermanent();
static {
filter.add(Predicates.or(
SubType.DWARF.getPredicate(),
SubType.EQUIPMENT.getPredicate(),
SubType.VEHICLE.getPredicate()
));
}
private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter);
private static final Hint hint = new ValueHint("Dwarves, Equipment, and/or Vehicles you control", xValue);
public BeardedAxe(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{R}");
this.subtype.add(SubType.EQUIPMENT);
// Equipped creature gets +1/+1 for each Dwarf, Equipment, and/or Vehicle you control.
this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(xValue, xValue)
.setText("equipped creature gets +1/+1 for each Dwarf, Equipment, and/or Vehicle you control")
).addHint(hint));
// Equip {2}
this.addAbility(new EquipAbility(2));
}
private BeardedAxe(final BeardedAxe card) {
super(card);
}
@Override
public BeardedAxe copy() {
return new BeardedAxe(this);
}
}

View file

@ -0,0 +1,56 @@
package mage.cards.g;
import mage.MageInt;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.effects.common.ReturnSourceFromGraveyardToHandEffect;
import mage.abilities.keyword.CrewAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterControlledPermanent;
import mage.target.common.TargetControlledPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class GildedAssaultCart extends CardImpl {
private static final FilterControlledPermanent filter
= new FilterControlledPermanent(SubType.TREASURE, "treasures");
public GildedAssaultCart(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{R}{R}");
this.subtype.add(SubType.VEHICLE);
this.power = new MageInt(5);
this.toughness = new MageInt(1);
// Trample
this.addAbility(TrampleAbility.getInstance());
// Crew 2
this.addAbility(new CrewAbility(2));
// Sacrifice two Treasures: Return Gilded Assault Cart from your graveyard to your hand.
this.addAbility(new SimpleActivatedAbility(
Zone.GRAVEYARD,
new ReturnSourceFromGraveyardToHandEffect(),
new SacrificeTargetCost(new TargetControlledPermanent(2, filter))
));
}
private GildedAssaultCart(final GildedAssaultCart card) {
super(card);
}
@Override
public GildedAssaultCart copy() {
return new GildedAssaultCart(this);
}
}

View file

@ -0,0 +1,56 @@
package mage.cards.g;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.keyword.ChangelingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.NamePredicate;
import mage.filter.predicate.permanent.AnotherPredicate;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class GladewalkerRitualist extends CardImpl {
private static final FilterPermanent filter
= new FilterCreaturePermanent("another creature named Gladewalker Ritualist");
static {
filter.add(AnotherPredicate.instance);
filter.add(new NamePredicate("Gladewalker Ritualist"));
}
public GladewalkerRitualist(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}");
this.subtype.add(SubType.SHAPESHIFTER);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Changeling
this.setIsAllCreatureTypes(true);
this.addAbility(ChangelingAbility.getInstance());
// Whenever another creature named Gladewalker Ritualist enters the battlefield under your control, draw a card.
this.addAbility(new EntersBattlefieldControlledTriggeredAbility(
new DrawCardSourceControllerEffect(1), filter
));
}
private GladewalkerRitualist(final GladewalkerRitualist card) {
super(card);
}
@Override
public GladewalkerRitualist copy() {
return new GladewalkerRitualist(this);
}
}

View file

@ -0,0 +1,78 @@
package mage.cards.r;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class RenegadeReaper extends CardImpl {
public RenegadeReaper(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}");
this.subtype.add(SubType.ANGEL);
this.subtype.add(SubType.BERSERKER);
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// Flying
this.addAbility(FlyingAbility.getInstance());
// When Renegade Reaper enters the battlefield, mill four cards. If at least one Angel card is milled this way, you gain 4 life.
this.addAbility(new EntersBattlefieldTriggeredAbility(new RenegadeReaperEffect()));
}
private RenegadeReaper(final RenegadeReaper card) {
super(card);
}
@Override
public RenegadeReaper copy() {
return new RenegadeReaper(this);
}
}
class RenegadeReaperEffect extends OneShotEffect {
RenegadeReaperEffect() {
super(Outcome.Benefit);
staticText = "mill four cards. If at least one Angel card is milled this way, you gain 4 life";
}
private RenegadeReaperEffect(final RenegadeReaperEffect effect) {
super(effect);
}
@Override
public RenegadeReaperEffect copy() {
return new RenegadeReaperEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
if (player.millCards(4, source, game)
.getCards(game)
.stream()
.anyMatch(card -> card.hasSubtype(SubType.ANGEL, game))) {
player.gainLife(4, game, source);
}
return true;
}
}

View file

@ -29,11 +29,15 @@ public final class Kaldheim extends ExpansionSet {
this.maxCardNumberInBooster = 285;
cards.add(new SetCardInfo("Barkchannel Pathway", 251, Rarity.RARE, mage.cards.b.BarkchannelPathway.class));
cards.add(new SetCardInfo("Bearded Axe", 388, Rarity.UNCOMMON, mage.cards.b.BeardedAxe.class));
cards.add(new SetCardInfo("Blightstep Pathway", 252, Rarity.RARE, mage.cards.b.BlightstepPathway.class));
cards.add(new SetCardInfo("Canopy Tactician", 378, Rarity.RARE, mage.cards.c.CanopyTactician.class));
cards.add(new SetCardInfo("Darkbore Pathway", 254, Rarity.RARE, mage.cards.d.DarkborePathway.class));
cards.add(new SetCardInfo("Elven Ambush", 391, Rarity.UNCOMMON, mage.cards.e.ElvenAmbush.class));
cards.add(new SetCardInfo("Gilded Assault Cart", 390, Rarity.UNCOMMON, mage.cards.g.GildedAssaultCart.class));
cards.add(new SetCardInfo("Gladewalker Ritualist", 392, Rarity.UNCOMMON, mage.cards.g.GladewalkerRitualist.class));
cards.add(new SetCardInfo("Hengegate Pathway", 260, Rarity.RARE, mage.cards.h.HengegatePathway.class));
cards.add(new SetCardInfo("Renegade Reaper", 386, Rarity.UNCOMMON, mage.cards.r.RenegadeReaper.class));
cards.add(new SetCardInfo("Showdown of the Skalds", 229, Rarity.RARE, mage.cards.s.ShowdownOfTheSkalds.class));
}
}

View file

@ -0,0 +1,73 @@
package org.mage.test.cards.single.gnt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.AttackedThisTurnOpponentsCount;
import mage.abilities.effects.Effect;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import mage.watchers.common.PlayersAttackedThisTurnWatcher;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestCommander4Players;
/**
* @author JayDi85
*/
public class MilitantAngelTest extends CardTestCommander4Players {
@Test
public void test_AttackedThisTurnOpponentsCount() {
// Player order: A -> D -> C -> B
// it's testing counter only (no need to test card -- it's same)
// When Militant Angel enters the battlefield, create a number of 2/2 white Knight creature tokens
// with vigilance equal to the number of opponents you attacked this turn.
//addCard(Zone.BATTLEFIELD, playerA, "Militant Angel", 2);
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
addCard(Zone.BATTLEFIELD, playerA, "Kitesail Corsair", 1);
// turn 1: 2x attack -> 1 player = 1 value
mustHaveValue("2x->1 = 1", 1, PhaseStep.PRECOMBAT_MAIN, 0);
attack(1, playerA, "Balduvian Bears", playerB);
attack(1, playerA, "Kitesail Corsair", playerB);
mustHaveValue("2x->1 = 1", 1, PhaseStep.POSTCOMBAT_MAIN, 1);
// between attacks - no value
mustHaveValue("no attacks = 0", 2, PhaseStep.PRECOMBAT_MAIN, 0);
// turn 5: 2x attack -> 2 players = 2 value
mustHaveValue("2x->2 = 2", 5, PhaseStep.PRECOMBAT_MAIN, 0);
attack(5, playerA, "Balduvian Bears", playerB);
attack(5, playerA, "Kitesail Corsair", playerC);
mustHaveValue("2x->2 = 2", 5, PhaseStep.POSTCOMBAT_MAIN, 2);
runCode("watcher must be copyable", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, (info, player, game) -> {
PlayersAttackedThisTurnWatcher watcher = game.getState().getWatcher(PlayersAttackedThisTurnWatcher.class);
PlayersAttackedThisTurnWatcher newWatcher = watcher.copy();
Assert.assertEquals("old watcher", 2, watcher.getAttackedOpponentsCount(player.getId()));
Assert.assertEquals("new watcher", 2, newWatcher.getAttackedOpponentsCount(player.getId()));
});
// between attacks - no value
mustHaveValue("no attacks = 0", 6, PhaseStep.PRECOMBAT_MAIN, 0);
setStrictChooseMode(true);
setStopAt(6, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
private void mustHaveValue(String needInfo, int turnNum, PhaseStep step, int needValue) {
runCode(needInfo, turnNum, step, playerA, (info, player, game) -> {
assertCounterValue(info, player, game, needValue);
});
}
private void assertCounterValue(String checkName, Player player, Game game, int needValue) {
Ability fakeAbility = new SimpleStaticAbility((Effect) null); // dynamic value need ability's controllerId
fakeAbility.setControllerId(player.getId());
Assert.assertEquals(checkName, needValue, AttackedThisTurnOpponentsCount.instance.calculate(game, fakeAbility, null));
}
}

View file

@ -2,6 +2,7 @@ package org.mage.test.player;
import mage.constants.PhaseStep;
import mage.game.Game;
import org.mage.test.serverside.base.CardTestCodePayload;
/**
* @author BetaSteward_at_googlemail.com
@ -12,12 +13,18 @@ public class PlayerAction {
private final int turnNum;
private final PhaseStep step;
private final String action;
private final CardTestCodePayload codePayload; // special code to execute (e.g. on dynamic check)
public PlayerAction(String actionName, int turnNum, PhaseStep step, String action) {
this(actionName, turnNum, step, action, null);
}
public PlayerAction(String actionName, int turnNum, PhaseStep step, String action, CardTestCodePayload codePayload) {
this.actionName = actionName;
this.turnNum = turnNum;
this.step = step;
this.action = action;
this.codePayload = codePayload;
}
public int getTurnNum() {
@ -33,7 +40,11 @@ public class PlayerAction {
}
public String getActionName() {
return this.actionName;
return actionName;
}
public CardTestCodePayload getCodePayload() {
return codePayload;
}
/**

View file

@ -738,6 +738,18 @@ public class TestPlayer implements Player {
}
Assert.fail("Unknow ai command: " + command);
} else if (action.getAction().startsWith(RUN_PREFIX)) {
String command = action.getAction();
command = command.substring(command.indexOf(RUN_PREFIX) + RUN_PREFIX.length());
// custom code execute
if (command.equals(RUN_COMMAND_CODE)) {
action.getCodePayload().run(action.getActionName(), computerPlayer, game);
actions.remove(action);
return true;
}
Assert.fail("Unknow run command: " + command);
} else if (action.getAction().startsWith(CHECK_PREFIX)) {
String command = action.getAction();
command = command.substring(command.indexOf(CHECK_PREFIX) + CHECK_PREFIX.length());

View file

@ -0,0 +1,21 @@
package org.mage.test.serverside.base;
import mage.game.Game;
import mage.players.Player;
/**
* @author JayDi85
*/
@FunctionalInterface
public interface CardTestCodePayload {
/**
* Run dynamic code in unit tests on player's priority.
*
* @param info
* @param player activate player who would execute the code on their priority
* @param game
*/
void run(String info, Player player, Game game);
}

View file

@ -34,6 +34,7 @@ import org.junit.Before;
import org.mage.test.player.PlayerAction;
import org.mage.test.player.TestPlayer;
import org.mage.test.serverside.base.CardTestAPI;
import org.mage.test.serverside.base.CardTestCodePayload;
import org.mage.test.serverside.base.MageTestPlayerBase;
import java.io.FileNotFoundException;
@ -57,6 +58,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
public static final String CHECK_PREFIX = "check:"; // prefix for all check commands
public static final String SHOW_PREFIX = "show:"; // prefix for all show commands
public static final String AI_PREFIX = "ai:"; // prefix for all ai commands
public static final String RUN_PREFIX = "run:"; // prefix for all run commands
static {
// aliases can be used in check commands, so all prefixes and delimeters must be unique
@ -77,6 +79,9 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
public static final String AI_COMMAND_PLAY_PRIORITY = "play priority";
public static final String AI_COMMAND_PLAY_STEP = "play step";
// commands for run
public static final String RUN_COMMAND_CODE = "code";
static {
// cards can be played/casted by activate ability command too
Assert.assertTrue("musts contains activate ability part", ACTIVATE_PLAY.startsWith(ACTIVATE_ABILITY));
@ -375,6 +380,24 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
addPlayerAction(player, checkName, turnNum, step, res);
}
/**
* Execute custom code under player's priority. You can stop debugger on it.
* <p>
* Example 1: check some conditions in the middle of the test
* Example 2: make game modifications (if you don't want to use custom abilities)
* Example 3: stop debugger in the middle of the game
*
* @param info
* @param turnNum
* @param step
* @param player
* @param codePayload code to execute
*/
public void runCode(String info, int turnNum, PhaseStep step, TestPlayer player, CardTestCodePayload codePayload) {
PlayerAction playerAction = new PlayerAction(info, turnNum, step, RUN_PREFIX + RUN_COMMAND_CODE, codePayload);
addPlayerAction(player, playerAction);
}
public void checkPT(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer power, Integer toughness) {
//Assert.assertNotEquals("", permanentName);
check(checkName, turnNum, step, player, CHECK_COMMAND_PT, permanentName, power.toString(), toughness.toString());

View file

@ -1,12 +1,14 @@
package mage.watchers;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.PlayerList;
import org.apache.log4j.Logger;
import java.io.Serializable;
import java.lang.reflect.*;
import java.util.*;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
import org.apache.log4j.Logger;
/**
* watches for certain game events to occur and flags condition
@ -123,6 +125,14 @@ public abstract class Watcher implements Serializable {
set.addAll(e.getValue());
target.put(e.getKey(), set);
}
} else if (valueType.getTypeName().contains("PlayerList")) {
Map<Object, PlayerList> source = (Map<Object, PlayerList>) field.get(this);
Map<Object, PlayerList> target = (Map<Object, PlayerList>) field.get(watcher);
target.clear();
for (Map.Entry<Object, PlayerList> e : source.entrySet()) {
PlayerList list = e.getValue().copy();
target.put(e.getKey(), list);
}
} else if (valueType.getTypeName().contains("List")) {
Map<Object, List<Object>> source = (Map<Object, List<Object>>) field.get(this);
Map<Object, List<Object>> target = (Map<Object, List<Object>>) field.get(watcher);
@ -141,14 +151,12 @@ public abstract class Watcher implements Serializable {
map.putAll(e.getValue());
target.put(e.getKey(), map);
}
} else {
((Map) field.get(watcher)).putAll((Map) field.get(this));
}
} else if (field.getType() == List.class) {
((List) field.get(watcher)).clear();
((List) field.get(watcher)).addAll((List) field.get(this));
} else {
field.set(watcher, field.get(this));
}

View file

@ -40019,11 +40019,14 @@ Vault of Champions|Commander Legends|715|R||Land|||Vault of Champions enters the
War Room|Commander Legends|716|R||Land|||{T}: Add {C}.${3}, {T}, Pay life equal to the number of colors in your commanders' color identity: Draw a card.|
Mana Confluence|Commander Legends|721|M||Land|||{T}, Pay 1 life: Add one mana of any color.|
Sengir, the Dark Baron|Commander Legends|722|R|{4}{B}{B}|Legendary Creature - Vampire Noble|4|4|Flying$Whenever another creature dies, put two +1/+1 counters on Sengir, the Dark Baron.$Whenever another player loses the game, you gain life equal to that player's life total as the turn began.$Partner|
Halvar, God of Battle|Kaldheim|15|M|{2}{W}{W}|Legendary Creature - God|4|4|Creatures you control that are enchanted or equipped have double strike.$At the beginning of each combat, you may attach target Aura or Equipment attached to a creature you control to target creature you control.|
Sword of the Realms|Kaldheim|15|M|{1}{W}|Legendary Artifact - Equipment|||Equipped creature gets +2/+0 and has vigilance.$Whenever equipped creature dies, return it to its owner's hand.$Equip {1}{W}|
Magda, Brazen Outlaw|Kaldheim|142|R|{1}{R}|Legendary Creature - Dwarf Berserker|2|1|Other Dwarves you control get +1/+0.$Whenever a Dwarf you control becomes tapped, create a Treasure token.$Sacrifice five Treasures: Search your library for an artifact or Dragon card, put that card onto the battlefield, then shuffle your library.|
Realmwalker|Kaldheim|188|R|{2}{G}|Creature - Shapeshifter|2|3|Changeling$As Realmwalker enters the battlefield, choose a creature type.$You may look at the top card of your library any time.$You may cast creature spells of the chose type from the top of your library.|
Realmwalker|Kaldheim|188|R|{2}{G}|Creature - Shapeshifter|2|3|Changeling$As Realmwalker enters the battlefield, choose a creature type.$You may look at the top card of your library any time.$You may cast creature spells of the chosen type from the top of your library.|
Kaya the Inexorable|Kaldheim|218|M|{3}{W}{B}|Legendary Planeswalker - Kaya|5|+1: Put a ghostform counter on up to one target nontoken creature. It gains "When this creature dies or is put into exile, return it to its owner's hand and create a 1/1 white Spirit creature token with flying."$3: Exile target nonland permanent.$7: You get an emblem with "At the beginning of your upkeep, you may cast a legendary spell from your hand, from your graveyard, or from among cards you own in exile without paying its mana cost."|
Sarulf, Realm Eater|Kaldheim|228|R|{1}{B}{G}|Legendary Creature - Wolf|3|3|Whenever a permanent an opponent controls is put into a graveyard from the battlefield, put a +1/+1 counter on Sarulf, Realm Eater.$At the beginning of your upkeep, if Sarulf has one or more +1/+1 counters on it, you may remove all of them. If you do, exile each other nonland permanent with converted mana cost less than or equal to the number of counters removed this way.|
Showdown of the Skalds|Kaldheim|229|R|{2}{R}{W}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I — Exile the top four cards of your library. Until the end of your next turn, you may play those cards.$II, III — Whenever you cast a spell this turn, put a +1/+1 counter on target creature you control.|
Pyre of Heroes|Kaldheim|241|R|{2}|Artifact|||{2}, {T}, Sacrifice a creature: Search your library for a creature card that shares a creature type with the sacrificed creature and has converted mana cost equal to one 1 plus that creature's converted mana cost. Put that card onto the battlefield, then shuffle your library. Activate this ability only any time you could cast a sorcery.|
Pyre of Heroes|Kaldheim|241|R|{2}|Artifact|||{2}, {T}, Sacrifice a creature: Search your library for a creature card that shares a creature type with the sacrificed creature and has converted mana cost equal to 1 plus that creature's converted mana cost. Put that card onto the battlefield, then shuffle your library. Activate this ability only any time you could cast a sorcery.|
Barkchannel Pathway|Kaldheim|251|R||Land|||{T}: Add {G}.|
Tidechannel Pathway|Kaldheim|251|R||Land|||{T}: Add {U}.|
Blightstep Pathway|Kaldheim|252|R||Land|||{T}: Add {B}.|
@ -40032,19 +40035,19 @@ Darkbore Pathway|Kaldheim|254|R||Land|||{T}: Add {B}.|
Slitherbore Pathway|Kaldheim|254|R||Land|||{T}: Add {G}.|
Hengegate Pathway|Kaldheim|260|R||Land|||{T}: Add {W}.|
Mistgate Pathway|Kaldheim|260|R||Land|||{T}: Add {U}.|
Valkyrie Harbinger|Kaldheim|374|R|{4}{W}{W}|Creature - Angel Cleric|4|5|Flying, lifelink$At the beginning of each end step, if you gain 4 or more life this turn, create a 4/4 white Angel creature token with flying and vigilance.|
Surtland Elementalist|Kaldheim|375|R|{5}{U}{U}|Creature - Giant Wizard|8|8|As an additional cost to cast this spell, reveal Giant card from your hand or pay {2}.$Whenever Surtland Elementalist attacks, you may cast an instant or sorcery spell from your hand without paying its mana cost.|
Valkyrie Harbinger|Kaldheim|374|R|{4}{W}{W}|Creature - Angel Cleric|4|5|Flying, lifelink$At the beginning of each end step, if you gained 4 or more life this turn, create a 4/4 white Angel creature token with flying and vigilance.|
Surtland Elementalist|Kaldheim|375|R|{5}{U}{U}|Creature - Giant Wizard|8|8|As an additional cost to cast this spell, reveal a Giant card from your hand or pay {2}.$Whenever Surtland Elementalist attacks, you may cast an instant or sorcery spell from your hand without paying its mana cost.|
Cleaving Reaper|Kaldheim|376|R|{3}{B}{B}|Creature - Angel Berserker|5|3|Flying, trample$Pay 3 life: Return Cleaving Reaper from your graveyard to your hand. Activate this ability only if you had an Angel or Berserker enter the battlefield under your control this turn.|
Surtland Flinger|Kaldheim|377|R|{3}{R}{R}|Creature - Giant Berserker|||Whenever Surtland Flinger attacks, you may sacrifice another creature. When you do, Surtland Flinger deals damage equal to the sacrificed creature's power to any target. If the sacrificed creature was a Giant, Surtland Flinger deals twice that much damage instead.|
Canopy Tactician|Kaldheim|378|R|{3}{G}|Creature - Elf Warrior|3|3|Other elves you control get +1/+1.${T}: Add {G}{G}{G}.|
Surtland Flinger|Kaldheim|377|R|{3}{R}{R}|Creature - Giant Berserker|4|6|Whenever Surtland Flinger attacks, you may sacrifice another creature. When you do, Surtland Flinger deals damage equal to the sacrificed creature's power to any target. If the sacrificed creature was a Giant, Surtland Flinger deals twice that much damage instead.|
Canopy Tactician|Kaldheim|378|R|{3}{G}|Creature - Elf Warrior|3|3|Other Elves you control get +1/+1.${T}: Add {G}{G}{G}.|
Armed and Armored|Kaldheim|379|U|{1}{W}|Instant|||Vehicles you control become artifact creatures until end of turn. Choose a Dwarf you control. Attach any number of Equipment you control to it.|
Starnhiem Aspirant|Kaldheim|380|U|{2}{W}|Creature - Human Cleric|2|2|Angel spells you cast cost {2} less to cast.|
Warchanter Skald|Kaldheim|381|U|{2}{W}|Creature - Dwarf Cleric|1|3|Whenever Warchanter Skald becomes tapped, if it's enchanted or equipped, create a 2/1 red Dwarf Berserker creature token.|
Youthful Valyrie|Kaldheim|382|U|{1}{W}|Creature - Angel|1|3|Flying$Whenever another Angel enters the battlefield under your control, put a +1/+1 counter on Youthful Valyrie.|
Starnheim Aspirant|Kaldheim|380|U|{2}{W}|Creature - Human Cleric|2|2|Angel spells you cast cost {2} less to cast.|
Warchanter Skald|Kaldheim|381|U|{2}{W}|Creature - Dwarf Cleric|2|3|Whenever Warchanter Skald becomes tapped, if it's enchanted or equipped, create a 2/1 red Dwarf Berserker creature token.|
Youthful Valkyrie|Kaldheim|382|U|{1}{W}|Creature - Angel|1|3|Flying$Whenever another Angel enters the battlefield under your control, put a +1/+1 counter on Youthful Valyrie.|
Absorb Identity|Kaldheim|383|U|{1}{U}|Instant|||Return target creature to its owner's hand. You may have Shapeshifters you control become copies of that creature until end of turn.|
Giant's Grasp|Kaldheim|384|U|{2}{U}{U}|Enchantment - Aura|||Enchant Giant you control$When Giant's Grasp enters the battlefield, gain control of target nonland permanent for as long as Giant's Grasp remains on the battlefield.|
Elderfang Ritualist|Kaldheim|385|U|{2}{B}|Creature - Elf Cleric|3|1|When Elderfang Ritualist dies, return another target Elf card from your graveyard to your hand.|
Renegade Reaper|Kaldheim|386|U|{2}{B}|Creature - Angel Berserker|2|3|Flying$When Renegade Reaper enters the battlefield, mill four cards. If at least one Angel is milled this way, you gain 4 life.|
Renegade Reaper|Kaldheim|386|U|{2}{B}|Creature - Angel Berserker|2|3|Flying$When Renegade Reaper enters the battlefield, mill four cards. If at least one Angel card is milled this way, you gain 4 life.|
Thornmantle Striker|Kaldheim|387|U|{4}{B}|Creature - Elf Rogue|4|3|When Thornmantle Striker enters the battlefield, choose one —$• Remove X counters from target permanent, where X is the number of Elves you control.$• Target creature an opponent controls gets -X/-X until end of turn, where X is the number of Elves you control.|
Bearded Axe|Kaldheim|388|U|{2}{R}|Artifact - Equipment|||Equipped creature gets +1/+1 for each Dwarf, Equipment, and/or Vehicle you control.$Equip {2}|
Fire Giant's Fury|Kaldheim|389|U|{1}{R}|Sorcery|||Target Giant you control gets +2/+2 and gains trample until end of turn. Whenever it deals combat damage to a player this turn, exile that many cards from the top of your library. Until the end of your next turn, you may play those cards.|