[AFR] Implementing Class enchantments (ready for review) (#7992)

* [AFR] Implemented Druid Class

* [AFR] Implemented Wizard Class

* [AFR] Implemented Cleric Class

* [AFR] Implemented Fighter Class

* reworked class ability implementation

* fixed an error with setting class level

* small reworking of class triggers

* added class level hint

* added tests

* small change

* added common class for reminder text
This commit is contained in:
Evan Kranzler 2021-07-14 09:17:07 -04:00 committed by GitHub
parent d00765c2d5
commit 5b88484cb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 884 additions and 5 deletions

View file

@ -0,0 +1,141 @@
package mage.cards.c;
import mage.abilities.Ability;
import mage.abilities.common.BecomesClassLevelTriggeredAbility;
import mage.abilities.common.GainLifeControllerTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.ClassLevelAbility;
import mage.abilities.keyword.ClassReminderAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class ClericClass extends CardImpl {
public ClericClass(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{W}");
this.subtype.add(SubType.CLASS);
// (Gain the next level as a sorcery to add its ability.)
this.addAbility(new ClassReminderAbility());
// If you would gain life, you gain that much life plus 1 instead.
this.addAbility(new SimpleStaticAbility(new ClericClassLifeEffect()));
// {3}{W}: Level 2
this.addAbility(new ClassLevelAbility(2, "{3}{W}"));
// Whenever you gain life, put a +1/+1 counter on target creature you control.
Ability ability = new GainLifeControllerTriggeredAbility(
new AddCountersTargetEffect(CounterType.P1P1.createInstance())
);
ability.addTarget(new TargetControlledCreaturePermanent());
this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 2)));
// {4}{W}: Level 3
this.addAbility(new ClassLevelAbility(3, "{4}{W}"));
// When this Class becomes level 3, return target creature card from your graveyard to the battlefield. You gain life equal to its toughness.
ability = new BecomesClassLevelTriggeredAbility(new ClericClassReturnEffect(), 3);
ability.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD));
this.addAbility(ability);
}
private ClericClass(final ClericClass card) {
super(card);
}
@Override
public ClericClass copy() {
return new ClericClass(this);
}
}
class ClericClassLifeEffect extends ReplacementEffectImpl {
ClericClassLifeEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "If you would gain life, you gain that much life plus 1 instead";
}
private ClericClassLifeEffect(final ClericClassLifeEffect effect) {
super(effect);
}
@Override
public ClericClassLifeEffect copy() {
return new ClericClassLifeEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
event.setAmount(event.getAmount() + 1);
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.GAIN_LIFE;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return source.isControlledBy(event.getPlayerId());
}
}
class ClericClassReturnEffect extends OneShotEffect {
ClericClassReturnEffect() {
super(Outcome.Benefit);
staticText = "return target creature card from your graveyard to the battlefield. " +
"You gain life equal to its toughness";
}
private ClericClassReturnEffect(final ClericClassReturnEffect effect) {
super(effect);
}
@Override
public ClericClassReturnEffect copy() {
return new ClericClassReturnEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Card card = game.getCard(source.getFirstTarget());
if (player == null || card == null) {
return false;
}
player.moveCards(card, Zone.BATTLEFIELD, source, game);
Permanent permanent = game.getPermanent(card.getId());
int toughness = permanent != null ? permanent.getToughness().getValue() : card.getToughness().getValue();
player.gainLife(toughness, game, source);
return true;
}
}

View file

