[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 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("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("Cloister Gargoyle", 7, Rarity.UNCOMMON, mage.cards.c.CloisterGargoyle.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("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("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("Dungeon Crawler", 99, Rarity.UNCOMMON, mage.cards.d.DungeonCrawler.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("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("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("Flumph", 15, Rarity.RARE, mage.cards.f.Flumph.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("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("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("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));

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;
import mage.MageObjectReference;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
@ -64,6 +65,7 @@ public class AttacksCreatureYouControlTriggeredAbility extends TriggeredAbilityI
if (setTargetPointer) {
this.getEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game));
}
this.getEffects().setValue("attackerRef", new MageObjectReference(sourcePermanent, game));
return true;
}
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;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.constants.CostModificationType;
import mage.constants.Duration;
import mage.constants.Outcome;
@ -8,22 +9,28 @@ import mage.game.Game;
import mage.util.CardUtil;
/**
*
* @author Styxo
*/
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);
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) {
super(effect);
this.activatedAbility = effect.activatedAbility;
this.amount = effect.amount;
}
@Override
@ -33,7 +40,7 @@ public class AbilitiesCostReductionControllerEffect extends CostModificationEffe
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
CardUtil.reduceCost(abilityToModify, 1);
CardUtil.reduceCost(abilityToModify, amount);
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.
AURA("Aura", SubTypeSet.EnchantmentType),
CARTOUCHE("Cartouche", SubTypeSet.EnchantmentType),
CLASS("Class", SubTypeSet.EnchantmentType),
CURSE("Curse", SubTypeSet.EnchantmentType),
RUNE("Rune", SubTypeSet.EnchantmentType),
SAGA("Saga", SubTypeSet.EnchantmentType),

View file

@ -340,6 +340,7 @@ public class GameEvent implements Serializable {
*/
BECOMES_EXERTED,
BECOMES_RENOWNED,
GAINS_CLASS_LEVEL,
/* BECOMES_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

View file

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

View file

@ -72,6 +72,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected boolean renowned;
protected boolean manifested = false;
protected boolean morphed = false;
protected int classLevel = 1;
protected UUID originalControllerId;
protected UUID controllerId;
protected UUID beforeResetControllerId;
@ -163,6 +164,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.transformed = permanent.transformed;
this.monstrous = permanent.monstrous;
this.renowned = permanent.renowned;
this.classLevel = permanent.classLevel;
this.pairedPermanent = permanent.pairedPermanent;
this.bandedCards.addAll(permanent.bandedCards);
this.timesLoyaltyUsed = permanent.timesLoyaltyUsed;
@ -1519,6 +1521,20 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
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
public void setPairedCard(MageObjectReference pairedCard) {
this.pairedPermanent = pairedCard;