diff --git a/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java index 18b5da88345..2f931ec2dab 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java @@ -3,8 +3,8 @@ package forge.game.ability.effects; import java.util.*; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; +import forge.card.CardStateName; import forge.game.Game; import forge.game.GameObject; import forge.game.ability.AbilityKey; @@ -13,41 +13,9 @@ import forge.game.card.*; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; -import forge.util.Lang; public class MutateEffect extends SpellAbilityEffect { - private void migrateTopCard(final Card host, final Card target) { - // Copy all status from target card and migrate all counters - // Also update all reference of target card to new top card - - // TODO: find out all necessary status that should be copied - host.setTapped(target.isTapped()); - host.setSickness(target.isFirstTurnControlled()); - host.setFlipped(target.isFlipped()); - host.setDamage(target.getDamage()); - host.setMonstrous(target.isMonstrous()); - host.setRenowned(target.isRenowned()); - - // Migrate counters - Map counters = target.getCounters(); - if (!counters.isEmpty()) { - host.setCounters(Maps.newHashMap(counters)); - } - target.clearCounters(); - - // Migrate attached cards - CardCollectionView attached = target.getAttachedCards(); - for (final Card c : attached) { - c.setEntityAttachedTo(host); - } - target.setAttachedCards(null); - host.setAttachedCards(attached); - - // TODO: move all remembered, imprinted objects to new top card - // and possibly many other needs to be migrated. - } - @Override public void resolve(SpellAbility sa) { final Player p = sa.getActivatingPlayer(); @@ -89,33 +57,34 @@ public class MutateEffect extends SpellAbilityEffect { host.setMergedToCard(target); } - // Now the top card always have all abilities from bottom cards + + // First remove current mutated states + if (target.getMutatedTimestamp() != -1) { + target.removeCloneState(target.getMutatedTimestamp()); + target.setMutatedTimestamp(-1); + } + // Now add all abilities from bottom cards final Long ts = game.getNextTimestamp(); + if (topCard.getCurrentStateName() != CardStateName.FaceDown) { + final CardCloneStates mutatedStates = CardFactory.getMutatedCloneStates(topCard, sa); + topCard.addCloneState(mutatedStates, ts); + topCard.setMutatedTimestamp(ts); + } if (topCard == target) { - final CardCloneStates cloneStates = CardFactory.getCloneStates(target, target, sa); - final CardState targetState = cloneStates.get(target.getCurrentStateName()); - final CardState newState = host.getCurrentState(); - targetState.addAbilitiesFrom(newState, false); - target.addCloneState(cloneStates, ts); // Re-register triggers for target card game.getTriggerHandler().clearActiveTriggers(target, null); game.getTriggerHandler().registerActiveTrigger(target, false); - } else { - final CardCloneStates cloneStates = CardFactory.getCloneStates(host, host, sa); - final CardState newState = cloneStates.get(host.getCurrentStateName()); - final CardState targetState = target.getCurrentState(); - newState.addAbilitiesFrom(targetState, false); - host.addCloneState(cloneStates, ts); } game.getAction().moveToPlay(host, p, sa); if (topCard == host) { - migrateTopCard(host, target); + CardFactory.migrateTopCard(host, target); } else { host.setTapped(target.isTapped()); host.setFlipped(target.isFlipped()); } + topCard.setTimesMutated(topCard.getTimesMutated() + 1); game.getTriggerHandler().runTrigger(TriggerType.Mutates, AbilityKey.mapFromCard(topCard), false); } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 4d8ed12491b..e345d723114 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -185,6 +185,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private long bestowTimestamp = -1; private long transformedTimestamp = 0; + private long mutatedTimestamp = -1; + private int timesMutated = 0; private boolean tributed = false; private boolean embalmed = false; private boolean eternalized = false; @@ -822,7 +824,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } public boolean isCloned() { - return !clonedStates.isEmpty(); + return !clonedStates.isEmpty() && clonedStates.lastEntry().getKey() != mutatedTimestamp; } public final CardCollectionView getDevouredCards() { @@ -1029,6 +1031,24 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return !getMergedCards().isEmpty() || getMergedToCard() != null; } + public final boolean isMutated() { + return mutatedTimestamp != -1; + } + + public final long getMutatedTimestamp() { + return mutatedTimestamp; + } + public final void setMutatedTimestamp(final long t) { + mutatedTimestamp = t; + } + + public final int getTimesMutated() { + return timesMutated; + } + public final void setTimesMutated(final int t) { + timesMutated = t; + } + public final String getFlipResult(final Player flipper) { if (flipResult == null) { return null; diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index dce30d6d5b7..370a7e40ede 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -20,6 +20,8 @@ package forge.game.card; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + import forge.ImageKeys; import forge.StaticData; import forge.card.*; @@ -820,4 +822,50 @@ public class CardFactory { return result; } + public static CardCloneStates getMutatedCloneStates(final Card card, final CardTraitBase sa) { + final CardStateName state = card.getCurrentStateName(); + final CardState ret = new CardState(card, state); + ret.copyFrom(card.getState(state, true), false); + + for (final Card c : card.getMergedCards()) { + ret.addAbilitiesFrom(c.getCurrentState(), false); + } + + final CardCloneStates result = new CardCloneStates(card, sa); + result.put(state, ret); + return result; + } + + public static void migrateTopCard(final Card host, final Card target) { + // Copy all status from target card and migrate all counters + // Also update all reference of target card to new top card + + // TODO: find out all necessary status that should be copied + host.setTapped(target.isTapped()); + host.setSickness(target.isFirstTurnControlled()); + host.setFlipped(target.isFlipped()); + host.setDamage(target.getDamage()); + host.setTimesMutated(target.getTimesMutated()); + host.setMonstrous(target.isMonstrous()); + host.setRenowned(target.isRenowned()); + + // Migrate counters + Map counters = target.getCounters(); + if (!counters.isEmpty()) { + host.setCounters(Maps.newHashMap(counters)); + } + target.clearCounters(); + + // Migrate attached cards + CardCollectionView attached = target.getAttachedCards(); + for (final Card c : attached) { + c.setEntityAttachedTo(host); + } + target.setAttachedCards(null); + host.setAttachedCards(attached); + + // TODO: move all remembered, imprinted objects to new top card + // and possibly many other needs to be migrated. + } + } // end class AbstractCardFactory diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index cfa82df2d7a..45a97eda506 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1319,6 +1319,9 @@ public class CardFactoryUtil { if (sq[0].contains("TimesPseudokicked")) { return doXMath(c.getPseudoKickerMagnitude(), m, c); } + if (sq[0].contains("TimesMutated")) { + return doXMath(c.getTimesMutated(), m, c); + } // Count$IfCastInOwnMainPhase.. // 7/10 if (sq[0].contains("IfCastInOwnMainPhase")) { diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index 2098be3399b..ea9b82a53d5 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -68,7 +68,7 @@ public class CardState extends GameObject implements IHasSVars { private Map sVars = Maps.newTreeMap(); private KeywordCollection cachedKeywords = new KeywordCollection(); - + private CardRarity rarity = CardRarity.Unknown; private String setCode = CardEdition.UNKNOWN.getCode(); @@ -138,7 +138,7 @@ public class CardState extends GameObject implements IHasSVars { view.updateType(this); } } - + public final void setCreatureTypes(Collection ctypes) { if (type.setCreatureTypes(ctypes)) { view.updateType(this); @@ -595,17 +595,11 @@ public class CardState extends GameObject implements IHasSVars { } } - staticAbilities.clear(); for (StaticAbility sa : source.staticAbilities) { if (sa.isIntrinsic()) { staticAbilities.add(sa.copy(card, lki)); } } - - // Not sure if this is needed - if (lki && source.loyaltyRep != null) { - this.loyaltyRep = source.loyaltyRep.copy(card, lki); - } } public CardState copy(final Card host, CardStateName name, final boolean lki) { @@ -625,11 +619,11 @@ public class CardState extends GameObject implements IHasSVars { public String getSetCode() { return setCode; } - + public CardTypeView getTypeWithChanges() { return getType().getTypeWithChanges(card.getChangedCardTypes()); } - + public void setSetCode(String setCode0) { setCode = setCode0; view.updateSetCode(this); diff --git a/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java b/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java index 4839ed8557f..99b8e277b77 100644 --- a/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java +++ b/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -26,7 +26,7 @@ import forge.game.player.Player; *

* PlayerZoneComesIntoPlay class. *

- * + * * @author Forge * @version $Id$ @@ -62,7 +62,7 @@ public class PlayerZoneBattlefield extends PlayerZone { super.add(c, position, latestState); - if (trigger && !c.isMerged()) { + if (trigger) { c.setSickness(true); // summoning sickness c.runComesIntoPlayCommands(); } diff --git a/forge-gui/res/cardsfolder/i/insatiable_hemophage.txt b/forge-gui/res/cardsfolder/i/insatiable_hemophage.txt new file mode 100644 index 00000000000..6d728ee65eb --- /dev/null +++ b/forge-gui/res/cardsfolder/i/insatiable_hemophage.txt @@ -0,0 +1,11 @@ +Name:Insatiable Hemophage +ManaCost:3 B +Types:Creature Nightmare +PT:3/3 +K:Mutate:2 B +K:Deathtouch +T:Mode$ Mutates | ValidCard$ Card.Self | Execute$ TrigLoseLife | TriggerDescription$ Whenever this creature mutates, each opponent loses X life and you gain X life, where X is the number of times this creature has mutated. +SVar:TrigLoseLife:DB$ LoseLife | Defined$ Opponent | LifeAmount$ X | References$ X | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X | References$ X +SVar:X:Count$TimesMutated +Oracle:Mutate {2}{B} (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 from under it.)\nDeathtouch\nWhenever this creature mutates, each opponent loses X life and you gain X life, where X is the number of times this creature has mutated. \ No newline at end of file