diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40.json index 6cf700287..b08212856 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set40.json @@ -758,6 +758,88 @@ } } }, + "40_27": { + "title": "*Second Wind", + "side": "free people", + "culture": "dwarven", + "cost": 2, + "type": "condition", + "keyword": "support area", + "effects": { + "type": "trigger", + "trigger": { + "type": "startOfPhase", + "phase": "maneuver" + }, + "optional": true, + "cost": { + "type": "discardStackedCards", + "on": "self" + }, + "effect": { + "type": "heal", + "filter": "choose(dwarf,companion)" + } + } + }, + "40_28": { + "title": "*Seven for the Dwarf-lords", + "side": "free people", + "culture": "dwarven", + "cost": 0, + "type": "artifact", + "keyword": "support area", + "effects": [ + { + "type": "activated", + "phase": "fellowship", + "effect": { + "type": "stackCardsFromHand", + "where": "self", + "filter": "choose(culture(dwarven))" + } + }, + { + "type": "activated", + "phase": "skirmish", + "cost": { + "type": "discardStackedCards", + "on": "self", + "count": 2 + }, + "effect": { + "type": "playCardFromDiscard", + "filter": "choose(culture(dwarven),skirmish,event)" + } + }, + { + "type": "activated", + "phase": "regroup", + "limitPerPhase": 1, + "cost": [ + { + "type": "discardStackedCards", + "on": "self", + "count": 3 + }, + { + "type": "exert", + "filter": "choose(dwarf)", + "times": 2, + "memorize": "exertedDwarf" + } + ], + "effect": { + "type": "drawCards", + "count": { + "type": "forEachKeywordOnCardInMemory", + "memory": "exertedDwarf", + "keyword": "damage" + } + } + } + ] + }, "40_184": { "title": "Black Steed", "subtitle": "Bred to Serve", diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/ActivatedEffectProcessor.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/ActivatedEffectProcessor.java index c070993f3..fb3387cfe 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/ActivatedEffectProcessor.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/ActivatedEffectProcessor.java @@ -5,16 +5,24 @@ import com.gempukku.lotro.cards.build.CardGenerationEnvironment; import com.gempukku.lotro.cards.build.InvalidCardDefinitionException; import com.gempukku.lotro.cards.build.field.EffectProcessor; import com.gempukku.lotro.cards.build.field.FieldUtils; +import com.gempukku.lotro.cards.build.field.effect.appender.AbstractEffectAppender; import com.gempukku.lotro.common.Phase; +import com.gempukku.lotro.game.PhysicalCard; +import com.gempukku.lotro.game.state.LotroGame; +import com.gempukku.lotro.logic.actions.CostToEffectAction; +import com.gempukku.lotro.logic.effects.IncrementPhaseLimitEffect; +import com.gempukku.lotro.logic.timing.Effect; +import com.gempukku.lotro.logic.timing.EffectResult; import com.gempukku.lotro.logic.timing.PlayConditions; import org.json.simple.JSONObject; public class ActivatedEffectProcessor implements EffectProcessor { @Override public void processEffect(JSONObject value, BuiltLotroCardBlueprint blueprint, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(value, "phase", "requirement", "cost", "effect"); + FieldUtils.validateAllowedFields(value, "phase", "requirement", "cost", "effect", "limitPerPhase"); final String[] phaseArray = FieldUtils.getStringArray(value.get("phase"), "phase"); + final int limitPerPhase = FieldUtils.getInteger(value.get("limitPerPhase"), "limitPerPhase", 0); if (phaseArray.length == 0) throw new InvalidCardDefinitionException("Unable to find phase for an activated effect"); @@ -23,6 +31,22 @@ public class ActivatedEffectProcessor implements EffectProcessor { final Phase phase = Phase.valueOf(phaseString.toUpperCase()); DefaultActionSource actionSource = new DefaultActionSource(); + if (limitPerPhase > 0) { + actionSource.addPlayRequirement( + (action, playerId, game, self, effectResult, effect) -> PlayConditions.checkPhaseLimit(game, self, phase, limitPerPhase)); + actionSource.addCost( + new AbstractEffectAppender() { + @Override + public boolean isPlayableInFull(CostToEffectAction action, String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect) { + return true; + } + + @Override + protected Effect createEffect(CostToEffectAction action, String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect) { + return new IncrementPhaseLimitEffect(self, phase, limitPerPhase); + } + }); + } actionSource.addPlayRequirement( (action, playerId, game, self, effectResult, effect) -> PlayConditions.isPhase(game, phase)); EffectUtils.processRequirementsCostsAndEffects(value, environment, actionSource); diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectAppender.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectAppender.java index 98f11d3ee..437fe883b 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectAppender.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectAppender.java @@ -12,4 +12,8 @@ public interface EffectAppender { void appendEffect(CostToEffectAction action, String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect); boolean isPlayableInFull(CostToEffectAction action, String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect); + + default boolean isPlayabilityCheckedForEffect() { + return false; + } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectAppenderFactory.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectAppenderFactory.java index 7ec79fb8a..ce66f424c 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectAppenderFactory.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectAppenderFactory.java @@ -46,6 +46,7 @@ public class EffectAppenderFactory { effectAppenderProducers.put("putcardsfromdiscardintohand", new PutCardsFromDiscardIntoHand()); effectAppenderProducers.put("addtrigger", new AddTrigger()); effectAppenderProducers.put("stackplayedevent", new StackPlayedEvent()); + effectAppenderProducers.put("playcardfromdiscard", new PlayCardFromDiscard()); } public EffectAppender getEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectUtils.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectUtils.java index f9a335a81..99d3aecce 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectUtils.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/EffectUtils.java @@ -23,14 +23,19 @@ public class EffectUtils { for (JSONObject cost : costArray) { final EffectAppender effectAppender = effectAppenderFactory.getEffectAppender(cost, environment); actionSource.addPlayRequirement( - (action, playerId, game, self, effectResult, effect) -> effectAppender.isPlayableInFull(action, playerId, game, self, effectResult, effect) - ); + (action, playerId, game, self, effectResult, effect) -> effectAppender.isPlayableInFull(action, playerId, game, self, effectResult, effect)); actionSource.addCost(effectAppender); } final JSONObject[] effectArray = FieldUtils.getObjectArray(value.get("effect"), "effect"); for (JSONObject effect : effectArray) { final EffectAppender effectAppender = effectAppenderFactory.getEffectAppender(effect, environment); + if (effectAppender == null) { + System.out.println(""); + } + if (effectAppender.isPlayabilityCheckedForEffect()) + actionSource.addPlayRequirement( + (action, playerId, game, self, effectResult, effect1) -> effectAppender.isPlayableInFull(action, playerId, game, self, effectResult, effect1)); actionSource.addEffect(effectAppender); } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/MultiEffectAppender.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/MultiEffectAppender.java index 831b96f1b..0077a9fff 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/MultiEffectAppender.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/MultiEffectAppender.java @@ -11,8 +11,14 @@ import java.util.LinkedList; import java.util.List; public class MultiEffectAppender implements EffectAppender { + private boolean playabilityCheckedForEffect; + private List effectAppenders = new LinkedList<>(); + public void setPlayabilityCheckedForEffect(boolean playabilityCheckedForEffect) { + this.playabilityCheckedForEffect = playabilityCheckedForEffect; + } + public void addEffectAppender(EffectAppender effectAppender) { effectAppenders.add(effectAppender); } @@ -38,4 +44,9 @@ public class MultiEffectAppender implements EffectAppender { return true; } + + @Override + public boolean isPlayabilityCheckedForEffect() { + return playabilityCheckedForEffect; + } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDiscard.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDiscard.java new file mode 100644 index 000000000..1531b6cde --- /dev/null +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PlayCardFromDiscard.java @@ -0,0 +1,57 @@ +package com.gempukku.lotro.cards.build.field.effect.appender; + +import com.gempukku.lotro.cards.build.CardGenerationEnvironment; +import com.gempukku.lotro.cards.build.InvalidCardDefinitionException; +import com.gempukku.lotro.cards.build.field.FieldUtils; +import com.gempukku.lotro.cards.build.field.effect.EffectAppender; +import com.gempukku.lotro.cards.build.field.effect.EffectAppenderProducer; +import com.gempukku.lotro.cards.build.field.effect.appender.resolver.CardResolver; +import com.gempukku.lotro.filters.Filters; +import com.gempukku.lotro.game.PhysicalCard; +import com.gempukku.lotro.game.state.LotroGame; +import com.gempukku.lotro.logic.PlayUtils; +import com.gempukku.lotro.logic.actions.CostToEffectAction; +import com.gempukku.lotro.logic.modifiers.ModifierFlag; +import com.gempukku.lotro.logic.modifiers.evaluator.ConstantEvaluator; +import com.gempukku.lotro.logic.timing.Effect; +import com.gempukku.lotro.logic.timing.EffectResult; +import com.gempukku.lotro.logic.timing.UnrespondableEffect; +import org.json.simple.JSONObject; + +public class PlayCardFromDiscard implements EffectAppenderProducer { + @Override + public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { + FieldUtils.validateAllowedFields(effectObject, "filter"); + + final String filter = FieldUtils.getString(effectObject.get("filter"), "filter"); + + MultiEffectAppender result = new MultiEffectAppender(); + result.setPlayabilityCheckedForEffect(true); + + result.addEffectAppender( + CardResolver.resolveCardsInDiscard(filter, + (playerId, game, source, effectResult, effect) -> Filters.playable(game), + new ConstantEvaluator(1), "_temp", "owner", "Choose card to play", environment)); + result.addEffectAppender( + new DelayedAppender() { + @Override + protected Effect createEffect(CostToEffectAction action, String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect) { + final PhysicalCard cardToPlay = action.getCardFromMemory("_temp"); + return new UnrespondableEffect() { + @Override + protected void doPlayEffect(LotroGame game) { + final CostToEffectAction playCardAction = PlayUtils.getPlayCardAction(game, cardToPlay, 0, Filters.any, false); + game.getActionsEnvironment().addActionToStack(playCardAction); + } + }; + } + + @Override + public boolean isPlayableInFull(CostToEffectAction action, String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect) { + return !game.getModifiersQuerying().hasFlagActive(game, ModifierFlag.CANT_PLAY_FROM_DISCARD_OR_DECK); + } + }); + + return result; + } +} diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PutStackedCardsIntoHand.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PutStackedCardsIntoHand.java index 48fc31745..515b8f739 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PutStackedCardsIntoHand.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/PutStackedCardsIntoHand.java @@ -47,7 +47,7 @@ public class PutStackedCardsIntoHand implements EffectAppenderProducer { } }); - return null; + return result; } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/CardResolver.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/CardResolver.java index 4802683a7..530e61d5f 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/CardResolver.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/CardResolver.java @@ -135,11 +135,24 @@ public class CardResolver { } public static EffectAppender resolveCardsInDiscard(String type, ValueSource countSource, String memory, String choicePlayer, String choiceText, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { + return resolveCardsInDiscard(type, null, countSource, memory, choicePlayer, choiceText, environment); + } + + public static EffectAppender resolveCardsInDiscard(String type, FilterableSource additionalFilter, ValueSource countSource, String memory, String choicePlayer, String choiceText, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { if (type.startsWith("memory(") && type.endsWith(")")) { + final PlayerSource playerSource = PlayerResolver.resolvePlayer(choicePlayer, environment); + String sourceMemory = type.substring(type.indexOf("(") + 1, type.lastIndexOf(")")); return new AbstractEffectAppender() { @Override public boolean isPlayableInFull(CostToEffectAction action, String playerId, LotroGame game, PhysicalCard self, EffectResult effectResult, Effect effect) { + if (additionalFilter != null) { + int min = countSource.getMinimum(null, playerId, game, self, effectResult, effect); + String choicePlayerId = playerSource.getPlayer(playerId, game, self, effectResult, effect); + final Collection cardsFromMemory = action.getCardsFromMemory(sourceMemory); + Filterable filter = additionalFilter.getFilterable(playerId, game, self, effectResult, effect); + return Filters.filter(game.getGameState().getDiscard(choicePlayerId), game, filter, Filters.in(cardsFromMemory)).size() >= min; + } return true; } @@ -163,7 +176,10 @@ public class CardResolver { int min = countSource.getMinimum(null, playerId, game, self, effectResult, effect); final Filterable filterable = filterableSource.getFilterable(playerId, game, self, effectResult, effect); String choicePlayerId = playerSource.getPlayer(playerId, game, self, effectResult, effect); - return Filters.filter(game.getGameState().getDiscard(choicePlayerId), game, filterable).size() >= min; + Filterable additionalFilterable = Filters.any; + if (additionalFilter != null) + additionalFilterable = additionalFilter.getFilterable(playerId, game, self, effectResult, effect); + return Filters.filter(game.getGameState().getDiscard(choicePlayerId), game, filterable, additionalFilterable).size() >= min; } @Override @@ -172,7 +188,10 @@ public class CardResolver { int max = countSource.getMaximum(action, playerId, game, self, effectResult, effect); final Filterable filterable = filterableSource.getFilterable(playerId, game, self, effectResult, effect); String choicePlayerId = playerSource.getPlayer(playerId, game, self, effectResult, effect); - return new ChooseCardsFromDiscardEffect(choicePlayerId, min, max, filterable) { + Filterable additionalFilterable = Filters.any; + if (additionalFilter != null) + additionalFilterable = additionalFilter.getFilterable(playerId, game, self, effectResult, effect); + return new ChooseCardsFromDiscardEffect(choicePlayerId, min, max, filterable, additionalFilterable) { @Override protected void cardsSelected(LotroGame game, Collection cards) { action.setCardMemory(memory, cards); diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/ValueResolver.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/ValueResolver.java index da99fe089..acee4ecad 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/ValueResolver.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/resolver/ValueResolver.java @@ -3,6 +3,7 @@ package com.gempukku.lotro.cards.build.field.effect.appender.resolver; import com.gempukku.lotro.cards.build.*; import com.gempukku.lotro.cards.build.field.FieldUtils; import com.gempukku.lotro.common.Filterable; +import com.gempukku.lotro.common.Keyword; import com.gempukku.lotro.filters.Filters; import com.gempukku.lotro.game.PhysicalCard; import com.gempukku.lotro.game.state.LotroGame; @@ -70,6 +71,13 @@ public class ValueResolver { final int count = action.getCardsFromMemory(memory).size(); return new ConstantEvaluator(count); }; + } else if (type.equalsIgnoreCase("forEachKeywordOnCardInMemory")) { + final String memory = FieldUtils.getString(object.get("memory"), "memory"); + final Keyword keyword = FieldUtils.getEnum(Keyword.class, object.get("keyword"), "keyword"); + return (action, playerId, game, self, effectResult, effect) -> { + int count = game.getModifiersQuerying().getKeywordCount(game, action.getCardFromMemory(memory), keyword); + return new ConstantEvaluator(count); + }; } else if (type.equalsIgnoreCase("limit")) { final int limit = FieldUtils.getInteger(object.get("value"), "value"); ValueSource valueSource = resolveEvaluator(object.get("amount"), 0, environment);