@ -0,0 +1,97 @@
package mage.cards.d;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.BecomesClassLevelTriggeredAbility;
import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.LandsYouControlCount;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect;
import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect;
import mage.abilities.effects.common.continuous.PlayAdditionalLandsControllerEffect;
import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect;
import mage.abilities.keyword.ClassLevelAbility;
import mage.abilities.keyword.ClassReminderAbility;
import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubLayer;
import mage.constants.SubType;
import mage.filter.StaticFilters;
import mage.game.permanent.token.TokenImpl;
import mage.target.TargetPermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class DruidClass extends CardImpl {
public DruidClass(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{G}");
this.subtype.add(SubType.CLASS);
// (Gain the next level as a sorcery to add its ability.)
this.addAbility(new ClassReminderAbility());
// Whenever a land enters the battlefield under your control, you gain 1 life.
this.addAbility(new EntersBattlefieldControlledTriggeredAbility(
new GainLifeEffect(1), StaticFilters.FILTER_CONTROLLED_LAND_SHORT_TEXT
));
// {2}{G}: Level 2
this.addAbility(new ClassLevelAbility(2, "{2}{G}"));
// You may play an additional land on each of your turns.
this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(
new PlayAdditionalLandsControllerEffect(1, Duration.WhileOnBattlefield), 2
)));
// {4}{G}: Level 3
this.addAbility(new ClassLevelAbility(3, "{4}{G}"));
// When this Class becomes level 3, target land you control becomes a creature with haste and "This creature's power and toughness are each equal to the number of lands you control." It's still a land.
Ability ability = new BecomesClassLevelTriggeredAbility(new BecomesCreatureTargetEffect(
new DruidClassToken(), false, true, Duration.Custom
), 3);
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND));
this.addAbility(ability);
}
private DruidClass(final DruidClass card) {
super(card);
}
@Override
public DruidClass copy() {
return new DruidClass(this);
}
}
class DruidClassToken extends TokenImpl {
DruidClassToken() {
super("", "creature with haste and \"This creature's power and toughness are each equal to the number of lands you control.\"");
this.cardType.add(CardType.CREATURE);
this.power = new MageInt(0);
this.toughness = new MageInt(0);
this.addAbility(HasteAbility.getInstance());
this.addAbility(new SimpleStaticAbility(new SetPowerToughnessSourceEffect(
LandsYouControlCount.instance, Duration.EndOfGame, SubLayer.SetPT_7b
).setText("this creature's power and toughness are each equal to the number of lands you control")));
}
private DruidClassToken(final DruidClassToken token) {
super(token);
}
public DruidClassToken copy() {
return new DruidClassToken(this);
}
}

View file

@ -0,0 +1,161 @@
package mage.cards.f;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.AttacksCreatureYouControlTriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.RequirementEffect;
import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect;
import mage.abilities.effects.common.cost.AbilitiesCostReductionControllerEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.abilities.keyword.ClassLevelAbility;
import mage.abilities.keyword.ClassReminderAbility;
import mage.abilities.keyword.EquipAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetCardInLibrary;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class FighterClass extends CardImpl {
private static final FilterCard filter = new FilterCard("an Equipment card");
static {
filter.add(SubType.EQUIPMENT.getPredicate());
}
public FighterClass(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{R}{W}");
this.subtype.add(SubType.CLASS);
// (Gain the next level as a sorcery to add its ability.)
this.addAbility(new ClassReminderAbility());
// When Fighter Class enters the battlefield, search your library for an Equipment card, reveal it, put it into your hand, then shuffle.
this.addAbility(new EntersBattlefieldTriggeredAbility(
new SearchLibraryPutInHandEffect(new TargetCardInLibrary(filter))
));
// {1}{R}{W}: Level 2
this.addAbility(new ClassLevelAbility(2, "{1}{R}{W}"));
// Equip abilities you activate cost {2} less to activate.
this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(
new AbilitiesCostReductionControllerEffect(EquipAbility.class, "Equip")
.setText("\"equip abilities you activate cost {2} less to activate\""),
2
)));
// {3}{R}{W}: Level 3
this.addAbility(new ClassLevelAbility(3, "{3}{R}{W}"));
// Whenever a creature you control attacks, up to one target creature blocks it this combat if able.
Ability ability = new AttacksCreatureYouControlTriggeredAbility(new FighterClassEffect(), false);
ability.addTarget(new TargetCreaturePermanent(0, 1));
this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 3)));
}
private FighterClass(final FighterClass card) {
super(card);
}
@Override
public FighterClass copy() {
return new FighterClass(this);
}
}
class FighterClassEffect extends OneShotEffect {
FighterClassEffect() {
super(Outcome.Benefit);
staticText = "up to one target creature blocks it this combat if able";
}
private FighterClassEffect(final FighterClassEffect effect) {
super(effect);
}
@Override
public FighterClassEffect copy() {
return new FighterClassEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
MageObjectReference attackerRef = (MageObjectReference) getValue("attackerRef");
if (attackerRef == null) {
return false;
}
Permanent attacker = attackerRef.getPermanent(game);
Permanent permanent = game.getPermanent(source.getFirstTarget());
if (attacker == null || permanent == null) {
return false;
}
game.addEffect(new FighterClassRequirementEffect(attackerRef).setTargetPointer(new FixedTarget(permanent, game)), source);
return true;
}
}
class FighterClassRequirementEffect extends RequirementEffect {
private final MageObjectReference mor;
public FighterClassRequirementEffect(MageObjectReference mor) {
super(Duration.EndOfCombat);
this.mor = mor;
}
public FighterClassRequirementEffect(final FighterClassRequirementEffect effect) {
super(effect);
this.mor = effect.mor;
}
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
Permanent attacker = mor.getPermanent(game);
if (attacker == null) {
discard();
return false;
}
return permanent != null
&& permanent.getId().equals(this.getTargetPointer().getFirst(game, source))
&& permanent.canBlock(source.getSourceId(), game);
}
@Override
public boolean mustAttack(Game game) {
return false;
}
@Override
public boolean mustBlock(Game game) {
return true;
}
@Override
public UUID mustBlockAttacker(Ability source, Game game) {
return mor.getSourceId();
}
@Override
public FighterClassRequirementEffect copy() {
return new FighterClassRequirementEffect(this);
}
}

