mirror of
https://github.com/correl/mage.git
synced 2025-01-11 19:13:02 +00:00
Ghostfire Blade - fixed that it can't be played without full available mana (#6698);
This commit is contained in:
parent
2a31e8063b
commit
6dccaee9a4
7 changed files with 127 additions and 48 deletions
|
@ -1,7 +1,5 @@
|
|||
|
||||
package mage.cards.g;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
|
@ -11,38 +9,53 @@ import mage.abilities.keyword.EquipAbility;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public final class GhostfireBlade extends CardImpl {
|
||||
|
||||
public GhostfireBlade(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{1}");
|
||||
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}");
|
||||
this.subtype.add(SubType.EQUIPMENT);
|
||||
|
||||
// Equipped creature gets +2/+2
|
||||
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEquippedEffect(2, 2)));
|
||||
|
||||
|
||||
// Equip {3}
|
||||
this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(3)));
|
||||
this.addAbility(new EquipAbility(Outcome.BoostCreature, new GenericManaCost(3))); // todo
|
||||
|
||||
// Ghostfire Blade's equip ability costs {2} less to activate if it targets a colorless creature.
|
||||
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new InfoEffect("{this}'s equip ability costs {2} less to activate if it targets a colorless creature")));
|
||||
}
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public void adjustCosts(Ability ability, Game game) {
|
||||
if (ability instanceof EquipAbility) {
|
||||
Permanent targetCreature = game.getPermanent(ability.getTargets().getFirstTarget());
|
||||
if (targetCreature != null && targetCreature.getColor(game).isColorless()) {
|
||||
CardUtil.reduceCost(ability, 2);
|
||||
if (game.inCheckPlayableState()) {
|
||||
// checking state
|
||||
boolean canSelectColorlessCreature = CardUtil.getAllPossibleTargets(ability, game).stream()
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(permanent -> permanent.getColor(game).isColorless());
|
||||
if (canSelectColorlessCreature) {
|
||||
CardUtil.reduceCost(ability, 2);
|
||||
}
|
||||
} else {
|
||||
// real cast state
|
||||
Permanent targetCreature = game.getPermanent(ability.getTargets().getFirstTarget());
|
||||
if (targetCreature != null && targetCreature.getColor(game).isColorless()) {
|
||||
CardUtil.reduceCost(ability, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package org.mage.test.cards.single;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
|
||||
public class GhostfireBladeTest extends CardTestPlayerBase {
|
||||
|
||||
@Test
|
||||
public void test_CanPlayWithCostReduce() {
|
||||
// Equipped creature gets +2/+2.
|
||||
// Equip {3}
|
||||
// Ghostfire Blade’s equip ability costs {2} less to activate if it targets a colorless creature.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Ghostfire Blade", 1);
|
||||
//
|
||||
addCard(Zone.HAND, playerA, "Alpha Myr", 1); // {2}, 2/1
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||
|
||||
checkPlayableAbility("can't play", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {3}", false);
|
||||
|
||||
// add creature and activate cost reduce
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Alpha Myr");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
checkPlayableAbility("can't play wit no mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {3}", false); // no mana after creature cast
|
||||
|
||||
// can play on next turn
|
||||
checkPlayableAbility("can play", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {3}", true);
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Equip {3}", "Alpha Myr");
|
||||
|
||||
setStopAt(3, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPowerToughness(playerA, "Alpha Myr", 2 + 2, 1 + 2);
|
||||
}
|
||||
|
||||
}
|
|
@ -1226,6 +1226,14 @@ public abstract class AbilityImpl implements Ability {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic cost modification for ability.
|
||||
* Example: if it need stack related info (like real targets) then must check two states (game.inCheckPlayableState):
|
||||
* 1. In playable state it must check all possible use cases (e.g. allow to reduce on any available target and modes)
|
||||
* 2. In real cast state it must check current use case (e.g. real selected targets and modes)
|
||||
*
|
||||
* @param costAdjuster
|
||||
*/
|
||||
@Override
|
||||
public void setCostAdjuster(CostAdjuster costAdjuster) {
|
||||
this.costAdjuster = costAdjuster;
|
||||
|
|
|
@ -8,5 +8,14 @@ import mage.game.Game;
|
|||
*/
|
||||
public interface CostAdjuster {
|
||||
|
||||
/**
|
||||
* Must check playable and real cast states.
|
||||
* Example: if it need stack related info (like real targets) then must check two states (game.inCheckPlayableState):
|
||||
* 1. In playable state it must check all possible use cases (e.g. allow to reduce on any available target and modes)
|
||||
* 2. In real cast state it must check current use case (e.g. real selected targets and modes)
|
||||
*
|
||||
* @param ability
|
||||
* @param game
|
||||
*/
|
||||
void adjustCosts(Ability ability, Game game);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package mage.abilities.effects.common.cost;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.constants.CostModificationType;
|
||||
import mage.constants.Duration;
|
||||
|
@ -11,13 +10,10 @@ import mage.filter.FilterCard;
|
|||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
|
@ -119,12 +115,12 @@ public class SpellsCostModificationThatTargetSourceEffect extends CostModificati
|
|||
Spell spell = (Spell) game.getStack().getStackObject(abilityToModify.getId());
|
||||
if (spell != null && this.spellFilter.match(spell, game)) {
|
||||
// real cast with put on stack
|
||||
Set<UUID> allTargets = getAllSelectedTargets(abilityToModify, source, game);
|
||||
Set<UUID> allTargets = CardUtil.getAllSelectedTargets(abilityToModify, game);
|
||||
return allTargets.contains(source.getSourceId());
|
||||
} else {
|
||||
// get playable and other staff without put on stack
|
||||
// used at least for flashback ability because Flashback ability doesn't use stack
|
||||
Set<UUID> allTargets = getAllPossibleTargets(abilityToModify, source, game);
|
||||
Set<UUID> allTargets = CardUtil.getAllPossibleTargets(abilityToModify, game);
|
||||
switch (this.getModificationType()) {
|
||||
case REDUCE_COST:
|
||||
// reduce all the time
|
||||
|
@ -137,27 +133,6 @@ public class SpellsCostModificationThatTargetSourceEffect extends CostModificati
|
|||
return false;
|
||||
}
|
||||
|
||||
private Set<UUID> getAllSelectedTargets(Ability abilityToModify, Ability source, Game game) {
|
||||
return abilityToModify.getModes().getSelectedModes()
|
||||
.stream()
|
||||
.map(abilityToModify.getModes()::get)
|
||||
.map(Mode::getTargets)
|
||||
.flatMap(Collection::stream)
|
||||
.map(Target::getTargets)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private Set<UUID> getAllPossibleTargets(Ability abilityToModify, Ability source, Game game) {
|
||||
return abilityToModify.getModes().values()
|
||||
.stream()
|
||||
.map(Mode::getTargets)
|
||||
.flatMap(Collection::stream)
|
||||
.map(t -> t.possibleTargets(abilityToModify.getSourceId(), abilityToModify.getControllerId(), game))
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public SpellsCostModificationThatTargetSourceEffect withTargetName(String targetName) {
|
||||
this.targetName = targetName;
|
||||
setText();
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
package mage.cards;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectImpl;
|
||||
import mage.Mana;
|
||||
|
@ -33,6 +27,13 @@ import mage.util.SubTypeList;
|
|||
import mage.watchers.Watcher;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -395,6 +396,15 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
|||
return spellAbility;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic cost modification for card (process only own abilities).
|
||||
* Example: if it need stack related info (like real targets) then must check two states (game.inCheckPlayableState):
|
||||
* 1. In playable state it must check all possible use cases (e.g. allow to reduce on any available target and modes)
|
||||
* 2. In real cast state it must check current use case (e.g. real selected targets and modes)
|
||||
*
|
||||
* @param ability
|
||||
* @param game
|
||||
*/
|
||||
@Override
|
||||
public void adjustCosts(Ability ability, Game game) {
|
||||
ability.adjustCosts(game);
|
||||
|
|
|
@ -4,6 +4,7 @@ import mage.MageObject;
|
|||
import mage.Mana;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.costs.VariableCost;
|
||||
import mage.abilities.costs.mana.*;
|
||||
|
@ -19,16 +20,15 @@ import mage.game.Game;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.token.Token;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.target.Target;
|
||||
import mage.util.functions.CopyTokenFunction;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author nantuko
|
||||
|
@ -808,4 +808,25 @@ public final class CardUtil {
|
|||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<UUID> getAllSelectedTargets(Ability ability, Game game) {
|
||||
return ability.getModes().getSelectedModes()
|
||||
.stream()
|
||||
.map(ability.getModes()::get)
|
||||
.map(Mode::getTargets)
|
||||
.flatMap(Collection::stream)
|
||||
.map(Target::getTargets)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static Set<UUID> getAllPossibleTargets(Ability ability, Game game) {
|
||||
return ability.getModes().values()
|
||||
.stream()
|
||||
.map(Mode::getTargets)
|
||||
.flatMap(Collection::stream)
|
||||
.map(t -> t.possibleTargets(ability.getSourceId(), ability.getControllerId(), game))
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue