diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index b2b8fa39ffa..b04ba570a51 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -290,7 +290,6 @@ public class GameCopier { // TODO: Is this correct? Does it not duplicate keywords from enchantments and such? for (KeywordInterface kw : c.getHiddenExtrinsicKeywords()) newCard.addHiddenExtrinsicKeyword(kw); - newCard.setExtrinsicKeyword(Lists.newArrayList(c.getExtrinsicKeyword())); if (c.isTapped()) { newCard.setTapped(true); } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index 258d0a4e272..6fd213ea43c 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -204,7 +204,8 @@ public final class AbilityFactory { else if (api == ApiType.PermanentCreature || api == ApiType.PermanentNoncreature) { // If API is a permanent type, and creating AF Spell // Clear out the auto created SpellPemanent spell - if (type == AbilityRecordType.Spell && !mapParams.containsKey("SubAbility")) { + if (type == AbilityRecordType.Spell + && !mapParams.containsKey("SubAbility") && !mapParams.containsKey("NonBasicSpell")) { hostCard.clearFirstSpell(); } } @@ -389,6 +390,10 @@ public final class AbilityFactory { sa.setBasicSpell(false); } + if (mapParams.containsKey("Dash")) { + sa.setDash(true); + } + if (mapParams.containsKey("Outlast")) { sa.setOutlast(true); } diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index 6c468779186..a0d4e8b10b4 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -238,7 +238,7 @@ public abstract class SpellAbilityEffect { } sb.append(Lang.joinHomogenous(crds)); if (location.equals("Hand")) { - sb.append("to your hand").append(" "); + sb.append(" to your hand"); } sb.append(" at the "); if (combat) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index 72ae0a53850..7292fcc8325 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -134,6 +134,9 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { && !sa.hasParam("Shuffle") && cards.size() >= 2 && !random) { Player p = AbilityUtils.getDefinedPlayers(source, sa.getParamOrDefault("DefinedPlayer", "You"), sa).get(0); cards = (CardCollection) p.getController().orderMoveToZoneList(cards, destination); + //the last card in this list will be the closest to the top, but we want the first card to be closest. + //so reverse it here before moving them to the library. + java.util.Collections.reverse(cards); } if (destination == ZoneType.Graveyard) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 4397465f47c..3a053f5937a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -537,10 +537,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { tgtC.getController().getZone(destination), tgtC, sa, null); if (sa.hasParam("Unearth")) { movedCard.setUnearthed(true); - movedCard.addExtrinsicKeyword("Haste"); + movedCard.addChangedCardKeywords(Lists.newArrayList("Haste"), null, false, false, + game.getNextTimestamp(), true); registerDelayedTrigger(sa, "Exile", Lists.newArrayList(movedCard)); addLeaveBattlefieldReplacement(movedCard, sa, "Exile"); - movedCard.updateStateForView(); } if (sa.hasParam("FaceDown")) { movedCard.turnFaceDown(true); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java index a4a1ae8e869..b8af9a537ad 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java @@ -3,6 +3,8 @@ package forge.game.ability.effects; import java.util.Arrays; import java.util.List; +import com.google.common.collect.Lists; + import forge.GameCommand; import forge.card.mana.ManaCost; import forge.game.Game; @@ -11,6 +13,7 @@ import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardCollectionView; import forge.game.card.CardLists; +import forge.game.event.GameEventCardStatsChanged; import forge.game.player.Player; import forge.game.spellability.Ability; import forge.game.spellability.SpellAbility; @@ -35,23 +38,26 @@ public class ControlGainEffect extends SpellAbilityEffect { if (tgts.isEmpty()) { sb.append(" (nothing)"); } else { - for (final Card c : getDefinedCards(sa)) { - sb.append(" "); - if (c.isFaceDown()) { - sb.append("Face-down creature (").append(c.getId()).append(')'); - } else { - sb.append(c); - } - } + for (final Card c : tgts) { + sb.append(" "); + if (c.isFaceDown()) { + sb.append("Face-down creature (").append(c.getId()).append(')'); + } else { + sb.append(c); + } + } } sb.append("."); + if (sa.hasParam("Untap")) { + sb.append(" Untap it."); + } + return sb.toString(); } private static void doLoseControl(final Card c, final Card host, - final boolean tapOnLose, final List addedKeywords, - final long tStamp) { + final boolean tapOnLose, final long tStamp) { if (null == c || c.hasKeyword("Other players can't gain control of CARDNAME.")) { return; } @@ -61,12 +67,6 @@ public class ControlGainEffect extends SpellAbilityEffect { if (tapOnLose) { c.tap(); } - - if (null != addedKeywords) { - for (final String kw : addedKeywords) { - c.removeExtrinsicKeyword(kw); - } - } } // if host.removeGainControlTargets(c); @@ -82,7 +82,7 @@ public class ControlGainEffect extends SpellAbilityEffect { final boolean remember = sa.hasParam("RememberControlled"); final boolean forget = sa.hasParam("ForgetControlled"); final List destroyOn = sa.hasParam("DestroyTgt") ? Arrays.asList(sa.getParam("DestroyTgt").split(",")) : null; - final List kws = sa.hasParam("AddKWs") ? Arrays.asList(sa.getParam("AddKWs").split(" & ")) : null; + final List keywords = sa.hasParam("AddKWs") ? Arrays.asList(sa.getParam("AddKWs").split(" & ")) : null; final List lose = sa.hasParam("LoseControl") ? Arrays.asList(sa.getParam("LoseControl").split(",")) : null; final List controllers = getDefinedPlayersOrTargeted(sa, "NewController"); @@ -125,15 +125,22 @@ public class ControlGainEffect extends SpellAbilityEffect { tgtC.untap(); } - if (null != kws) { - for (final String kw : kws) { - tgtC.addExtrinsicKeyword(kw); - if (kw.equals("Haste")) { - tgtC.updateStateForView(); // ensure that summoning sickness icon is removed + final List kws = Lists.newArrayList(); + if (null != keywords) { + for (final String kw : keywords) { + if (kw.startsWith("HIDDEN")) { + tgtC.addHiddenExtrinsicKeyword(kw); + } else { + kws.add(kw); } } } + if (!kws.isEmpty()) { + tgtC.addChangedCardKeywords(kws, Lists.newArrayList(), false, false, tStamp); + game.fireEvent(new GameEventCardStatsChanged(tgtC)); + } + if (remember && !sa.getHostCard().isRemembered(tgtC)) { sa.getHostCard().addRemembered(tgtC); } @@ -143,7 +150,7 @@ public class ControlGainEffect extends SpellAbilityEffect { } if (lose != null) { - final GameCommand loseControl = getLoseControlCommand(tgtC, tStamp, bTapOnLose, source, kws); + final GameCommand loseControl = getLoseControlCommand(tgtC, tStamp, bTapOnLose, source); if (lose.contains("LeavesPlay")) { sa.getHostCard().addLeavesPlayCommand(loseControl); } @@ -183,6 +190,26 @@ public class ControlGainEffect extends SpellAbilityEffect { } } + if (keywords != null) { + // Add keywords only until end of turn + final GameCommand untilKeywordEOT = new GameCommand() { + private static final long serialVersionUID = -42244224L; + + @Override + public void run() { + if (keywords.size() > 0) { + for (String kw : keywords) { + if (kw.startsWith("HIDDEN")) { + tgtC.removeHiddenExtrinsicKeyword(kw); + } + } + tgtC.removeChangedCardKeywords(tStamp); + } + } + }; + game.getEndOfTurn().addUntil(untilKeywordEOT); + } + game.getAction().controllerChangeZoneCorrection(tgtC); } // end foreach target } @@ -236,14 +263,13 @@ public class ControlGainEffect extends SpellAbilityEffect { * @return a {@link forge.GameCommand} object. */ private static GameCommand getLoseControlCommand(final Card c, - final long tStamp, final boolean bTapOnLose, final Card hostCard, - final List kws) { + final long tStamp, final boolean bTapOnLose, final Card hostCard) { final GameCommand loseControl = new GameCommand() { private static final long serialVersionUID = 878543373519872418L; @Override public void run() { - doLoseControl(c, hostCard, bTapOnLose, kws, tStamp); + doLoseControl(c, hostCard, bTapOnLose, tStamp); c.getSVars().remove("SacMe"); } }; diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index 7d8a8d4cab4..f3e66bad91b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -83,10 +83,6 @@ public class DigEffect extends SpellAbilityEffect { boolean changeAll = false; boolean allButOne = false; - final List keywords = new ArrayList(); - if (sa.hasParam("Keywords")) { - keywords.addAll(Arrays.asList(sa.getParam("Keywords").split(" & "))); - } if (sa.hasParam("ChangeNum")) { if (sa.getParam("ChangeNum").equalsIgnoreCase("All")) { @@ -307,9 +303,6 @@ public class DigEffect extends SpellAbilityEffect { else { c = game.getAction().moveTo(zone, c, sa); if (destZone1.equals(ZoneType.Battlefield)) { - for (final String kw : keywords) { - c.addExtrinsicKeyword(kw); - } if (sa.hasParam("Tapped")) { c.setTapped(true); } @@ -377,11 +370,7 @@ public class DigEffect extends SpellAbilityEffect { if (!origin.equals(c.getZone().getZoneType())) { table.put(origin, c.getZone().getZoneType(), c); } - if (destZone2 == ZoneType.Battlefield && !keywords.isEmpty()) { - for (final String kw : keywords) { - c.addExtrinsicKeyword(kw); - } - } else if (destZone2 == ZoneType.Exile) { + if (destZone2 == ZoneType.Exile) { if (sa.hasParam("ExileWithCounter")) { c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, true, counterTable); diff --git a/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java index 3324a88a9a0..f916ee8b67e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java @@ -1,5 +1,7 @@ package forge.game.ability.effects; +import com.google.common.collect.Lists; + import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.player.Player; @@ -25,9 +27,8 @@ public class PermanentEffect extends SpellAbilityEffect { // some extra for Dashing if (sa.isDash()) { - c.addExtrinsicKeyword("Haste"); c.setSVar("EndOfTurnLeavePlay", "Dash"); - c.updateKeywords(); + registerDelayedTrigger(sa, "Hand", Lists.newArrayList(c)); } } } 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 900da08cee7..26c21399155 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -94,7 +94,6 @@ public class Card extends GameEntity implements Comparable { private final CardDamageHistory damageHistory = new CardDamageHistory(); private Map> countersAddedBy = Maps.newTreeMap(); - private KeywordCollection extrinsicKeyword = new KeywordCollection(); // Hidden keywords won't be displayed on the card private final KeywordCollection hiddenExtrinsicKeyword = new KeywordCollection(); @@ -3727,7 +3726,6 @@ public class Card extends GameEntity implements Comparable { if (!removeIntrinsic) { keywords.insertAll(state.getIntrinsicKeywords()); } - keywords.insertAll(extrinsicKeyword.getValues()); // see if keyword changes are in effect for (final KeywordsChange ck : changedCardKeywords.values()) { @@ -3755,11 +3753,6 @@ public class Card extends GameEntity implements Comparable { return; } } - for (KeywordInterface kw : extrinsicKeyword.getValues()) { - if (!visitor.visit(kw)) { - return; - } - } } else { for (KeywordInterface kw : getUnhiddenKeywords(state)) { if (!visitor.visit(kw)) { @@ -3927,53 +3920,6 @@ public class Card extends GameEntity implements Comparable { } } - public Collection getExtrinsicKeyword() { - return extrinsicKeyword.getValues(); - } - public final void setExtrinsicKeyword(final List a) { - extrinsicKeyword.clear(); - extrinsicKeyword.addAll(a); - } - public void setExtrinsicKeyword(Collection extrinsicKeyword2) { - extrinsicKeyword.clear(); - extrinsicKeyword.insertAll(extrinsicKeyword2); - } - - public void addExtrinsicKeyword(final String s) { - if (s.startsWith("HIDDEN")) { - addHiddenExtrinsicKeyword(s); - } - else { - extrinsicKeyword.add(s); - } - } - - public void removeExtrinsicKeyword(final String s) { - if (s.startsWith("HIDDEN")) { - removeHiddenExtrinsicKeyword(s); - } - else if (extrinsicKeyword.remove(s)) { - currentState.getView().updateKeywords(this, currentState); - } - } - - public void removeAllExtrinsicKeyword(final String s) { - final List strings = Lists.newArrayList(); - strings.add(s); - boolean needKeywordUpdate = false; - if (extrinsicKeyword.removeAll(strings)) { - needKeywordUpdate = true; - } - strings.add("HIDDEN " + s); - if (hiddenExtrinsicKeyword.removeAll(strings)) { - view.updateNonAbilityText(this); - needKeywordUpdate = true; - } - if (needKeywordUpdate) { - currentState.getView().updateKeywords(this, currentState); - } - } - // Hidden Keywords will be returned without the indicator HIDDEN public final List getHiddenExtrinsicKeywords() { ListKeywordVisitor visitor = new ListKeywordVisitor(); 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 a958d18e9ae..5ed5219df8a 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3850,23 +3850,11 @@ public class CardFactoryUtil { inst.addSpellAbility(sa); } else if (keyword.startsWith("Dash")) { final String[] k = keyword.split(":"); - final String dashString = "SP$ PermanentCreature | Cost$ " + k[1] + " | SubAbility$" - + " DashDelayedTrigger"; - final String dbDelayTrigger = "DB$ DelayedTrigger | Mode$ Phase | Phase$" - + " End of Turn | Execute$ DashReturnSelf | RememberObjects$ Self" - + " | TriggerDescription$ Return CARDNAME from the battlefield to" + " its owner's hand."; - final String dbReturn = "DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand" - + " | Defined$ DelayTriggerRemembered"; - card.setSVar("DashDelayedTrigger", dbDelayTrigger); - card.setSVar("DashReturnSelf", dbReturn); + final String dashString = "SP$ PermanentCreature | Cost$ " + k[1] + " | StackDescription$ CARDNAME (Dash)" + + " | Dash$ True | NonBasicSpell$ True" + + " | SpellDescription$ Dash " + ManaCostParser.parse(k[1]) + " (" + inst.getReminderText() + ")"; final SpellAbility newSA = AbilityFactory.getAbility(dashString, card); - String desc = "Dash " + ManaCostParser.parse(k[1]) + " (" + inst.getReminderText() - + ")"; - newSA.setStackDescription(card.getName() + " (Dash)"); - newSA.setDescription(desc); - newSA.setBasicSpell(false); - newSA.setDash(true); newSA.setIntrinsic(intrinsic); newSA.setTemporary(!intrinsic); @@ -4527,6 +4515,8 @@ public class CardFactoryUtil { card.setSVar("CipherTrigger", trig); card.setSVar("PlayEncoded", ab); + } else if (keyword.startsWith("Dash")) { + effect = "Mode$ Continuous | Affected$ Card.Self+dashed | AddKeyword$ Haste"; } else if (keyword.equals("Devoid")) { effect = "Mode$ Continuous | EffectZone$ All | Affected$ Card.Self" + " | CharacteristicDefining$ True | SetColor$ Colorless | Secondary$ True" + diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 8828cc02790..6be6628b541 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1639,6 +1639,11 @@ public class CardProperty { return false; } return card.getCastSA().isSurged(); + } else if (property.startsWith("dashed")) { + if (card.getCastSA() == null) { + return false; + } + return card.getCastSA().isDash(); } else if (property.startsWith("evoked")) { if (card.getCastSA() == null) { return false; diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index 1c177ec82fa..f96d0f8af38 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -255,7 +255,6 @@ public final class CardUtil { newCopy.setBaseToughness(in.getCurrentToughness() + in.getTempToughnessBoost() + in.getSemiPermanentToughnessBoost()); newCopy.setCounters(Maps.newEnumMap(in.getCounters())); - newCopy.setExtrinsicKeyword(in.getExtrinsicKeyword()); newCopy.setColor(in.determineColor().getColor()); newCopy.setReceivedDamageFromThisTurn(in.getReceivedDamageFromThisTurn()); diff --git a/forge-game/src/main/java/forge/game/phase/Untap.java b/forge-game/src/main/java/forge/game/phase/Untap.java index 7c3106b9364..7835a67cdaf 100644 --- a/forge-game/src/main/java/forge/game/phase/Untap.java +++ b/forge-game/src/main/java/forge/game/phase/Untap.java @@ -209,11 +209,10 @@ public class Untap extends Phase { // Remove temporary keywords for (final Card c : player.getCardsIn(ZoneType.Battlefield)) { - c.removeAllExtrinsicKeyword("This card doesn't untap during your next untap step."); - c.removeAllExtrinsicKeyword("HIDDEN This card doesn't untap during your next untap step."); + c.removeHiddenExtrinsicKeyword("This card doesn't untap during your next untap step."); if (c.hasKeyword("This card doesn't untap during your next two untap steps.")) { - c.removeAllExtrinsicKeyword("HIDDEN This card doesn't untap during your next two untap steps."); - c.addHiddenExtrinsicKeyword("HIDDEN This card doesn't untap during your next untap step."); + c.removeHiddenExtrinsicKeyword("This card doesn't untap during your next two untap steps."); + c.addHiddenExtrinsicKeyword("This card doesn't untap during your next untap step."); } }