View file

@ -0,0 +1,67 @@
package mage.cards.w;
import mage.abilities.Ability;
import mage.abilities.common.BecomesClassLevelTriggeredAbility;
import mage.abilities.common.DrawCardControllerTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.continuous.GainClassAbilitySourceEffect;
import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.ClassLevelAbility;
import mage.abilities.keyword.ClassReminderAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.counters.CounterType;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class WizardClass extends CardImpl {
public WizardClass(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{U}");
this.subtype.add(SubType.CLASS);
// (Gain the next level as a sorcery to add its ability.)
this.addAbility(new ClassReminderAbility());
// You have no maximum hand size.
this.addAbility(new SimpleStaticAbility(new MaximumHandSizeControllerEffect(
Integer.MAX_VALUE, Duration.WhileOnBattlefield,
MaximumHandSizeControllerEffect.HandSizeModification.SET
)));
// {2}{U}: Level 2
this.addAbility(new ClassLevelAbility(2, "{2}{U}"));
// When this Class becomes level 2, draw two cards.
this.addAbility(new BecomesClassLevelTriggeredAbility(new DrawCardSourceControllerEffect(2), 2));
// {4}{U}: Level 3
this.addAbility(new ClassLevelAbility(3, "{4}{U}"));
// Whenever you draw a card, put a +1/+1 counter on target creature you control.
Ability ability = new DrawCardControllerTriggeredAbility(
new AddCountersTargetEffect(CounterType.P1P1.createInstance()), false
);
ability.addTarget(new TargetControlledCreaturePermanent());
this.addAbility(new SimpleStaticAbility(new GainClassAbilitySourceEffect(ability, 3)));
}
private WizardClass(final WizardClass card) {
super(card);
}
@Override
public WizardClass copy() {
return new WizardClass(this);
}
}

View file

