Playtest updates + bug fixes
- Helpless updated to disable special abilities instead of all text + not usable by enduring nazgul - Terrible as the Dawn is now unique - Why Shouldn't I Keep It? now has the shadow player choose the condition to discard - Updated PC-Movie Playtest to be Multipath. You can now select FOTR or Towers paths in addition to the existing King path. - renamed "Special" site path to "Multipath" - Updated AssignFpCharacterToSkirmish to no longer restrict to only choose or self selectors. No idea why it's so limited. - Fixed Guided by One Purpose (V1_31) not actually offering to let the RB exert twice to prevent. - Added new SendMessage effect that only logs to output. Added additional player replacement values on top of the existing culture and memory replacements.
This commit is contained in:
@@ -360,14 +360,14 @@ gl_theOneRing,1_2
|
||||
9_58,9_35
|
||||
#Gollum, Dark as Darkness FA
|
||||
9_59,9_28
|
||||
#2023 WC - Goblin Runner FA
|
||||
1_386,1_178
|
||||
#2023 WC Top 8 - Goblin Runner FA
|
||||
1_383,1_178
|
||||
#2023 WC World Challenger - Goblin Runner FA
|
||||
1_384,1_178
|
||||
#2023 WC World Champion- Goblin Runner FA
|
||||
1_385,1_178
|
||||
#2023 WC - Goblin Runner FA
|
||||
1_386,1_178
|
||||
#Aragorn,Elessar Telcontar FA
|
||||
#10_127,10_25
|
||||
#Arwen, Queen of Elves and Men FA
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
{
|
||||
type: modifier
|
||||
modifier: {
|
||||
type: DisableGameText
|
||||
type: cantUseSpecialAbilities
|
||||
filter: bearer
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
phase: maneuver
|
||||
cost: {
|
||||
type: exert
|
||||
filter: choose(Nazgul)
|
||||
filter: choose(Nazgul,not(enduring))
|
||||
}
|
||||
effect: {
|
||||
type: Transfer
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
unique: false
|
||||
side: Shadow
|
||||
culture: Moria
|
||||
twilight: 0
|
||||
twilight: 1
|
||||
type: Condition
|
||||
target: unbound,companion,not(hasAttached(name(Such a Little Thing)))
|
||||
effects: [
|
||||
@@ -51,24 +51,35 @@
|
||||
filter: bearer
|
||||
memory: bearer
|
||||
}
|
||||
{
|
||||
type: ChooseActiveCards
|
||||
text: Choose a possession attached to {bearer} to discard.
|
||||
filter: choose(possession,AttachedTo(bearer))
|
||||
memorize: chosenPoss
|
||||
}
|
||||
{
|
||||
type: choice
|
||||
player: freeps
|
||||
texts: [
|
||||
Discard an item from {bearer}
|
||||
Wound {bearer}
|
||||
Exert {bearer} to prevent discarding {chosenPoss}
|
||||
Discard another Free Peoples item (artifact or possession) to prevent discarding {chosenPoss}
|
||||
Let {chosenPoss} get discarded
|
||||
]
|
||||
effects: [
|
||||
{
|
||||
type: Discard
|
||||
player: freeps
|
||||
filter: choose(item,AttachedTo(bearer))
|
||||
}
|
||||
{
|
||||
type: wound
|
||||
type: exert
|
||||
filter: bearer
|
||||
times: 1
|
||||
}
|
||||
{
|
||||
type: Discard
|
||||
player: freeps
|
||||
filter: choose(item,side(freeps),not(memory(chosenPoss)))
|
||||
}
|
||||
{
|
||||
type: SendMessage
|
||||
text: "{freeps} chooses to not prevent {chosenPoss} getting discarded."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
style: Standard
|
||||
}
|
||||
title: Terrible as the Dawn
|
||||
unique: false
|
||||
unique: true
|
||||
side: Shadow
|
||||
culture: Sauron
|
||||
twilight: 2
|
||||
@@ -116,7 +116,7 @@
|
||||
unique: false
|
||||
side: Shadow
|
||||
culture: Sauron
|
||||
twilight: 2
|
||||
twilight: 1
|
||||
type: Condition
|
||||
target: or(companion,ally)
|
||||
requires: {
|
||||
@@ -141,7 +141,7 @@
|
||||
effects: [
|
||||
{
|
||||
type: Discard
|
||||
player: freeps
|
||||
player: shadow
|
||||
filter: choose(side(freeps),condition)
|
||||
}
|
||||
{
|
||||
|
||||
@@ -30,11 +30,29 @@
|
||||
type: discard
|
||||
filter: self
|
||||
}
|
||||
effect: {
|
||||
type: assignFpCharacterToSkirmish
|
||||
fpCharacter: choose(ringbearer)
|
||||
against: choose(culture(moria),creature)
|
||||
}
|
||||
effect: [
|
||||
{
|
||||
type: ChooseActiveCards
|
||||
text: Choose a {moria} creature to assign to the Ring-bearer.
|
||||
filter: choose(culture(moria),creature)
|
||||
memorize: chosenCreature
|
||||
}
|
||||
{
|
||||
type: Preventable
|
||||
player: freeps
|
||||
text: Would you like to exert the Ring-bearer twice to prevent {chosenCreature} being assigned to them?
|
||||
cost: {
|
||||
type: Exert
|
||||
filter: choose(Ring-bearer)
|
||||
times: 2
|
||||
}
|
||||
effect: {
|
||||
type: assignFpCharacterToSkirmish
|
||||
fpCharacter: choose(ringbearer)
|
||||
against: memory(chosenCreature)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
name: Debug
|
||||
code: debug
|
||||
sites: SPECIAL
|
||||
sites: MULTIPATH
|
||||
maximumSameName: 100
|
||||
set: [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 101, 151
|
||||
@@ -368,7 +368,7 @@
|
||||
name: Pre-Shadows Multipath
|
||||
code: multipath
|
||||
order: 27
|
||||
sites: SPECIAL
|
||||
sites: MULTIPATH
|
||||
banned: [
|
||||
"1_138", "1_311", "1_316", "1_331", "1_338", "1_354", "1_355", "1_360",
|
||||
"2_14", "2_75", "3_1", "3_113", "4_73", "4_338", "4_355", "4_357", "7_190",
|
||||
@@ -503,7 +503,7 @@
|
||||
name: Anything Goes
|
||||
code: rev_tow_sta
|
||||
order: 37
|
||||
sites: SPECIAL
|
||||
sites: MULTIPATH
|
||||
cancelRingBearerSkirmish: true
|
||||
set: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
|
||||
30, 31, 32, 33
|
||||
@@ -592,7 +592,7 @@
|
||||
name: PLAYTEST - Movie Block (PC)
|
||||
code: test_pc_movie
|
||||
order: 42
|
||||
sites: KING
|
||||
sites: MULTIPATH
|
||||
winAtEndOfRegroup: true
|
||||
discardPileIsPublic: true
|
||||
hall: true
|
||||
@@ -748,7 +748,7 @@
|
||||
{
|
||||
name: Limited - Multipath Movie
|
||||
code: limited_multipath
|
||||
sites: SPECIAL
|
||||
sites: MULTIPATH
|
||||
cancelRingBearerSkirmish: false
|
||||
maximumSameName: 100
|
||||
set: [
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.gempukku.lotro.common;
|
||||
|
||||
public enum SitesBlock {
|
||||
FELLOWSHIP("Fellowship"), TWO_TOWERS("Towers"), KING("King"),
|
||||
SHADOWS("Shadows and onwards"), SPECIAL("Special"),
|
||||
SHADOWS("Shadows and onwards"), MULTIPATH("Multipath"),
|
||||
SECOND_ED("2nd edition"),
|
||||
|
||||
//Additional Hobbit Draft block
|
||||
|
||||
@@ -135,6 +135,7 @@ public class EffectAppenderFactory {
|
||||
effectAppenderProducers.put("revealhand", new RevealHand());
|
||||
effectAppenderProducers.put("revealrandomcardsfromhand", new RevealRandomCardsFromHand());
|
||||
effectAppenderProducers.put("revealtopcardsofdrawdeck", new RevealTopCardsOfDrawDeck());
|
||||
effectAppenderProducers.put("sendmessage", new SendMessage());
|
||||
effectAppenderProducers.put("shadowcanthaveinitiative", new ShadowCantHaveInitiative());
|
||||
effectAppenderProducers.put("shufflecardsfromdiscardintodrawdeck", new ShuffleCardsFromDiscardIntoDrawDeck());
|
||||
effectAppenderProducers.put("shufflecardsfromhandintodrawdeck", new ShuffleCardsFromHandIntoDrawDeck());
|
||||
|
||||
@@ -69,11 +69,16 @@ public class AssignFpCharacterToSkirmish implements EffectAppenderProducer {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private FilterableSource getSource(String filter, CardGenerationEnvironment environment) throws InvalidCardDefinitionException {
|
||||
if (filter.startsWith("choose(") && filter.endsWith(")"))
|
||||
return environment.getFilterFactory().generateFilter(filter.substring(filter.indexOf("(") + 1, filter.lastIndexOf(")")), environment);
|
||||
if (filter.equals("self"))
|
||||
return ActionContext::getSource;
|
||||
throw new InvalidCardDefinitionException("The selectors for this effect have to be of 'choose' type or 'self'");
|
||||
|
||||
return environment.getFilterFactory().generateFilter(filter, environment);
|
||||
//What exactly was the point of this? Pulling from other sources should be just fine.
|
||||
//I wish I knew what bug this was addressing.
|
||||
//throw new InvalidCardDefinitionException("The selectors for this effect have to be of 'choose' type or 'self'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
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.ValueSource;
|
||||
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.filters.Filters;
|
||||
import com.gempukku.lotro.game.PhysicalCard;
|
||||
import com.gempukku.lotro.game.state.LotroGame;
|
||||
import com.gempukku.lotro.logic.GameUtils;
|
||||
import com.gempukku.lotro.logic.actions.CostToEffectAction;
|
||||
import com.gempukku.lotro.logic.effects.ExertCharactersEffect;
|
||||
import com.gempukku.lotro.logic.timing.AbstractSuccessfulEffect;
|
||||
import com.gempukku.lotro.logic.timing.Effect;
|
||||
import com.gempukku.lotro.logic.timing.UnrespondableEffect;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SendMessage implements EffectAppenderProducer {
|
||||
@Override
|
||||
public EffectAppender createEffectAppender(JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException {
|
||||
FieldUtils.validateAllowedFields(effectObject, "text");
|
||||
|
||||
final String text = FieldUtils.getString(effectObject.get("text"), "text");
|
||||
if (text == null)
|
||||
throw new InvalidCardDefinitionException("SendMessage requires the text field to be set.");
|
||||
|
||||
return new DelayedAppender() {
|
||||
@Override
|
||||
protected Effect createEffect(boolean cost, CostToEffectAction action, ActionContext actionContext) {
|
||||
return new UnrespondableEffect() {
|
||||
|
||||
@Override
|
||||
protected void doPlayEffect(LotroGame game) {
|
||||
game.getGameState().sendMessage(GameUtils.SubstituteText(text, actionContext));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayableInFull(ActionContext actionContext) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,6 +73,10 @@ public class GameUtils {
|
||||
return shadowPlayers.toArray(new String[shadowPlayers.size()]);
|
||||
}
|
||||
|
||||
public static String getFirstOpponent(LotroGame game, String playerId) {
|
||||
return Arrays.stream(getOpponents(game, playerId)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public static String[] getAllPlayers(LotroGame game) {
|
||||
final GameState gameState = game.getGameState();
|
||||
final PlayerOrder playerOrder = gameState.getPlayerOrder();
|
||||
@@ -189,23 +193,60 @@ public class GameUtils {
|
||||
while (result.contains("{")) {
|
||||
int startIndex = result.indexOf("{");
|
||||
int endIndex = result.indexOf("}");
|
||||
String memory = result.substring(startIndex + 1, endIndex);
|
||||
String memoryName = result.substring(startIndex + 1, endIndex);
|
||||
String memory = memoryName.toLowerCase();
|
||||
String found = null;
|
||||
String culture = getCultureImage(memory);
|
||||
if(culture != null) {
|
||||
result = result.replace("{" + memory + "}", culture);
|
||||
found = culture;
|
||||
}
|
||||
else if(context != null){
|
||||
String cardNames = GameUtils.getAppendedNames(context.getCardsFromMemory(memory));
|
||||
if(cardNames.equalsIgnoreCase("none")) {
|
||||
try {
|
||||
cardNames = context.getValueFromMemory(memory);
|
||||
}
|
||||
catch(IllegalArgumentException ex) {
|
||||
cardNames = "none";
|
||||
if(memory.equals("you")) {
|
||||
found = context.getPerformingPlayer();
|
||||
if(found == null) {
|
||||
found = "nobody";
|
||||
}
|
||||
}
|
||||
else if(memory.equals("owner")) {
|
||||
var source = context.getSource();
|
||||
if(source != null) {
|
||||
found = source.getOwner();
|
||||
}
|
||||
if(found == null) {
|
||||
found = "nobody";
|
||||
}
|
||||
}
|
||||
else if(memory.equals("opponent")) {
|
||||
found = getFirstOpponent(context.getGame(), context.getSource().getOwner());
|
||||
if(found == null) {
|
||||
found = "nobody";
|
||||
}
|
||||
}
|
||||
else if(memory.equals("freeps") || memory.equals("free peoples")) {
|
||||
found = getFreePeoplePlayer(context.getGame());
|
||||
if(found == null) {
|
||||
found = "nobody";
|
||||
}
|
||||
}
|
||||
else if(memory.equals("shadow")) {
|
||||
found = getFirstShadowPlayer(context.getGame());
|
||||
}
|
||||
else {
|
||||
found = GameUtils.getAppendedNames(context.getCardsFromMemory(memory));
|
||||
if(found.equalsIgnoreCase("none")) {
|
||||
try {
|
||||
found = context.getValueFromMemory(memory);
|
||||
}
|
||||
catch(IllegalArgumentException ex) {
|
||||
found = "NONE";
|
||||
}
|
||||
}
|
||||
}
|
||||
result = result.replace("{" + memory + "}", cardNames);
|
||||
}
|
||||
if(found == null) {
|
||||
found = "NOTHING";
|
||||
}
|
||||
result = result.replace("{" + memoryName + "}", found);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class DefaultLotroFormat implements LotroFormat {
|
||||
private final Map<String,String> _errataCardMap = new TreeMap<>();
|
||||
|
||||
public DefaultLotroFormat(AdventureLibrary adventureLibrary, LotroCardBlueprintLibrary library, JSONDefs.Format def) throws InvalidPropertiesFormatException{
|
||||
this(library, adventureLibrary.getAdventure(def.adventure), def.name, def.code, def.order, def.surveyUrl, SitesBlock.valueOf(def.sites),
|
||||
this(library, adventureLibrary.getAdventure(def.adventure), def.name, def.code, def.order, def.surveyUrl, SitesBlock.findBlock(def.sites),
|
||||
def.validateShadowFPCount, def.minimumDeckSize, def.maximumSameName, def.mulliganRule, def.cancelRingBearerSkirmish,
|
||||
def.ruleOfFour, def.winAtEndOfRegroup, def.discardPileIsPublic, def.winOnControlling5Sites, def.playtest, def.hall);
|
||||
|
||||
@@ -611,10 +611,10 @@ public class DefaultLotroFormat implements LotroFormat {
|
||||
if (siteBlueprint.getCardType() != CardType.SITE) {
|
||||
result += "Card assigned as Site is not really a site.\n";
|
||||
}
|
||||
else if (siteBlueprint.getSiteBlock() != _siteBlock && _siteBlock != SitesBlock.SPECIAL) {
|
||||
else if (siteBlueprint.getSiteBlock() != _siteBlock && _siteBlock != SitesBlock.MULTIPATH) {
|
||||
result += "Site does not match block: " + GameUtils.getFullName(siteBlueprint) + "\n";
|
||||
}
|
||||
else if (_siteBlock == SitesBlock.SPECIAL && siteBlueprint.getSiteBlock() == SitesBlock.SHADOWS) {
|
||||
else if (_siteBlock == SitesBlock.MULTIPATH && siteBlueprint.getSiteBlock() == SitesBlock.SHADOWS) {
|
||||
result += "Post-Shadows site not allowed: " + GameUtils.getFullName(siteBlueprint) + "\n";
|
||||
}
|
||||
else {
|
||||
@@ -630,7 +630,7 @@ public class DefaultLotroFormat implements LotroFormat {
|
||||
}
|
||||
|
||||
}
|
||||
if (_siteBlock == SitesBlock.SPECIAL) {
|
||||
if (_siteBlock == SitesBlock.MULTIPATH) {
|
||||
SitesBlock usedBlock = null;
|
||||
for (String site : deck.getSites()) {
|
||||
try {
|
||||
|
||||
@@ -112,7 +112,7 @@ public class Card_V1_031_Tests
|
||||
}
|
||||
|
||||
@Test
|
||||
public void PurposeSpots4StackedTentaclesToAssignToRB() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
public void PurposeSpots4StackedTentaclesToAssignToRBIfPreventionDeclined() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
//Pre-game setup
|
||||
GenericCardTestHelper scn = GetScenario();
|
||||
|
||||
@@ -137,8 +137,46 @@ public class Card_V1_031_Tests
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
assertTrue(scn.ShadowActionAvailable("One Purpose"));
|
||||
scn.ShadowUseCardAction(purpose);
|
||||
|
||||
scn.FreepsChooseNo();
|
||||
assertTrue(scn.IsCharAssigned(scn.GetRingBearer()));
|
||||
//should now see One Purpose and the four tentacles stacked on it in the discard pile
|
||||
assertEquals(5, scn.GetShadowDiscardCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void PurposeRBAssignmentCanBePreventedByExertingRBTwice() throws DecisionResultInvalidException, CardNotFoundException {
|
||||
//Pre-game setup
|
||||
GenericCardTestHelper scn = GetScenario();
|
||||
|
||||
var frodo = scn.GetRingBearer();
|
||||
PhysicalCardImpl merry = scn.GetFreepsCard("merry");
|
||||
PhysicalCardImpl aragorn = scn.GetFreepsCard("aragorn");
|
||||
scn.FreepsMoveCharToTable(merry, aragorn);
|
||||
|
||||
PhysicalCardImpl purpose = scn.GetShadowCard("purpose");
|
||||
PhysicalCardImpl htentacle = scn.GetShadowCard("htentacle");
|
||||
scn.ShadowMoveCardToHand(htentacle);
|
||||
scn.ShadowMoveCardToSupportArea(purpose);
|
||||
scn.StackCardsOn(purpose,scn.GetShadowCard("ftentacle1"), scn.GetShadowCard("ftentacle2"), scn.GetShadowCard("ftentacle3"), scn.GetShadowCard("ftentacle4"));
|
||||
|
||||
scn.StartGame();
|
||||
scn.SetTwilight(4);
|
||||
scn.ApplyAdHocModifier(new KeywordModifier(null, Filters.siteNumber(2), Keyword.MARSH));
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
scn.ShadowPlayCard(htentacle);
|
||||
scn.ShadowDeclineOptionalTrigger();
|
||||
|
||||
scn.SkipToPhase(Phase.ASSIGNMENT);
|
||||
scn.FreepsPassCurrentPhaseAction();
|
||||
assertTrue(scn.ShadowActionAvailable("One Purpose"));
|
||||
scn.ShadowUseCardAction(purpose);
|
||||
|
||||
assertEquals(4, scn.GetVitality(frodo));
|
||||
scn.FreepsChooseYes();
|
||||
assertEquals(2, scn.GetVitality(frodo));
|
||||
assertFalse(scn.IsCharAssigned(scn.GetRingBearer()));
|
||||
//should now see One Purpose and the four tentacles stacked on it in the discard pile
|
||||
assertEquals(5, scn.GetShadowDiscardCount());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user