Implement offspring mechanic (#12572)

* implement offspring mechanic

* create offspring test
This commit is contained in:
Evan Kranzler 2024-07-12 23:38:29 -04:00 committed by GitHub
parent 6232368162
commit 204f67c5f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 185 additions and 11 deletions

View file

@ -4,15 +4,11 @@ import mage.cards.ExpansionSet;
import mage.constants.Rarity;
import mage.constants.SetType;
import java.util.Arrays;
import java.util.List;
/**
* @author TheElk801
*/
public final class Bloomburrow extends ExpansionSet {
private static final List<String> unfinished = Arrays.asList("Coruscation Mage", "Darkstar Augur", "Finch Formation", "Flowerfoot Swordmaster", "Iridescent Vinelasher", "Manifold Mouse", "Splash Lasher", "Steampath Charger", "Tender Wildguide", "Thundertrap Trainer", "Warren Warleader");
private static final Bloomburrow instance = new Bloomburrow();
public static Bloomburrow getInstance() {
@ -120,7 +116,5 @@ public final class Bloomburrow extends ExpansionSet {
cards.add(new SetCardInfo("Wandertale Mentor", 240, Rarity.UNCOMMON, mage.cards.w.WandertaleMentor.class));
cards.add(new SetCardInfo("Warren Warleader", 38, Rarity.MYTHIC, mage.cards.w.WarrenWarleader.class));
cards.add(new SetCardInfo("Zoraline, Cosmos Caller", 242, Rarity.RARE, mage.cards.z.ZoralineCosmosCaller.class));
cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); // remove when mechanic is implemented
}
}

View file

@ -0,0 +1,83 @@
package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentToken;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author TheElk801
*/
public class OffspringTest extends CardTestPlayerBase {
private static final String vinelasher = "Iridescent Vinelasher";
private Permanent getCreature(String name, boolean isToken) {
for (Permanent permanent : currentGame.getBattlefield().getActivePermanents(playerA.getId(), currentGame)) {
if (name.equals(permanent.getName()) && (permanent instanceof PermanentToken) == isToken) {
return permanent;
}
}
return null;
}
private void checkOffspring(String name, int power, int toughness, boolean paid) {
assertPermanentCount(playerA, name, paid ? 2 : 1);
assertTokenCount(playerA, name, paid ? 1 : 0);
Permanent original = getCreature(name, false);
Assert.assertEquals(
"Original creature should have power " + power,
power, original.getPower().getValue()
);
Assert.assertEquals(
"Original creature should have toughness " + toughness,
toughness, original.getToughness().getValue()
);
if (!paid) {
return;
}
Permanent token = getCreature(name, true);
Assert.assertEquals(
"Token creature should have power 1",
1, token.getPower().getValue()
);
Assert.assertEquals(
"Token creature should have toughness 1",
1, token.getToughness().getValue()
);
}
@Test
public void testNoPay() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
addCard(Zone.HAND, playerA, vinelasher);
setChoice(playerA, false);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, vinelasher);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
checkOffspring(vinelasher, 1, 2, false);
}
@Test
public void testPay() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.HAND, playerA, vinelasher);
setChoice(playerA, true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, vinelasher);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
checkOffspring(vinelasher, 1, 2, true);
}
}

View file

@ -1,26 +1,58 @@
package mage.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.costs.Cost;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.costs.*;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.util.CardUtil;
/**
* @author TheElk801
* TODO: Implement this
*/
public class OffspringAbility extends StaticAbility {
public class OffspringAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
private static final String keywordText = "Offspring";
private static final String reminderText = "You may pay an additional %s as you cast this spell. If you do, when this creature enters, create a 1/1 token copy of it.";
private final String rule;
public static final String OFFSPRING_ACTIVATION_VALUE_KEY = "offspringActivation";
protected OptionalAdditionalCost additionalCost;
public OffspringAbility(String manaString) {
this(new ManaCostsImpl<>(manaString));
}
public OffspringAbility(Cost cost) {
super(Zone.ALL, null);
super(Zone.STACK, null);
this.additionalCost = new OptionalAdditionalCostImpl(
keywordText + ' ' + cost.getText(),
String.format(reminderText, cost.getText()), cost
);
this.additionalCost.setRepeatable(false);
this.rule = additionalCost.getName() + ' ' + additionalCost.getReminderText();
this.setRuleAtTheTop(true);
this.addSubAbility(new ConditionalInterveningIfTriggeredAbility(
new EntersBattlefieldTriggeredAbility(new OffspringEffect()), OffspringCondition.instance,
"When this creature enters, if its offspring cost was paid, create a 1/1 token copy of it."
).setRuleVisible(false));
}
private OffspringAbility(final OffspringAbility ability) {
super(ability);
this.rule = ability.rule;
this.additionalCost = ability.additionalCost.copy();
}
@Override
@ -28,8 +60,73 @@ public class OffspringAbility extends StaticAbility {
return new OffspringAbility(this);
}
@Override
public void addOptionalAdditionalCosts(Ability ability, Game game) {
if (!(ability instanceof SpellAbility)) {
return;
}
Player player = game.getPlayer(ability.getControllerId());
if (player == null) {
return;
}
additionalCost.reset();
if (!additionalCost.canPay(ability, this, ability.getControllerId(), game)
|| !player.chooseUse(Outcome.PutCreatureInPlay, "Pay " + additionalCost.getText(true) + " for offspring?", ability, game)) {
return;
}
additionalCost.activate();
for (Cost cost : ((Costs<Cost>) additionalCost)) {
ability.getCosts().add(cost.copy());
}
ability.setCostsTag(OFFSPRING_ACTIVATION_VALUE_KEY, null);
}
@Override
public String getCastMessageSuffix() {
return additionalCost.getCastSuffixMessage(0);
}
@Override
public String getRule() {
return "Offspring";
return rule;
}
}
class OffspringEffect extends OneShotEffect {
OffspringEffect() {
super(Outcome.Benefit);
}
private OffspringEffect(final OffspringEffect effect) {
super(effect);
}
@Override
public OffspringEffect copy() {
return new OffspringEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = source.getSourcePermanentOrLKI(game);
return permanent != null && new CreateTokenCopyTargetEffect(
null, null, false, 1, false,
false, null, 1, 1, false
).setSavedPermanent(permanent).apply(game, source);
}
}
enum OffspringCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return CardUtil.checkSourceCostsTagExists(game, source, OffspringAbility.OFFSPRING_ACTIVATION_VALUE_KEY);
}
@Override
public String toString() {
return "Offspring cost was paid";
}
}