@ -55,6 +55,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet {
cards.add(new SetCardInfo("Circle of Dreams Druid", 176, Rarity.RARE, mage.cards.c.CircleOfDreamsDruid.class)); cards.add(new SetCardInfo("Circle of Dreams Druid", 176, Rarity.RARE, mage.cards.c.CircleOfDreamsDruid.class));
cards.add(new SetCardInfo("Circle of the Moon Druid", 177, Rarity.COMMON, mage.cards.c.CircleOfTheMoonDruid.class)); cards.add(new SetCardInfo("Circle of the Moon Druid", 177, Rarity.COMMON, mage.cards.c.CircleOfTheMoonDruid.class));
cards.add(new SetCardInfo("Clattering Skeletons", 93, Rarity.COMMON, mage.cards.c.ClatteringSkeletons.class)); cards.add(new SetCardInfo("Clattering Skeletons", 93, Rarity.COMMON, mage.cards.c.ClatteringSkeletons.class));
cards.add(new SetCardInfo("Cleric Class", 6, Rarity.UNCOMMON, mage.cards.c.ClericClass.class));
cards.add(new SetCardInfo("Clever Conjurer", 51, Rarity.COMMON, mage.cards.c.CleverConjurer.class)); cards.add(new SetCardInfo("Clever Conjurer", 51, Rarity.COMMON, mage.cards.c.CleverConjurer.class));
cards.add(new SetCardInfo("Cloister Gargoyle", 7, Rarity.UNCOMMON, mage.cards.c.CloisterGargoyle.class)); cards.add(new SetCardInfo("Cloister Gargoyle", 7, Rarity.UNCOMMON, mage.cards.c.CloisterGargoyle.class));
cards.add(new SetCardInfo("Compelled Duel", 178, Rarity.COMMON, mage.cards.c.CompelledDuel.class)); cards.add(new SetCardInfo("Compelled Duel", 178, Rarity.COMMON, mage.cards.c.CompelledDuel.class));
@ -77,6 +78,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet {
cards.add(new SetCardInfo("Dragon's Fire", 139, Rarity.COMMON, mage.cards.d.DragonsFire.class)); cards.add(new SetCardInfo("Dragon's Fire", 139, Rarity.COMMON, mage.cards.d.DragonsFire.class));
cards.add(new SetCardInfo("Drider", 98, Rarity.UNCOMMON, mage.cards.d.Drider.class)); cards.add(new SetCardInfo("Drider", 98, Rarity.UNCOMMON, mage.cards.d.Drider.class));
cards.add(new SetCardInfo("Drizzt Do'Urden", 220, Rarity.RARE, mage.cards.d.DrizztDoUrden.class)); cards.add(new SetCardInfo("Drizzt Do'Urden", 220, Rarity.RARE, mage.cards.d.DrizztDoUrden.class));
cards.add(new SetCardInfo("Druid Class", 180, Rarity.UNCOMMON, mage.cards.d.DruidClass.class));
cards.add(new SetCardInfo("Dueling Rapier", 140, Rarity.COMMON, mage.cards.d.DuelingRapier.class)); cards.add(new SetCardInfo("Dueling Rapier", 140, Rarity.COMMON, mage.cards.d.DuelingRapier.class));
cards.add(new SetCardInfo("Dungeon Crawler", 99, Rarity.UNCOMMON, mage.cards.d.DungeonCrawler.class)); cards.add(new SetCardInfo("Dungeon Crawler", 99, Rarity.UNCOMMON, mage.cards.d.DungeonCrawler.class));
cards.add(new SetCardInfo("Dungeon Descent", 255, Rarity.RARE, mage.cards.d.DungeonDescent.class)); cards.add(new SetCardInfo("Dungeon Descent", 255, Rarity.RARE, mage.cards.d.DungeonDescent.class));
@ -94,6 +96,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet {
cards.add(new SetCardInfo("Fates' Reversal", 102, Rarity.COMMON, mage.cards.f.FatesReversal.class)); cards.add(new SetCardInfo("Fates' Reversal", 102, Rarity.COMMON, mage.cards.f.FatesReversal.class));
cards.add(new SetCardInfo("Feign Death", 103, Rarity.COMMON, mage.cards.f.FeignDeath.class)); cards.add(new SetCardInfo("Feign Death", 103, Rarity.COMMON, mage.cards.f.FeignDeath.class));
cards.add(new SetCardInfo("Fifty Feet of Rope", 244, Rarity.UNCOMMON, mage.cards.f.FiftyFeetOfRope.class)); cards.add(new SetCardInfo("Fifty Feet of Rope", 244, Rarity.UNCOMMON, mage.cards.f.FiftyFeetOfRope.class));
cards.add(new SetCardInfo("Fighter Class", 222, Rarity.RARE, mage.cards.f.FighterClass.class));
cards.add(new SetCardInfo("Find the Path", 183, Rarity.COMMON, mage.cards.f.FindThePath.class)); cards.add(new SetCardInfo("Find the Path", 183, Rarity.COMMON, mage.cards.f.FindThePath.class));
cards.add(new SetCardInfo("Flumph", 15, Rarity.RARE, mage.cards.f.Flumph.class)); cards.add(new SetCardInfo("Flumph", 15, Rarity.RARE, mage.cards.f.Flumph.class));
cards.add(new SetCardInfo("Fly", 59, Rarity.UNCOMMON, mage.cards.f.Fly.class)); cards.add(new SetCardInfo("Fly", 59, Rarity.UNCOMMON, mage.cards.f.Fly.class));
@ -241,6 +244,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet {
cards.add(new SetCardInfo("White Dragon", 41, Rarity.UNCOMMON, mage.cards.w.WhiteDragon.class)); cards.add(new SetCardInfo("White Dragon", 41, Rarity.UNCOMMON, mage.cards.w.WhiteDragon.class));
cards.add(new SetCardInfo("Wight", 127, Rarity.RARE, mage.cards.w.Wight.class)); cards.add(new SetCardInfo("Wight", 127, Rarity.RARE, mage.cards.w.Wight.class));
cards.add(new SetCardInfo("Wild Shape", 212, Rarity.UNCOMMON, mage.cards.w.WildShape.class)); cards.add(new SetCardInfo("Wild Shape", 212, Rarity.UNCOMMON, mage.cards.w.WildShape.class));
cards.add(new SetCardInfo("Wizard Class", 81, Rarity.UNCOMMON, mage.cards.w.WizardClass.class));
cards.add(new SetCardInfo("Xorn", 167, Rarity.RARE, mage.cards.x.Xorn.class)); cards.add(new SetCardInfo("Xorn", 167, Rarity.RARE, mage.cards.x.Xorn.class));
cards.add(new SetCardInfo("You Come to a River", 83, Rarity.COMMON, mage.cards.y.YouComeToARiver.class)); cards.add(new SetCardInfo("You Come to a River", 83, Rarity.COMMON, mage.cards.y.YouComeToARiver.class));
cards.add(new SetCardInfo("You Come to the Gnoll Camp", 168, Rarity.COMMON, mage.cards.y.YouComeToTheGnollCamp.class)); cards.add(new SetCardInfo("You Come to the Gnoll Camp", 168, Rarity.COMMON, mage.cards.y.YouComeToTheGnollCamp.class));

View file

@ -0,0 +1,133 @@
package org.mage.test.cards.enchantments;
import mage.abilities.keyword.HasteAbility;
import mage.constants.CardType;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.permanent.Permanent;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author TheElk801
*/
public class ClassTest extends CardTestPlayerBase {
private static final String wizard = "Wizard Class";
private static final String merfolk = "Merfolk of the Pearl Trident";
private static final String druid = "Druid Class";
private static final String forest = "Forest";
private static final String wastes = "Wastes";
private void assertClassLevel(String cardName, int level) {
Permanent permanent = getPermanent(cardName);
Assert.assertEquals(
cardName + " should be level " + level +
" but was level " + permanent.getClassLevel(),
level, permanent.getClassLevel()
);
}
@Test
public void test_WizardClass__FirstLevel() {
addCard(Zone.BATTLEFIELD, playerA, wizard);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertClassLevel(wizard, 1);
assertHandCount(playerA, 0);
}
@Test
public void test_WizardClass__SecondLevel() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
addCard(Zone.BATTLEFIELD, playerA, wizard);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{U}");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertClassLevel(wizard, 2);
assertHandCount(playerA, 2);
}
@Test
public void test_WizardClass__ThirdLevel() {
addCard(Zone.BATTLEFIELD, playerA, "Island", 8);
addCard(Zone.BATTLEFIELD, playerA, wizard);
addCard(Zone.BATTLEFIELD, playerA, merfolk);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{U}");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{U}");
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertClassLevel(wizard, 3);
assertHandCount(playerA, 3);
assertCounterCount(merfolk, CounterType.P1P1, 1);
}
@Test
public void test_DruidClass__FirstLevel() {
addCard(Zone.BATTLEFIELD, playerA, druid);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertClassLevel(druid, 1);
assertHandCount(playerA, 0);
}
@Test
public void test_DruidClass__SecondLevel() {
addCard(Zone.BATTLEFIELD, playerA, forest, 3);
addCard(Zone.BATTLEFIELD, playerA, druid);
addCard(Zone.HAND, playerA, forest, 2);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{G}");
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, forest);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, forest);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertClassLevel(druid, 2);
assertHandCount(playerA, 0);
assertPermanentCount(playerA, forest, 5);
assertLife(playerA, 20 + 1 + 1);
}
@Test
public void test_DruidClass__ThirdLevel() {
addCard(Zone.BATTLEFIELD, playerA, forest, 6);
addCard(Zone.BATTLEFIELD, playerA, druid);
addCard(Zone.HAND, playerA, forest);
addCard(Zone.HAND, playerA, wastes);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}{G}");
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, forest);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, wastes);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{G}");
addTarget(playerA, wastes);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertClassLevel(druid, 3);
assertHandCount(playerA, 0);
assertPermanentCount(playerA, forest, 7);
assertLife(playerA, 20 + 1 + 1);
assertType(wastes, CardType.CREATURE, true);
assertPowerToughness(playerA, wastes, 8, 8);
assertAbility(playerA, wastes, HasteAbility.getInstance(), true);
}
}

