forked from External/mage
[CMM] Implement Demon of Fate's Design (#10737)
* refactor SacrificeCostManaValue to be an enum. * [CMM] Implement Demon of Fates Design * Add Unit Tests, including one bug on alternative cost. * fix alternativeCosts made from dynamicCost returning that they were not activated when paid. * fix small issues, add hint * cleanup tests and add a couple * Capitalize enum instances * Minor fixes * simplify the ContinuousEffect * use the ConditionPermanentHint made for the Demon * fix text
This commit is contained in:
parent
0ce21d12a5
commit
eef8f508e4
22 changed files with 707 additions and 111 deletions
|
|
@ -0,0 +1,37 @@
|
|||
package mage.abilities.condition.common;
|
||||
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.cards.AdventureCardSpell;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.ModalDoubleFacedCardHalf;
|
||||
import mage.cards.SplitCardHalf;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public enum IsBeingCastFromHandCondition implements Condition {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
MageObject object = game.getObject(source);
|
||||
if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) {
|
||||
UUID mainCardId = ((Card) object).getMainCard().getId();
|
||||
object = game.getObject(mainCardId);
|
||||
}
|
||||
if (object instanceof Spell) { // needed to check if it can be cast by alternate cost
|
||||
Spell spell = (Spell) object;
|
||||
return Zone.HAND.equals(spell.getFromZone());
|
||||
}
|
||||
if (object instanceof Card) { // needed for the check what's playable
|
||||
Card card = (Card) object;
|
||||
return game.getPlayer(card.getOwnerId()).getHand().get(card.getId(), game) != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import java.util.stream.Collectors;
|
|||
public class AlternativeCostSourceAbility extends StaticAbility implements AlternativeSourceCosts {
|
||||
|
||||
private static final String ALTERNATIVE_COST_ACTIVATION_KEY = "AlternativeCostActivated";
|
||||
private static final String ALTERNATIVE_DYNAMIC_COST_ACTIVATED_KEY = "AlternativeDynamicCostActivated";
|
||||
|
||||
private Costs<AlternativeCost> alternateCosts = new CostsImpl<>();
|
||||
protected Condition condition;
|
||||
|
|
@ -162,8 +163,13 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
}
|
||||
}
|
||||
|
||||
// Those cost have been paid, we want to store them.
|
||||
if (dynamicCost != null) {
|
||||
rememberDynamicCost(game, ability, alternativeCostsToCheck);
|
||||
}
|
||||
|
||||
// save activated status
|
||||
game.getState().setValue(getActivatedKey(ability), Boolean.TRUE);
|
||||
doActivate(game, ability);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -174,6 +180,10 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
return isActivated(ability, game);
|
||||
}
|
||||
|
||||
protected void doActivate(Game game, Ability ability) {
|
||||
game.getState().setValue(getActivatedKey(ability), Boolean.TRUE);
|
||||
}
|
||||
|
||||
private String getActivatedKey(Ability source) {
|
||||
return getActivatedKey(this.getOriginalId(), source.getSourceId(), source.getSourceObjectZoneChangeCounter());
|
||||
}
|
||||
|
|
@ -184,6 +194,20 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
return ALTERNATIVE_COST_ACTIVATION_KEY + "_" + alternativeCostOriginalId + "_" /*+ sourceId + "_"*/ + sourceZCC;
|
||||
}
|
||||
|
||||
private void rememberDynamicCost(Game game, Ability ability, Costs<AlternativeCost> costs) {
|
||||
game.getState().setValue(getDynamicCostActivatedKey(ability), costs);
|
||||
}
|
||||
|
||||
private String getDynamicCostActivatedKey(Ability source) {
|
||||
return getDynamicCostActivatedKey(this.getOriginalId(), source.getSourceId(), source.getSourceObjectZoneChangeCounter());
|
||||
}
|
||||
|
||||
private static String getDynamicCostActivatedKey(UUID alternativeCostOriginalId, UUID sourceId, int sourceZCC) {
|
||||
// can't use sourceId cause copied cards are different...
|
||||
// TODO: enable sourceId after copy card fix (it must copy cards with all related game state values)
|
||||
return ALTERNATIVE_DYNAMIC_COST_ACTIVATED_KEY + "_" + alternativeCostOriginalId + "_" /*+ sourceId + "_"*/ + sourceZCC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search activated status of alternative cost.
|
||||
* <p>
|
||||
|
|
@ -210,8 +234,10 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
|||
public boolean isActivated(Ability source, Game game) {
|
||||
Costs<AlternativeCost> alternativeCostsToCheck;
|
||||
if (dynamicCost != null) {
|
||||
alternativeCostsToCheck = new CostsImpl<>();
|
||||
alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(source, game)));
|
||||
alternativeCostsToCheck = (Costs<AlternativeCost>) game.getState().getValue(getDynamicCostActivatedKey(source));
|
||||
if (alternativeCostsToCheck == null) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
alternativeCostsToCheck = this.alternateCosts;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class PayLifeCost extends CostImpl {
|
|||
this.text = "pay " + text;
|
||||
}
|
||||
|
||||
public PayLifeCost(PayLifeCost cost) {
|
||||
protected PayLifeCost(final PayLifeCost cost) {
|
||||
super(cost);
|
||||
this.amount = cost.amount.copy();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,27 +10,27 @@ import mage.game.Game;
|
|||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author LoneFox
|
||||
* @author LoneFox, Susucr
|
||||
*/
|
||||
public class SacrificeCostConvertedMana implements DynamicValue {
|
||||
public enum SacrificeCostManaValue implements DynamicValue {
|
||||
CREATURE("creature"),
|
||||
ENCHANTMENT("enchantment"),
|
||||
ARTIFACT("artifact"),
|
||||
PERMANENT("permanent");
|
||||
|
||||
private final String type;
|
||||
|
||||
public SacrificeCostConvertedMana(String type) {
|
||||
private SacrificeCostManaValue(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public SacrificeCostConvertedMana(SacrificeCostConvertedMana value) {
|
||||
this.type = value.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||
for(Cost cost : sourceAbility.getCosts()) {
|
||||
if(cost instanceof SacrificeTargetCost) {
|
||||
for (Cost cost : sourceAbility.getCosts()) {
|
||||
if (cost instanceof SacrificeTargetCost) {
|
||||
SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost;
|
||||
int totalCMC = 0;
|
||||
for(Permanent permanent : sacrificeCost.getPermanents()) {
|
||||
for (Permanent permanent : sacrificeCost.getPermanents()) {
|
||||
totalCMC += permanent.getManaValue();
|
||||
}
|
||||
return totalCMC;
|
||||
|
|
@ -40,8 +40,8 @@ public class SacrificeCostConvertedMana implements DynamicValue {
|
|||
}
|
||||
|
||||
@Override
|
||||
public SacrificeCostConvertedMana copy() {
|
||||
return new SacrificeCostConvertedMana(this);
|
||||
public SacrificeCostManaValue copy() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1,21 +1,19 @@
|
|||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.CompoundCondition;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.condition.common.IsBeingCastFromHandCondition;
|
||||
import mage.abilities.condition.common.SourceIsSpellCondition;
|
||||
import mage.abilities.costs.AlternativeCostSourceAbility;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.cards.AdventureCardSpell;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.ModalDoubleFacedCardHalf;
|
||||
import mage.cards.SplitCardHalf;
|
||||
import mage.constants.*;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Layer;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubLayer;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -81,26 +79,4 @@ public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImp
|
|||
public boolean hasLayer(Layer layer) {
|
||||
return layer == Layer.RulesEffects;
|
||||
}
|
||||
}
|
||||
|
||||
enum IsBeingCastFromHandCondition implements Condition {
|
||||
instance;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
MageObject object = game.getObject(source);
|
||||
if (object instanceof SplitCardHalf || object instanceof AdventureCardSpell || object instanceof ModalDoubleFacedCardHalf) {
|
||||
UUID mainCardId = ((Card) object).getMainCard().getId();
|
||||
object = game.getObject(mainCardId);
|
||||
}
|
||||
if (object instanceof Spell) { // needed to check if it can be cast by alternate cost
|
||||
Spell spell = (Spell) object;
|
||||
return Zone.HAND.equals(spell.getFromZone());
|
||||
}
|
||||
if (object instanceof Card) { // needed for the check what's playable
|
||||
Card card = (Card) object;
|
||||
return game.getPlayer(card.getOwnerId()).getHand().get(card.getId(), game) != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public class ConditionHint implements Hint {
|
|||
this.useIcons = useIcons;
|
||||
}
|
||||
|
||||
private ConditionHint(final ConditionHint hint) {
|
||||
protected ConditionHint(final ConditionHint hint) {
|
||||
this.condition = hint.condition;
|
||||
this.trueText = hint.trueText;
|
||||
this.trueColor = hint.trueColor;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
package mage.abilities.hint.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.abilities.hint.ConditionHint;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* ConditionHint that is only shown on permanents.
|
||||
*
|
||||
* @author Susucr
|
||||
*/
|
||||
public class ConditionPermanentHint extends ConditionHint {
|
||||
|
||||
public ConditionPermanentHint(Condition condition) {
|
||||
super(condition);
|
||||
}
|
||||
|
||||
public ConditionPermanentHint(Condition condition, String textWithIcons) {
|
||||
super(condition, textWithIcons);
|
||||
}
|
||||
|
||||
public ConditionPermanentHint(Condition condition, String trueText, Color trueColor, String falseText, Color falseColor, Boolean useIcons) {
|
||||
super(condition, trueText, trueColor, falseText, falseColor, useIcons);
|
||||
}
|
||||
|
||||
private ConditionPermanentHint(final ConditionPermanentHint hint) {
|
||||
super(hint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText(Game game, Ability ability) {
|
||||
if (game.getPermanent(ability.getSourceId()) == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return super.getText(game, ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hint copy() {
|
||||
return new ConditionPermanentHint(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1212,7 +1212,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
game.fireEvent(castEvent);
|
||||
if (spell.activate(game, noMana)) {
|
||||
GameEvent castedEvent = GameEvent.getEvent(GameEvent.EventType.SPELL_CAST,
|
||||
spell.getSpellAbility().getId(), spell.getSpellAbility(), playerId, approvingObject);
|
||||
ability.getId(), ability, playerId, approvingObject);
|
||||
castedEvent.setZone(fromZone);
|
||||
game.fireEvent(castedEvent);
|
||||
if (!game.isSimulation()) {
|
||||
|
|
@ -4721,7 +4721,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
boolean chooseOrder = false;
|
||||
if (userData.askMoveToGraveOrder() && (cards.size() > 1)) {
|
||||
chooseOrder = choosingPlayer.chooseUse(Outcome.Neutral,
|
||||
"Choose the order in which the cards go to the graveyard?", source, game);
|
||||
"Choose the order in which the cards go to the graveyard?", source, game);
|
||||
}
|
||||
if (chooseOrder) {
|
||||
TargetCard target = new TargetCard(fromZone,
|
||||
|
|
@ -5145,13 +5145,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
@Override
|
||||
public Permanent getRingBearer(Game game) {
|
||||
return game.getBattlefield()
|
||||
.getActivePermanents(
|
||||
StaticFilters.FILTER_CONTROLLED_RINGBEARER,
|
||||
getId(),null, game)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
.getActivePermanents(
|
||||
StaticFilters.FILTER_CONTROLLED_RINGBEARER,
|
||||
getId(), null, game)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
// 701.52a Certain spells and abilities have the text “the Ring tempts you.” Each time the Ring tempts
|
||||
|
|
@ -5163,11 +5163,11 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
UUID currentBearerId = currentBearer == null ? null : currentBearer.getId();
|
||||
|
||||
List<UUID> ids = game.getBattlefield()
|
||||
.getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, getId(), null, game)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(p -> p.getId())
|
||||
.collect(Collectors.toList());
|
||||
.getActivePermanents(StaticFilters.FILTER_CONTROLLED_CREATURE, getId(), null, game)
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(p -> p.getId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (ids.isEmpty()) {
|
||||
game.informPlayers(getLogName() + " has no creature to be Ring-bearer.");
|
||||
|
|
@ -5196,8 +5196,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
choose(Outcome.Neutral, target, null, game);
|
||||
|
||||
newBearerId = target.getFirstTarget();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
newBearerId = currentBearerId;
|
||||
}
|
||||
}
|
||||
|
|
@ -5211,11 +5210,11 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
// or abilities that care about which creature was chosen as your Ring-bearer.
|
||||
// (2023-06-16)
|
||||
game.informPlayers(getLogName() + " did not choose a new Ring-bearer. " +
|
||||
"It is still " + (currentBearer == null ? "" : currentBearer.getLogName()) + ".");
|
||||
"It is still " + (currentBearer == null ? "" : currentBearer.getLogName()) + ".");
|
||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.RING_BEARER_CHOSEN, currentBearerId, null, getId()));
|
||||
} else {
|
||||
Permanent ringBearer = game.getPermanent(newBearerId);
|
||||
if(ringBearer != null){
|
||||
if (ringBearer != null) {
|
||||
// The setRingBearer method is taking care of removing
|
||||
// the status from the current ring bearer, if existing.
|
||||
ringBearer.setRingBearer(game, true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue