diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set08-errata-playtest.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set08-errata-playtest.json index 6c1da2efa..e02397ae3 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set08-errata-playtest.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set08-errata-playtest.json @@ -150,5 +150,59 @@ ] } ] + }, + "78_103": { + "title": "*Grond", + "subtitle": "Hammer of the Underworld", + "culture": "Sauron", + "cost": 3, + "type": "possession", + "keyword": [ + "Support Area", + "Engine" + ], + "condition": { + "type": "canSpot", + "filter": "culture(sauron),minion" + }, + "effects": [ + { + "type": "activated", + "phase": "fellowship", + "cost": { + "type": "play", + "filter": "choose(culture(sauron),minion)" + }, + "effect": { + "type": "addTokens", + "culture": "sauron", + "filter": "self" + } + }, + { + "type": "trigger", + "optional": true, + "trigger": { + "type": "startOfPhase", + "phase": "regroup" + }, + "cost": { + "type": "chooseandremovetokens", + "culture": "sauron", + "filter": "self", + "memorize": "numberOfTokens" + }, + "effect": [ + { + "type": "discard", + "filter": "choose(side(free people),not(companion),minTwilight(memory(numberOfTokens)),maxTwilight(memory(numberOfTokens)))" + }, + { + "type": "discard", + "filter": "choose(or(and(culture(sauron),minion),self))" + } + ] + } + ] } } diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set09-errata-playtest.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set09-errata-playtest.json new file mode 100644 index 000000000..82b323de6 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set09-errata-playtest.json @@ -0,0 +1,84 @@ +{ + "79_12": { + "title": "*Aiglos", + "culture": "elven", + "cost": 2, + "type": "artifact", + "possession": "hand weapon", + "vitality": 1, + "target": "elf,not(archer)", + "effects": [ + { + "type": "modifier", + "modifier": { + "type": "modifyStrength", + "filter": "bearer,name(Gil-galad)", + "amount": { + "type": "forEachYouCanSpot", + "filter": "culture(elven),artifact", + "limit": 6 + } + } + }, + { + "type": "activated", + "phase": "regroup", + "cost": { + "type": "discard", + "filter": "self" + }, + "effect": [ + { + "type": "chooseHowManyToSpot", + "filter": "elf", + "memorize": "spottedElfCount" + }, + { + "type": "duplicate", + "amount": { + "type": "fromMemory", + "memory": "spottedElfCount" + }, + "effect": { + "type": "wound", + "filter": "choose(minion)" + } + } + ] + } + ] + }, + "79_14": { + "title": "*Galadriel", + "subtitle": "Bearer of Wisdom", + "culture": "elven", + "cost": 3, + "type": "companion", + "race": "elf", + "strength": 3, + "vitality": 3, + "resistance": 3, + "keyword": "can start with ring", + "effects": [ + { + "type": "modifier", + "modifier": { + "type": "modifyResistance", + "filter": "self,hasAttached(or(artifact,name(The One Ring)))", + "amount": { + "type": "forEachYouCanSpot", + "filter": "culture(elven),companion" + } + } + }, + { + "type": "modifier", + "modifier": { + "type": "cantBear", + "filter": "self", + "cardFilter": "ranged weapon" + } + } + ] + } +} \ No newline at end of file diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set09-errata.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set09-errata.json new file mode 100644 index 000000000..7a73a41bf --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set09-errata.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set10-errata-playtest.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set10-errata-playtest.json index 2c63c0851..e443a9db0 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set10-errata-playtest.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/errata/set10-errata-playtest.json @@ -1,2 +1,115 @@ { + "80_8": { + "title": "*Cirdan", + "subtitle": "The Shipwright", + "culture": "Elven", + "cost": 4, + "type": "companion", + "race": "Elf", + "strength": 7, + "vitality": 4, + "resistance": 6, + "condition": { + "type": "canSpot", + "filter": "elf", + "count": 2 + }, + "effects": { + "type": "activated", + "phase": "skirmish", + "cost": { + "type": "exert", + "filter": "self" + }, + "effect": { + "type": "modifyStrength", + "filter": "choose(minion,inSkirmishAgainst(self))", + "amount": { + "type": "forEachInDiscard", + "filter": "culture(elven),event", + "limit": 4, + "multiplier": -1 + } + } + } + }, + "80_17": { + "title": "Out of the High Airs", + "cost": 3, + "culture": "Gandalf", + "type": "event", + "keyword": "response", + "condition": { + "type": "phase", + "phase": "regroup" + }, + "effects": { + "type": "responseEvent", + "trigger": { + "type": "moves" + }, + "cost": { + "type": "exert", + "filter": "choose(your,wizard)" + }, + "effect": { + "type": "discard", + "count": "0-3", + "filter": "choose(minion)" + } + } + }, + "80_32": { + "title": "*Fifth Level", + "culture": "Gondor", + "cost": 1, + "type": "condition", + "keyword": [ + "Support Area", + "Fortification" + ], + "effects": [ + { + "type": "modifier", + "modifier": { + "type": "modifyStrength", + "filter": "bearer", + "amount": { + "type": "forEachYouCanSpot", + "filter": "fortification", + "multiplier": -1 + } + } + }, + { + "type": "activated", + "phase": "maneuver", + "cost": { + "type": "choice", + "texts": [ + "Exert 2 GONDOR Men", + "Spot 3 Knights" + ], + "effects": [ + { + "type": "exert", + "filter": "choose(culture(gondor),man)", + "count": 2 + }, + { + "type": "spot", + "filter": "knight", + "count": 3, + "skipMemorize": true + } + ] + }, + "effect": { + "type": "transfer", + "filter": "choose(self,zone(support))", + "where": "choose(minion)" + } + } + ] + } } diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/PC_Cards.js b/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/PC_Cards.js index 760a11af5..a00db91be 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/PC_Cards.js +++ b/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/PC_Cards.js @@ -124,15 +124,21 @@ var PCCards = { '71_55': 'https://i.lotrtcgpc.net/errata/xlist/71_055.jpg', '71_254': 'https://i.lotrtcgpc.net/errata/xlist/71_254.jpg', '71_282': 'https://i.lotrtcgpc.net/errata/xlist/71_282.jpg', - '72_6': 'https://i.lotrtcgpc.net/errata/xlist/72_6.jpg', - '72_7': 'https://i.lotrtcgpc.net/errata/xlist/72_7.jpg', - '72_55': 'https://i.lotrtcgpc.net/errata/xlist/72_55.jpg', - '73_54': 'https://i.lotrtcgpc.net/errata/xlist/73_54.jpg', + '72_6': 'https://i.lotrtcgpc.net/errata/xlist/72_006.jpg', + '72_7': 'https://i.lotrtcgpc.net/errata/xlist/72_007.jpg', + '72_55': 'https://i.lotrtcgpc.net/errata/xlist/72_055.jpg', + '73_54': 'https://i.lotrtcgpc.net/errata/xlist/73_054.jpg', '73_120': 'https://i.lotrtcgpc.net/errata/xlist/73_120.jpg', - '78_24': 'https://i.lotrtcgpc.net/errata/xlist/78_24.jpg', - '78_51': 'https://i.lotrtcgpc.net/errata/xlist/78_51.jpg', - '78_57': 'https://i.lotrtcgpc.net/errata/xlist/78_57.jpg', - '78_59': 'https://i.lotrtcgpc.net/errata/xlist/78_59.jpg', + '78_24': 'https://i.lotrtcgpc.net/errata/xlist/78_024.jpg', + '78_51': 'https://i.lotrtcgpc.net/errata/xlist/78_051.jpg', + '78_57': 'https://i.lotrtcgpc.net/errata/xlist/78_057.jpg', + '78_59': 'https://i.lotrtcgpc.net/errata/xlist/78_059.jpg', + '78_103': 'https://i.lotrtcgpc.net/errata/xlist/78_103.jpg', + '79_12': 'https://i.lotrtcgpc.net/errata/xlist/79_012.jpg', + '79_14': 'https://i.lotrtcgpc.net/errata/xlist/79_014.jpg', + '80_8': 'https://i.lotrtcgpc.net/errata/xlist/80_008.jpg', + '80_17': 'https://i.lotrtcgpc.net/errata/xlist/80_017.jpg', + '80_32': 'https://i.lotrtcgpc.net/errata/xlist/80_032.jpg', /// Player's Council League Promos // April 2021 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 59503402a..737927dc4 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 @@ -100,6 +100,7 @@ public class EffectAppenderFactory { effectAppenderProducers.put("revealcards", new RevealCards()); effectAppenderProducers.put("addtokens", new AddTokens()); effectAppenderProducers.put("removetokens", new RemoveTokens()); + effectAppenderProducers.put("chooseandremovetokens", new ChooseAndRemoveTokens()); effectAppenderProducers.put("transfer", new Transfer()); effectAppenderProducers.put("transfertosupport", new TransferToSupport()); effectAppenderProducers.put("transferfromdiscard", new TransferFromDiscard()); diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/ChooseAndRemoveTokens.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/ChooseAndRemoveTokens.java new file mode 100644 index 000000000..1ea9dfd1e --- /dev/null +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/ChooseAndRemoveTokens.java @@ -0,0 +1,98 @@ +package com.gempukku.lotro.cards.build.field.effect.appender; + +import com.gempukku.lotro.cards.build.ActionContext; +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.common.Culture; +import com.gempukku.lotro.common.Token; +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.decisions.DecisionResultInvalidException; +import com.gempukku.lotro.logic.decisions.IntegerAwaitingDecision; +import com.gempukku.lotro.logic.effects.PlayoutDecisionEffect; +import com.gempukku.lotro.logic.effects.RemoveTokenEffect; +import com.gempukku.lotro.logic.modifiers.ModifierFlag; +import com.gempukku.lotro.logic.modifiers.evaluator.ConstantEvaluator; +import com.gempukku.lotro.logic.timing.Effect; +import org.json.simple.JSONObject; + +import java.util.LinkedList; +import java.util.List; + +public class ChooseAndRemoveTokens implements EffectAppenderProducer { + @Override + public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { + FieldUtils.validateAllowedFields(effectObject, "culture", "filter", "memorize", "memorizeCard"); + + final Culture culture = FieldUtils.getEnum(Culture.class, effectObject.get("culture"), "culture"); + final String filter = FieldUtils.getString(effectObject.get("filter"), "filter", "self"); + final String memory = FieldUtils.getString(effectObject.get("memorize"), "memorize", "_temp"); + final String memorizeCard = FieldUtils.getString(effectObject.get("memorizeCard"), "memorizeCard"); + + final Token token = (culture != null) ? Token.findTokenForCulture(culture) : null; + + MultiEffectAppender result = new MultiEffectAppender(); + result.addEffectAppender( + CardResolver.resolveCards(filter, null, new ConstantEvaluator(1), + memorizeCard, "you", "Choose card to remove tokens from", environment)); + result.addEffectAppender( + new DelayedAppender() { + @Override + public boolean isPlayableInFull(ActionContext actionContext) { + final LotroGame game = actionContext.getGame(); + return !game.getModifiersQuerying().hasFlagActive(game, ModifierFlag.CANT_TOUCH_CULTURE_TOKENS); + } + + @Override + protected Effect createEffect(boolean cost, CostToEffectAction action, ActionContext actionContext) { + final PhysicalCard cardFromMemory = actionContext.getCardFromMemory(memorizeCard); + + return new PlayoutDecisionEffect(actionContext.getPerformingPlayer(), + new IntegerAwaitingDecision(1, "How many tokens do you wish to remove", + 0, actionContext.getGame().getGameState().getTokenCount(cardFromMemory, token)) { + @Override + public void decisionMade(String result) throws DecisionResultInvalidException { + actionContext.setValueToMemory(memory, String.valueOf(getValidatedResult(result))); + } + }); + } + }); + result.addEffectAppender( + new DelayedAppender() { + @Override + public boolean isPlayableInFull(ActionContext actionContext) { + final LotroGame game = actionContext.getGame(); + return !game.getModifiersQuerying().hasFlagActive(game, ModifierFlag.CANT_TOUCH_CULTURE_TOKENS); + } + + @Override + protected List createEffects(boolean cost, CostToEffectAction action, ActionContext actionContext) { + final PhysicalCard cardFromMemory = actionContext.getCardFromMemory(memorizeCard); + int tokenCount = Integer.parseInt(actionContext.getValueFromMemory(memory)); + + List result = new LinkedList<>(); + if (token != null) + result.add(new RemoveTokenEffect(actionContext.getSource(), cardFromMemory, token, tokenCount)); + else { + result.add(new RemoveTokenEffect(actionContext.getSource(), cardFromMemory, + getCultureTokenOnCard(actionContext.getGame(), cardFromMemory), tokenCount)); + } + return result; + } + }); + return result; + } + + private Token getCultureTokenOnCard(LotroGame game, PhysicalCard card) { + for (Token token : Token.values()) + if (token.getCulture() != null && game.getGameState().getTokenCount(card, token) > 0) + return token; + + return null; + } +} diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/Spot.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/Spot.java index 6db5a6570..52876630b 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/Spot.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/Spot.java @@ -1,23 +1,53 @@ 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.*; 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.cards.build.field.effect.appender.resolver.ValueResolver; +import com.gempukku.lotro.common.Filterable; +import com.gempukku.lotro.game.state.LotroGame; +import com.gempukku.lotro.logic.actions.CostToEffectAction; +import com.gempukku.lotro.logic.timing.Effect; +import com.gempukku.lotro.logic.timing.PlayConditions; +import com.gempukku.lotro.logic.timing.UnrespondableEffect; import org.json.simple.JSONObject; public class Spot implements EffectAppenderProducer { @Override public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject, "filter", "memorize", "text"); + FieldUtils.validateAllowedFields(effectObject, "count", "filter", "memorize", "text", "skipMemorize"); + final ValueSource valueSource = ValueResolver.resolveEvaluator(effectObject.get("count"), 1, environment); final String filter = FieldUtils.getString(effectObject.get("filter"), "filter"); final String memorize = FieldUtils.getString(effectObject.get("memorize"), "memorize", "_temp"); final String text = FieldUtils.getString(effectObject.get("text"), "text", "Choose card to spot"); + final boolean skipMemorize = FieldUtils.getBoolean(effectObject.get("skipMemorize"), "skipMemorize", false); - return CardResolver.resolveCard( - filter, memorize, "you", text, environment); + if (skipMemorize) { + final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(filter, environment); + + return new DelayedAppender() { + @Override + protected Effect createEffect(boolean cost, CostToEffectAction action, ActionContext actionContext) { + return new UnrespondableEffect() { + @Override + protected void doPlayEffect(LotroGame game) { + } + }; + } + + @Override + public boolean isPlayableInFull(ActionContext actionContext) { + final int count = valueSource.getEvaluator(actionContext).evaluateExpression(actionContext.getGame(), null); + Filterable filterable = filterableSource.getFilterable(actionContext); + return PlayConditions.canSpot(actionContext.getGame(), count, filterable); + } + }; + } else { + return CardResolver.resolveCards( + filter, valueSource, memorize, "you", text, environment); + } } } diff --git a/gemp-lotr/gemp-lotr-server/src/main/resources/lotrFormats.json b/gemp-lotr/gemp-lotr-server/src/main/resources/lotrFormats.json index 26d56609f..20bb5c24c 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/resources/lotrFormats.json +++ b/gemp-lotr/gemp-lotr-server/src/main/resources/lotrFormats.json @@ -460,8 +460,9 @@ "2_32":"52_32", "2_55":"72_55", "2_75":"52_75", "2_101":"52_101", "2_108":"52_108", "2_121":"52_121", "3_17":"53_17", "3_38":"53_38", "3_42":"53_42", "3_54":"73_54", "3_67":"53_67", "3_68":"53_68", "3_106":"53_106", "3_108":"53_108", "3_113":"53_113", "3_120":"73_120", "4_73":"54_73", "4_192":"54_192", "4_276":"54_276", "4_304":"54_304", "7_49":"57_49", - "7_96":"57_96", "8_1":"58_1", "8_24":"58_24", "8_51":"58_51", "8_57":"58_57", "8_59":"58_59", "10_2":"60_2", - "10_11":"60_11", "10_91":"60_91"} + "7_96":"57_96", "8_1":"58_1", "8_24":"78_24", "8_51":"78_51", "8_57":"78_57", "8_59":"78_59", "8_103":"78_103", + "9_12":"79_12", "9_14":"79_14", "10_2":"60_2", "10_8":"80_8", "10_11":"60_11", "10_17":"80_17", "10_32":"80_32", + "10_91":"60_91"} }, { "name":"PLAYTEST - Expanded (PC)", @@ -475,9 +476,10 @@ "1_254":"71_254", "1_282":"71_282", "1_311":"51_311", "1_313":"51_313", "1_316":"51_316", "2_6":"72_6", "2_7":"72_7", "2_32":"52_32", "2_55":"72_55", "2_75":"52_75", "2_101":"52_101", "2_108":"52_108", "2_121":"52_121", "3_17":"53_17", "3_38":"53_38", "3_42":"53_42", "3_54":"73_54", "3_67":"53_67", "3_68":"53_68", "3_106":"53_106", "3_108":"53_108", - "3_113":"53_113", "3_120":"73_120", "4_73":"54_73", "4_192":"54_192", "4_276":"54_276", "4_304":"54_304", "7_49":"57_49", - "7_96":"57_96", "8_1":"58_1", "8_24":"58_24", "8_51":"58_51", "8_57":"58_57", "8_59":"58_59", "10_2":"60_2", - "10_11":"60_11", "10_91":"60_91", "11_31":"61_31", "11_100":"61_100", "11_114":"61_114", "11_132":"61_132", - "13_188":"63_188", "15_64":"65_64"} + "3_113":"53_113", "3_120":"73_120", "4_73":"54_73", "4_192":"54_192", "4_276":"54_276", "4_304":"54_304", "7_49":"57_49", + "7_96":"57_96", "8_1":"58_1", "8_24":"78_24", "8_51":"78_51", "8_57":"78_57", "8_59":"78_59", "8_103":"78_103", + "9_12":"79_12", "9_14":"79_14", "10_2":"60_2", "10_8":"80_8", "10_11":"60_11", "10_17":"80_17", "10_32":"80_32", + "10_91":"60_91", "11_31":"61_31", "11_100":"61_100", "11_114":"61_114", "11_132":"61_132", "13_188":"63_188", + "15_64":"65_64"} } ]