View file

@ -1,5 +1,6 @@
package mage.abilities.common; package mage.abilities.common;
import mage.MageObjectReference;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.constants.Zone; import mage.constants.Zone;
@ -64,6 +65,7 @@ public class AttacksCreatureYouControlTriggeredAbility extends TriggeredAbilityI
if (setTargetPointer) { if (setTargetPointer) {
this.getEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game)); this.getEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game));
} }
this.getEffects().setValue("attackerRef", new MageObjectReference(sourcePermanent, game));
return true; return true;
} }
return false; return false;

View file

@ -0,0 +1,45 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
* @author TheElk801
*/
public class BecomesClassLevelTriggeredAbility extends TriggeredAbilityImpl {
private final int level;
public BecomesClassLevelTriggeredAbility(Effect effect, int level) {
super(Zone.BATTLEFIELD, effect);
this.level = level;
}
private BecomesClassLevelTriggeredAbility(final BecomesClassLevelTriggeredAbility ability) {
super(ability);
this.level = ability.level;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.GAINS_CLASS_LEVEL;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return event.getAmount() == level && event.getSourceId().equals(getSourceId());
}
@Override
public BecomesClassLevelTriggeredAbility copy() {
return new BecomesClassLevelTriggeredAbility(this);
}
@Override
public String getRule() {
return "When this Class becomes level " + level + ", " + super.getRule();
}
}

