mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 12:18:00 +00:00
Mutate first step
This commit is contained in:
@@ -105,6 +105,7 @@ public enum ApiType {
|
||||
MultiplyCounter (CountersMultiplyEffect.class),
|
||||
MustAttack (MustAttackEffect.class),
|
||||
MustBlock (MustBlockEffect.class),
|
||||
Mutate (MutateEffect.class),
|
||||
NameCard (ChooseCardNameEffect.class),
|
||||
NoteCounters (CountersNoteEffect.class),
|
||||
PeekAndReveal (PeekAndRevealEffect.class),
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package forge.game.ability.effects;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.game.GameObject;
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.ability.SpellAbilityEffect;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardCollection;
|
||||
import forge.game.card.CardCollectionView;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.trigger.TriggerType;
|
||||
import forge.util.Lang;
|
||||
|
||||
public class MutateEffect extends SpellAbilityEffect {
|
||||
|
||||
@Override
|
||||
public String getStackDescription(SpellAbility sa) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<GameObject> targets = getTargets(sa);
|
||||
|
||||
sb.append(" Mutates with ");
|
||||
sb.append(Lang.joinHomogenous(targets));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolve(SpellAbility sa) {
|
||||
final Player p = sa.getActivatingPlayer();
|
||||
final Card host = sa.getHostCard();
|
||||
// There shouldn't be any mutate abilities, but for now.
|
||||
if (sa.isSpell()) {
|
||||
host.setController(p, 0);
|
||||
}
|
||||
|
||||
// 111.11. A copy of a permanent spell becomes a token as it resolves.
|
||||
// The token has the characteristics of the spell that became that token.
|
||||
// The token is not “created” for the purposes of any replacement effects or triggered abilities that refer to creating a token.
|
||||
if (host.isCopiedSpell()) {
|
||||
host.setCopiedSpell(false);
|
||||
host.setToken(true);
|
||||
}
|
||||
|
||||
final List<GameObject> targets = getDefinedOrTargeted(sa, "Defined");
|
||||
Card target = (Card)targets.get(0);
|
||||
|
||||
CardCollectionView view = CardCollection.getView(Lists.newArrayList(host, target));
|
||||
Card topCard = host.getController().getController().chooseSingleEntityForEffect(
|
||||
view,
|
||||
sa,
|
||||
"Choose which creature to be the top",
|
||||
false,
|
||||
new HashMap<>()
|
||||
);
|
||||
final boolean putOnTop = (topCard == host);
|
||||
|
||||
if (putOnTop) {
|
||||
host.addMergedCard(target);
|
||||
host.addMergedCards(target.getMergedCards());
|
||||
target.clearMergedCards();
|
||||
target.setMergedToCard(host);
|
||||
} else {
|
||||
target.addMergedCard(host);
|
||||
host.setMergedToCard(target);
|
||||
}
|
||||
|
||||
final Card c = p.getGame().getAction().moveToPlay(host, p, sa);
|
||||
sa.setHostCard(c);
|
||||
|
||||
p.getGame().getTriggerHandler().runTrigger(TriggerType.Mutates, AbilityKey.mapFromCard(c), false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -98,9 +98,11 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
// cards attached or otherwise linked to this card
|
||||
private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards, encodedCards;
|
||||
private CardCollection mustBlockCards, gainControlTargets, chosenCards, blockedThisTurn, blockedByThisTurn;
|
||||
private CardCollection mergedCards;
|
||||
|
||||
// if this card is attached or linked to something, what card is it currently attached to
|
||||
private Card encoding, cloneOrigin, haunting, effectSource, pairedWith, meldedWith;
|
||||
private Card mergedTo;
|
||||
|
||||
private SpellAbility effectSourceAbility = null;
|
||||
|
||||
@@ -994,6 +996,35 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
encoding = e;
|
||||
}
|
||||
|
||||
public final CardCollectionView getMergedCards() {
|
||||
return CardCollection.getView(mergedCards);
|
||||
}
|
||||
public final boolean hasMergedCard() {
|
||||
return FCollection.hasElements(mergedCards);
|
||||
}
|
||||
public final boolean hasMergedCard(Card c) {
|
||||
return FCollection.hasElement(mergedCards, c);
|
||||
}
|
||||
public final void addMergedCard(final Card c) {
|
||||
mergedCards = view.addCard(mergedCards, c, TrackableProperty.MergedCards);
|
||||
}
|
||||
public final void addMergedCards(final Iterable<Card> cards) {
|
||||
mergedCards = view.addCards(mergedCards, cards, TrackableProperty.MergedCards);
|
||||
}
|
||||
public final void removeMergedCard(final Card c) {
|
||||
mergedCards = view.removeCard(mergedCards, c, TrackableProperty.MergedCards);
|
||||
}
|
||||
public final void clearMergedCards() {
|
||||
mergedCards = view.clearCards(mergedCards, TrackableProperty.MergedCards);
|
||||
}
|
||||
|
||||
public final Card getMergedToCard() {
|
||||
return mergedTo;
|
||||
}
|
||||
public final void setMergedToCard(final Card c) {
|
||||
mergedTo = view.setCard(mergedTo, c, TrackableProperty.MergedTo);
|
||||
}
|
||||
|
||||
public final String getFlipResult(final Player flipper) {
|
||||
if (flipResult == null) {
|
||||
return null;
|
||||
@@ -1842,7 +1873,7 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
|
||||
|| keyword.startsWith("Amplify") || keyword.startsWith("Ninjutsu") || keyword.startsWith("Adapt")
|
||||
|| keyword.startsWith("Transfigure") || keyword.startsWith("Aura swap")
|
||||
|| keyword.startsWith("Cycling") || keyword.startsWith("TypeCycling")
|
||||
|| keyword.startsWith("Encore")) {
|
||||
|| keyword.startsWith("Encore") || keyword.startsWith("Mutate")) {
|
||||
// keyword parsing takes care of adding a proper description
|
||||
} else if (keyword.startsWith("CantBeBlockedByAmount")) {
|
||||
sbLong.append(getName()).append(" can't be blocked ");
|
||||
|
||||
@@ -4273,6 +4273,22 @@ public class CardFactoryUtil {
|
||||
} else {
|
||||
sa.addAnnounceVar("Multikicker");
|
||||
}
|
||||
} else if (keyword.startsWith("Mutate")) {
|
||||
final String[] params = keyword.split(":");
|
||||
final String cost = params[1];
|
||||
|
||||
final StringBuilder sbMutate = new StringBuilder();
|
||||
sbMutate.append("SP$ Mutate | Cost$ ");
|
||||
sbMutate.append(cost);
|
||||
sbMutate.append(" | Mutate True | ValidTgts$ Creature.YouOwn+nonHuman");
|
||||
|
||||
final SpellAbility sa = AbilityFactory.getAbility(sbMutate.toString(), card);
|
||||
sa.setDescription("Mutate " + ManaCostParser.parse(cost) +
|
||||
" (" + inst.getReminderText() + ")");
|
||||
sa.setStackDescription("Mutate - " + card.getName());
|
||||
sa.setAlternativeCost(AlternativeCost.Mutate);
|
||||
sa.setIntrinsic(intrinsic);
|
||||
inst.addSpellAbility(sa);
|
||||
} else if (keyword.startsWith("Ninjutsu")) {
|
||||
final String[] k = keyword.split(":");
|
||||
final String manacost = k[1];
|
||||
|
||||
@@ -507,6 +507,17 @@ public class CardView extends GameEntityView {
|
||||
return get(TrackableProperty.EncodedCards);
|
||||
}
|
||||
|
||||
public FCollectionView<CardView> getMergedCards() {
|
||||
return get(TrackableProperty.MergedCards);
|
||||
}
|
||||
public boolean hasMergedCards() {
|
||||
return getMergedCards() != null;
|
||||
}
|
||||
|
||||
public CardView getMergedTo() {
|
||||
return get(TrackableProperty.MergedTo);
|
||||
}
|
||||
|
||||
public GameEntityView getEntityAttachedTo() {
|
||||
return get(TrackableProperty.EntityAttachedTo);
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ public enum Keyword {
|
||||
MODULAR("Modular", Modular.class, false, "This creature enters the battlefield with {%d:+1/+1 counter} on it. When it dies, you may put its +1/+1 counters on target artifact creature."),
|
||||
MORPH("Morph", KeywordWithCost.class, false, "You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost."),
|
||||
MULTIKICKER("Multikicker", KeywordWithCost.class, false, "You may pay an additional %s any number of times as you cast this spell."),
|
||||
MUTATE("Mutate", KeywordWithCost.class, true, "If you cast this spell for its mutate cost, put it over or under target non-Human creature you own. They mutate into the creature on top plus all abilities under it."),
|
||||
MYRIAD("Myriad", SimpleKeyword.class, false, "Whenever this creature attacks, for each opponent other than defending player, you may create a token that's a copy of this creature that's tapped and attacking that player or a planeswalker they control. Exile the tokens at end of combat."),
|
||||
NINJUTSU("Ninjutsu", Ninjutsu.class, false, "%s, Return an unblocked attacker you control to hand: Put this card onto the battlefield from your %s tapped and attacking."),
|
||||
OUTLAST("Outlast", KeywordWithCost.class, false, "%s, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery."),
|
||||
|
||||
@@ -11,6 +11,7 @@ public enum AlternativeCost {
|
||||
Flashback,
|
||||
Foretold,
|
||||
Madness,
|
||||
Mutate,
|
||||
Offering,
|
||||
Outlast, // ActivatedAbility
|
||||
Prowl,
|
||||
|
||||
@@ -1219,6 +1219,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
||||
return isAlternativeCost(AlternativeCost.Madness);
|
||||
}
|
||||
|
||||
public final boolean isMutate() {
|
||||
return isAlternativeCost(AlternativeCost.Mutate);
|
||||
}
|
||||
|
||||
public final boolean isProwl() {
|
||||
return isAlternativeCost(AlternativeCost.Prowl);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package forge.game.trigger;
|
||||
|
||||
import forge.game.ability.AbilityKey;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import java.util.*;
|
||||
|
||||
public class TriggerMutates extends Trigger {
|
||||
public TriggerMutates(final Map<String, String> params, final Card host, final boolean intrinsic) {
|
||||
super(params, host, intrinsic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performTest(Map<AbilityKey, Object> runParams) {
|
||||
if (hasParam("ValidCard")) {
|
||||
return matchesValid(runParams.get(AbilityKey.Card), getParam("ValidCard").split(","),
|
||||
this.getHostCard());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTriggeringObjects(SpellAbility sa, Map<AbilityKey, Object> runParams) {
|
||||
sa.setTriggeringObject(AbilityKey.Card, runParams.get(AbilityKey.Card));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImportantStackObjects(SpellAbility sa) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Mutates").append(": ").append(sa.getTriggeringObject(AbilityKey.Card));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,7 @@ public enum TriggerType {
|
||||
LifeGained(TriggerLifeGained.class),
|
||||
LifeLost(TriggerLifeLost.class),
|
||||
LosesGame(TriggerLosesGame.class),
|
||||
Mutates(TriggerMutates.class),
|
||||
NewGame(TriggerNewGame.class),
|
||||
PayCumulativeUpkeep(TriggerPayCumulativeUpkeep.class),
|
||||
PayEcho(TriggerPayEcho.class),
|
||||
|
||||
@@ -470,6 +470,9 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
|
||||
first.setActivatingPlayer(sa.getActivatingPlayer());
|
||||
game.fireEvent(new GameEventCardStatsChanged(source));
|
||||
AbilityUtils.resolve(first);
|
||||
} else if (sa.isMutate()) {
|
||||
game.fireEvent(new GameEventCardStatsChanged(source));
|
||||
AbilityUtils.resolve(sa.getHostCard().getFirstSpellAbility());
|
||||
} else {
|
||||
// TODO: Spell fizzles, what's the best way to alert player?
|
||||
Log.debug(source.getName() + " ability fizzles.");
|
||||
|
||||
@@ -62,12 +62,14 @@ public enum TrackableProperty {
|
||||
PlayerMayLook(TrackableTypes.PlayerViewCollectionType, FreezeMode.IgnoresFreeze),
|
||||
EntityAttachedTo(TrackableTypes.GameEntityViewType),
|
||||
EncodedCards(TrackableTypes.CardViewCollectionType),
|
||||
MergedCards(TrackableTypes.CardViewCollectionType),
|
||||
GainControlTargets(TrackableTypes.CardViewCollectionType),
|
||||
CloneOrigin(TrackableTypes.CardViewType),
|
||||
|
||||
ImprintedCards(TrackableTypes.CardViewCollectionType),
|
||||
HauntedBy(TrackableTypes.CardViewCollectionType),
|
||||
Haunting(TrackableTypes.CardViewType),
|
||||
MergedTo(TrackableTypes.CardViewType),
|
||||
MustBlockCards(TrackableTypes.CardViewCollectionType),
|
||||
PairedWith(TrackableTypes.CardViewType),
|
||||
CurrentState(TrackableTypes.CardStateViewType, FreezeMode.IgnoresFreeze),
|
||||
|
||||
@@ -685,6 +685,21 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
|
||||
}
|
||||
toPanel.getAttachedPanels().clear();
|
||||
|
||||
// Treat merged cards like attached cards
|
||||
if (card.hasMergedCards()) {
|
||||
final Iterable<CardView> merged = card.getMergedCards();
|
||||
for (final CardView c : merged) {
|
||||
final CardPanel cardC = getCardPanel(c.getId());
|
||||
if (cardC != null) {
|
||||
if (cardC.getAttachedToPanel() != toPanel) {
|
||||
cardC.setAttachedToPanel(toPanel);
|
||||
needLayoutRefresh = true; //ensure layout refreshed if any attachments change
|
||||
}
|
||||
toPanel.getAttachedPanels().add(cardC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (card.hasCardAttachments()) {
|
||||
final Iterable<CardView> enchants = card.getAttachedCards();
|
||||
for (final CardView e : enchants) {
|
||||
@@ -699,11 +714,13 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
|
||||
}
|
||||
}
|
||||
|
||||
// Treat merged cards like attached cards
|
||||
CardPanel attachedToPanel;
|
||||
if (card.getAttachedTo() != null) {
|
||||
if (card.getMergedTo() != null) {
|
||||
attachedToPanel = getCardPanel(card.getMergedTo().getId());
|
||||
} else if (card.getAttachedTo() != null) {
|
||||
attachedToPanel = getCardPanel(card.getAttachedTo().getId());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
attachedToPanel = null;
|
||||
}
|
||||
if (toPanel.getAttachedToPanel() != attachedToPanel) {
|
||||
|
||||
@@ -286,6 +286,17 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
|
||||
attachedPanels.clear();
|
||||
|
||||
// Treat merged cards like attached cards
|
||||
if (card.hasMergedCards()) {
|
||||
final Iterable<CardView> merged = card.getMergedCards();
|
||||
for (final CardView c : merged) {
|
||||
final CardAreaPanel cardC = CardAreaPanel.get(c);
|
||||
if (cardC != null) {
|
||||
attachedPanels.add(cardC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (card.hasCardAttachments()) {
|
||||
final Iterable<CardView> enchants = card.getAttachedCards();
|
||||
for (final CardView e : enchants) {
|
||||
@@ -296,7 +307,10 @@ public abstract class VCardDisplayArea extends VDisplayArea implements ActivateH
|
||||
}
|
||||
}
|
||||
|
||||
if (card.getAttachedTo() != null) {
|
||||
if (card.getMergedTo() != null ) {
|
||||
setAttachedToPanel(CardAreaPanel.get(card.getMergedTo()));
|
||||
}
|
||||
else if (card.getAttachedTo() != null) {
|
||||
setAttachedToPanel(CardAreaPanel.get(card.getAttachedTo()));
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -536,6 +536,20 @@ public class CardDetailUtil {
|
||||
area.append("Encoded: ").append(card.getEncodedCards());
|
||||
}
|
||||
|
||||
// Merge
|
||||
if (card.getMergedCards() != null) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("Merged: ").append(card.getMergedCards());
|
||||
}
|
||||
if (card.getMergedTo() != null) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("Merged to: ").append(card.getMergedTo());
|
||||
}
|
||||
|
||||
// must block
|
||||
if (card.getMustBlockCards() != null) {
|
||||
if (area.length() != 0) {
|
||||
|
||||
Reference in New Issue
Block a user