View file

@ -0,0 +1,55 @@
package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.Effect;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author TheElk801
*/
public class GainClassAbilitySourceEffect extends ContinuousEffectImpl implements SourceEffect {
private final Ability ability;
private final int level;
public GainClassAbilitySourceEffect(Effect effect, int level) {
this(new SimpleStaticAbility(effect), level);
}
public GainClassAbilitySourceEffect(Ability ability, int level) {
super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
staticText = ability.getRule();
this.ability = ability;
this.level = level;
this.ability.setRuleVisible(false);
generateGainAbilityDependencies(ability, null);
}
private GainClassAbilitySourceEffect(final GainClassAbilitySourceEffect effect) {
super(effect);
this.level = effect.level;
this.ability = effect.ability;
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent == null || permanent.getClassLevel() < level) {
return false;
}
permanent.addAbility(ability, source.getSourceId(), game);
return true;
}
@Override
public GainClassAbilitySourceEffect copy() {
return new GainClassAbilitySourceEffect(this);
}
}

View file

@ -1,6 +1,7 @@
package mage.abilities.effects.common.cost; package mage.abilities.effects.common.cost;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.constants.CostModificationType; import mage.constants.CostModificationType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
@ -8,22 +9,28 @@ import mage.game.Game;
import mage.util.CardUtil; import mage.util.CardUtil;
/** /**
*
* @author Styxo * @author Styxo
*/ */
public class AbilitiesCostReductionControllerEffect extends CostModificationEffectImpl { public class AbilitiesCostReductionControllerEffect extends CostModificationEffectImpl {
private final Class activatedAbility; private final Class<? extends ActivatedAbility> activatedAbility;
private final int amount;
public AbilitiesCostReductionControllerEffect(Class activatedAbility, String activatedAbilityName) { public AbilitiesCostReductionControllerEffect(Class<? extends ActivatedAbility> activatedAbility, String activatedAbilityName) {
this(activatedAbility, activatedAbilityName, 1);
}
public AbilitiesCostReductionControllerEffect(Class<? extends ActivatedAbility> activatedAbility, String activatedAbilityName, int amount) {
super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST); super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.REDUCE_COST);
this.activatedAbility = activatedAbility; this.activatedAbility = activatedAbility;
staticText = activatedAbilityName + " costs you pay cost {1} less"; staticText = activatedAbilityName + " costs you pay cost {" + amount + "} less";
this.amount = amount;
} }
public AbilitiesCostReductionControllerEffect(AbilitiesCostReductionControllerEffect effect) { public AbilitiesCostReductionControllerEffect(AbilitiesCostReductionControllerEffect effect) {
super(effect); super(effect);
this.activatedAbility = effect.activatedAbility; this.activatedAbility = effect.activatedAbility;
this.amount = effect.amount;
} }
@Override @Override
@ -33,7 +40,7 @@ public class AbilitiesCostReductionControllerEffect extends CostModificationEffe
@Override @Override
public boolean apply(Game game, Ability source, Ability abilityToModify) { public boolean apply(Game game, Ability source, Ability abilityToModify) {
CardUtil.reduceCost(abilityToModify, 1); CardUtil.reduceCost(abilityToModify, amount);
return true; return true;
} }

View file

@ -0,0 +1,27 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.hint.Hint;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author TheElk801
*/
public enum ClassLevelHint implements Hint {
instance;
@Override
public String getText(Game game, Ability ability) {
Permanent permanent = ability.getSourcePermanentIfItStillExists(game);
if (permanent == null) {
return null;
}
return "Class level: " + permanent.getClassLevel();
}
@Override
public ClassLevelHint copy() {
return this;
}
}

View file

@ -0,0 +1,88 @@
package mage.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public class ClassLevelAbility extends ActivatedAbilityImpl {
private final int level;
private final String manaString;
public ClassLevelAbility(int level, String manaString) {
super(Zone.BATTLEFIELD, new SetClassLevelEffect(level), new ManaCostsImpl<>(manaString));
this.level = level;
this.manaString = manaString;
setTiming(TimingRule.SORCERY);
}
private ClassLevelAbility(final ClassLevelAbility ability) {
super(ability);
this.level = ability.level;
this.manaString = ability.manaString;
}
@Override
public ClassLevelAbility copy() {
return new ClassLevelAbility(this);
}
@Override
public String getRule() {
return manaString + ": Level " + level;
}
@Override
public ActivationStatus canActivate(UUID playerId, Game game) {
Permanent permanent = getSourcePermanentIfItStillExists(game);
if (permanent != null && permanent.getClassLevel() == level - 1) {
return super.canActivate(playerId, game);
}
return ActivationStatus.getFalse();
}
}
class SetClassLevelEffect extends OneShotEffect {
private final int level;
SetClassLevelEffect(int level) {
super(Outcome.Benefit);
this.level = level;
}
private SetClassLevelEffect(final SetClassLevelEffect effect) {
super(effect);
this.level = effect.level;
}
@Override
public SetClassLevelEffect copy() {
return new SetClassLevelEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent == null || !permanent.setClassLevel(level)) {
return false;
}
game.fireEvent(GameEvent.getEvent(
GameEvent.EventType.GAINS_CLASS_LEVEL, source.getSourceId(),
source, source.getControllerId(), level
));
return true;
}
}

View file

@ -0,0 +1,30 @@
package mage.abilities.keyword;
import mage.abilities.StaticAbility;
import mage.abilities.hint.common.ClassLevelHint;
import mage.constants.Zone;
/**
* @author TheElk801
*/
public class ClassReminderAbility extends StaticAbility {
public ClassReminderAbility() {
super(Zone.ALL, null);
this.addHint(ClassLevelHint.instance);
}
private ClassReminderAbility(final ClassReminderAbility ability) {
super(ability);
}
@Override
public ClassReminderAbility copy() {
return new ClassReminderAbility(this);
}
@Override
public String getRule() {
return "<i>(Gain the next level as a sorcery to add its ability.)</i>";
}
}

View file

@ -32,6 +32,7 @@ public enum SubType {
// 205.3h Enchantments have their own unique set of subtypes; these subtypes are called enchantment types. // 205.3h Enchantments have their own unique set of subtypes; these subtypes are called enchantment types.
AURA("Aura", SubTypeSet.EnchantmentType), AURA("Aura", SubTypeSet.EnchantmentType),
CARTOUCHE("Cartouche", SubTypeSet.EnchantmentType), CARTOUCHE("Cartouche", SubTypeSet.EnchantmentType),
CLASS("Class", SubTypeSet.EnchantmentType),
CURSE("Curse", SubTypeSet.EnchantmentType), CURSE("Curse", SubTypeSet.EnchantmentType),
RUNE("Rune", SubTypeSet.EnchantmentType), RUNE("Rune", SubTypeSet.EnchantmentType),
SAGA("Saga", SubTypeSet.EnchantmentType), SAGA("Saga", SubTypeSet.EnchantmentType),

View file

@ -340,6 +340,7 @@ public class GameEvent implements Serializable {
*/ */
BECOMES_EXERTED, BECOMES_EXERTED,
BECOMES_RENOWNED, BECOMES_RENOWNED,
GAINS_CLASS_LEVEL,
/* BECOMES_MONARCH /* BECOMES_MONARCH
targetId playerId of the player that becomes the monarch targetId playerId of the player that becomes the monarch
sourceId id of the source object that created that effect, if no effect exist it's null sourceId id of the source object that created that effect, if no effect exist it's null

View file

@ -73,6 +73,10 @@ public interface Permanent extends Card, Controllable {
void setRenowned(boolean value); void setRenowned(boolean value);
int getClassLevel();
boolean setClassLevel(int classLevel);
void setCardNumber(String cid); void setCardNumber(String cid);
void setExpansionSetCode(String expansionSetCode); void setExpansionSetCode(String expansionSetCode);

View file

@ -72,6 +72,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected boolean renowned; protected boolean renowned;
protected boolean manifested = false; protected boolean manifested = false;
protected boolean morphed = false; protected boolean morphed = false;
protected int classLevel = 1;
protected UUID originalControllerId; protected UUID originalControllerId;
protected UUID controllerId; protected UUID controllerId;
protected UUID beforeResetControllerId; protected UUID beforeResetControllerId;
@ -163,6 +164,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.transformed = permanent.transformed; this.transformed = permanent.transformed;
this.monstrous = permanent.monstrous; this.monstrous = permanent.monstrous;
this.renowned = permanent.renowned; this.renowned = permanent.renowned;
this.classLevel = permanent.classLevel;
this.pairedPermanent = permanent.pairedPermanent; this.pairedPermanent = permanent.pairedPermanent;
this.bandedCards.addAll(permanent.bandedCards); this.bandedCards.addAll(permanent.bandedCards);
this.timesLoyaltyUsed = permanent.timesLoyaltyUsed; this.timesLoyaltyUsed = permanent.timesLoyaltyUsed;
@ -1519,6 +1521,20 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.renowned = value; this.renowned = value;
} }
@Override
public int getClassLevel() {
return classLevel;
}
@Override
public boolean setClassLevel(int classLevel) {
if (this.classLevel == classLevel - 1) {
this.classLevel = classLevel;
return true;
}
return false;
}
@Override @Override
public void setPairedCard(MageObjectReference pairedCard) { public void setPairedCard(MageObjectReference pairedCard) {
this.pairedPermanent = pairedCard; this.pairedPermanent = pairedCard;