Map cards now save and load and validate for pertinent formats

This commit is contained in:
Christian 'ketura' McCarty
2024-04-20 02:59:27 -05:00
parent 488f152c02
commit 203d4bb55d
30 changed files with 493 additions and 97 deletions

View File

@@ -140,7 +140,7 @@ public class DeckRequestHandler extends LotroServerRequestHandler implements Uri
int fpCount = 0;
int shadowCount = 0;
for (String card : deck.getAdventureCards()) {
for (String card : deck.getDrawDeckCards()) {
Side side = _library.getLotroCardBlueprint(card).getSide();
if (side == Side.SHADOW)
shadowCount++;
@@ -369,7 +369,7 @@ public class DeckRequestHandler extends LotroServerRequestHandler implements Uri
result.append("<b>Ring:</b> " + generateCardTooltip(_library.getLotroCardBlueprint(ring), ring) + "<br/>");
DefaultCardCollection deckCards = new DefaultCardCollection();
for (String card : deck.getAdventureCards())
for (String card : deck.getDrawDeckCards())
deckCards.addItem(_library.getBaseBlueprintId(card), 1);
for (String site : deck.getSites())
deckCards.addItem(_library.getBaseBlueprintId(site), 1);
@@ -577,13 +577,19 @@ public class DeckRequestHandler extends LotroServerRequestHandler implements Uri
deckElem.appendChild(ring);
}
if (deck.getMap() != null) {
Element map = doc.createElement("map");
map.setAttribute("blueprintId", deck.getMap());
deckElem.appendChild(map);
}
for (CardItem cardItem : _sortAndFilterCards.process("sort:siteNumber,twilight", createCardItems(deck.getSites()), _library, _formatLibrary)) {
Element site = doc.createElement("site");
site.setAttribute("blueprintId", cardItem.getBlueprintId());
deckElem.appendChild(site);
}
for (CardItem cardItem : _sortAndFilterCards.process("sort:cardType,culture,name", createCardItems(deck.getAdventureCards()), _library, _formatLibrary)) {
for (CardItem cardItem : _sortAndFilterCards.process("sort:cardType,culture,name", createCardItems(deck.getDrawDeckCards()), _library, _formatLibrary)) {
Element card = doc.createElement("card");
String side;
try {

View File

@@ -127,7 +127,7 @@
Map
<div class="helper-text" >
(Click here to select your Map.)
<br/><br/>(Maps are now required for PC-Movie decks. They determine what site path you will use.)
<br/><br/><i><a href="https://lotrtcgpc.net" target="_blank">See here for details.</a></i>
</div>
</div>
</div>

View File

@@ -623,8 +623,12 @@ var PCCards = {
//Aragorn,Elessar Telcontar FA
'10_127' : 'https://i.lotrtcgpc.net/promos/LOTR-EN10U025.0_card.jpg',
//Arwen, Queen of Elves and Men FA
'10_128' : 'https://i.lotrtcgpc.net/promos/LOTR-EN10U006.0_card.jpg'
'10_128' : 'https://i.lotrtcgpc.net/promos/LOTR-EN10U006.0_card.jpg',
//Maps
'100_2' : 'https://i.lotrtcgpc.net/sets/vset0/LOTR-ENV0S002.0_card.jpg',
'100_3' : 'https://i.lotrtcgpc.net/sets/vset0/LOTR-ENV0S003.0_card.jpg',
'100_4' : 'https://i.lotrtcgpc.net/sets/vset0/LOTR-ENV0S004.0_card.jpg'
}

View File

@@ -842,6 +842,15 @@ var GempLotrDeckBuildingUI = Class.extend({
cards.push($(this).data("card").blueprintId);
});
result += cards;
let formatCode = this.formatSelect.val();
if(formatCode == "pc_movie" || formatCode == "test_pc_movie") {
result += "|";
var map = $(".card", this.mapDiv);
if (map.length > 0)
result += map.data("card").blueprintId;
}
return result;
},
@@ -1075,6 +1084,10 @@ var GempLotrDeckBuildingUI = Class.extend({
if (ring.length > 0)
this.addCardToContainer(ring[0].getAttribute("blueprintId"), "deck", this.ringDiv, false).addClass("cardInDeck");
var map = root.getElementsByTagName("map");
if (map.length > 0)
this.addCardToContainer(map[0].getAttribute("blueprintId"), "deck", this.mapDiv, false).addClass("cardInDeck");
var sites = root.getElementsByTagName("site");
for (var i = 0; i < sites.length; i++)
this.addCardToContainer(sites[i].getAttribute("blueprintId"), "deck", this.siteDiv, false).addClass("cardInDeck");
@@ -1158,7 +1171,7 @@ var GempLotrDeckBuildingUI = Class.extend({
this.mapDiv.css({ display:"block", position:"absolute", left:padding, top:manageHeight + 2 * padding+ rowHeight, width:Math.floor((sitesWidth - padding) / 2), height:rowHeight });
this.mapGroup.setBounds(0, 0, Math.floor((sitesWidth - padding) / 2), rowHeight);
this.siteDiv.css({ position:"absolute", left:padding, top:manageHeight + 3 * padding + 2 * rowHeight, width:sitesWidth, height:deckHeight - rowHeight - 2 * padding});
this.siteDiv.css({ position:"absolute", left:padding, top:manageHeight + 3 * padding + 2 * rowHeight, width:sitesWidth, height:deckHeight - 2*rowHeight - 2 * padding});
this.siteGroup.setBounds(0, 0, sitesWidth, deckHeight - 2*rowHeight - 2 * padding);
}
else {

View File

@@ -0,0 +1,122 @@
{
100_2: {
cardInfo: {
imagePath: sets/LOTR-ENV0S002.0.jpg
javaClass: false
version: 0
collInfo: V0 2
rarity: X
setNum: V0
cardNum: 2
style: Standard
}
title: Path of the Fellowship
unique: false
type: Map
twilight: 0
effects: [
{
type: DeckBuildingRestriction
pile: sites
#We do it this way instead of checking for fellowship
# so that the error message shows all sites that don't
# match, instead of listing all the ones that do and
# making the user figure out which one isn't listed.
filter: or(siteblock(towers),siteblock(king),siteblock(shadows))
max: 0
text: Adventure Deck must only contain sites from the Fellowship block path.
}
]
gametext: Before the game begins, play this to your support area. Your adventure deck must only contain cards from the Fellowship site path.
lore: ""
promotext: ""
alts: {
promos: {
}
errata: {
}
}
}
100_3: {
cardInfo: {
imagePath: sets/LOTR-ENV0S003.0.jpg
javaClass: false
version: 0
collInfo: V0 3
rarity: X
setNum: V0
cardNum: 3
style: Standard
}
title: Road to the Towers
unique: false
type: Map
twilight: 0
effects: [
{
type: DeckBuildingRestriction
pile: sites
#We do it this way instead of checking for fellowship
# so that the error message shows all sites that don't
# match, instead of listing all the ones that do and
# making the user figure out which one isn't listed.
filter: or(siteblock(fellowship),siteblock(king),siteblock(shadows))
max: 0
text: Adventure deck must only contain sites from the Towers block site path.
}
]
gametext: Before the game begins, play this to your support area. Your adventure deck must only contain cards from the Towers site path.
lore: ""
promotext: ""
alts: {
promos: {
}
errata: {
}
}
}
100_4: {
cardInfo: {
imagePath: sets/LOTR-ENV0S004.0.jpg
javaClass: false
//parentId: 1_319
//One of: Variant, Errata, Reprint
//parentType: Variant
//parentPath: alts/promo
version: 0
collInfo: V0 4
rarity: X
setNum: V0
cardNum: 4
style: Standard
}
title: Journey of the King
unique: false
type: Map
twilight: 0
effects: [
{
type: DeckBuildingRestriction
pile: sites
#We do it this way instead of checking for fellowship
# so that the error message shows all sites that don't
# match, instead of listing all the ones that do and
# making the user figure out which one isn't listed.
filter: or(siteblock(fellowship),siteblock(towers),siteblock(shadows))
max: 0
text: Adventure Deck must only contain sites from the King block site path.
}
]
gametext: Before the game begins, play this to your support area. Your adventure deck must only contain cards from the King site path.
lore: ""
promotext: ""
alts: {
promos: {
}
errata: {
}
}
}
}

View File

@@ -53,11 +53,15 @@
name: Movie Block (PC)
code: pc_movie
order: 4
sites: KING
sites: MULTIPATH
usesMaps: true
winAtEndOfRegroup: true
discardPileIsPublic: true
set: [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 101
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 101
]
banned: [
"100_1"
]
erratasets: [
51,52,53,54,55,56,57,58,59,60
@@ -595,6 +599,7 @@
sites: MULTIPATH
winAtEndOfRegroup: true
discardPileIsPublic: true
usesMaps: true
hall: true
playtest: true
set: [

View File

@@ -1,5 +1,18 @@
package com.gempukku.lotro.common;
public enum CardType implements Filterable {
THE_ONE_RING, SITE, COMPANION, ALLY, MINION, POSSESSION, ARTIFACT, EVENT, CONDITION, FOLLOWER, ADVENTURE
THE_ONE_RING,
SITE,
//Characters
COMPANION, ALLY, MINION,
//Items
POSSESSION, ARTIFACT,
EVENT,
CONDITION,
FOLLOWER,
//Unused
ADVENTURE,
//Player's Council card types
MAP
}

View File

@@ -48,6 +48,7 @@ public class JSONDefs {
public int minimumDeckSize = 60;
public int maximumSameName = 4;
public boolean mulliganRule = true;
public boolean usesMaps = false;
public ArrayList<Integer> set;
public ArrayList<String> banned = new ArrayList<>();
public ArrayList<String> restricted = new ArrayList<>();

View File

@@ -0,0 +1,30 @@
package com.gempukku.lotro.common;
/**
* Represents the different kinds of deck piles that the player comes to
* the table with.
*/
public enum PileType implements Filterable {
FREE_PEOPLES, SHADOW, ADVENTURE, RING_BEARER, RING, MAP;
public static PileType Parse(String value) {
value = value
.toUpperCase()
.replace(" ", "_")
.replace("-", "_");
if(value.contains("FREEPS") || value.contains("FREE") || value.contains("PEOPLE"))
return FREE_PEOPLES;
if(value.contains("SITE") || value.contains("ADVENTURE"))
return ADVENTURE;
for (PileType type : values()) {
if (type.toString().equalsIgnoreCase(value))
return type;
}
return null;
}
}

View File

@@ -0,0 +1,4 @@
package com.gempukku.lotro.common;
public record Result(boolean success, String message) {
}

View File

@@ -5,6 +5,7 @@ import com.gempukku.lotro.filters.Filters;
import com.gempukku.lotro.game.ExtraPlayCost;
import com.gempukku.lotro.game.LotroCardBlueprint;
import com.gempukku.lotro.game.PhysicalCard;
import com.gempukku.lotro.game.PhysicalCardImpl;
import com.gempukku.lotro.game.state.LotroGame;
import com.gempukku.lotro.logic.PlayUtils;
import com.gempukku.lotro.logic.actions.*;
@@ -82,8 +83,14 @@ public class BuiltLotroCardBlueprint implements LotroCardBlueprint {
private ExtraPossessionClassTest extraPossessionClassTest;
private PreGameDeckValidation deckValidation;
// Building methods
public void setDeckValidation(PreGameDeckValidation validation) {
this.deckValidation = validation;
}
public void setAllyHomeSites(SitesBlock block, int[] numbers) {
this.allyHomeBlock = block;
this.allyHomeSites = numbers;
@@ -332,6 +339,14 @@ public class BuiltLotroCardBlueprint implements LotroCardBlueprint {
// Implemented methods
@Override
public Result validatePreGameDeckCheck(List<PhysicalCardImpl> freeps, List<PhysicalCardImpl> shadow,
List<PhysicalCardImpl> sites, PhysicalCardImpl rb, PhysicalCardImpl ring, PhysicalCardImpl map) {
if(deckValidation != null)
return deckValidation.validatePreGameDeckCheck(freeps, shadow, sites, rb, ring, map);
return new Result(true, null);
}
@Override
public Side getSide() {
return side;
@@ -975,10 +990,10 @@ public class BuiltLotroCardBlueprint implements LotroCardBlueprint {
throw new InvalidCardDefinitionException("Card has to have a title");
if (cardType == null)
throw new InvalidCardDefinitionException("Card has to have a type");
if (cardType != CardType.THE_ONE_RING && cardType != CardType.SITE && side == null)
throw new InvalidCardDefinitionException("Only The One Ring does not have a side defined");
if (cardType != CardType.THE_ONE_RING && cardType != CardType.SITE && culture == null)
throw new InvalidCardDefinitionException("Only The One Ring does not have a culture defined");
if (cardType != CardType.THE_ONE_RING && cardType != CardType.SITE && cardType != CardType.MAP && side == null)
throw new InvalidCardDefinitionException("All cards except The One Ring, Sites, and Maps must have a side defined");
if (cardType != CardType.THE_ONE_RING && cardType != CardType.SITE && cardType != CardType.MAP && culture == null)
throw new InvalidCardDefinitionException("All cards except The One Ring, Sites, and Maps have a culture defined");
if (siteNumber != 0
&& cardType != CardType.SITE
&& cardType != CardType.MINION)

View File

@@ -33,7 +33,7 @@ public class LotroCardBlueprintBuilder implements CardGenerationEnvironment {
fieldProcessors.put("itemclass", new PossessionClassFieldProcessor());
fieldProcessors.put("keyword", new KeywordFieldProcessor());
fieldProcessors.put("keywords", new KeywordFieldProcessor());
fieldProcessors.put("twilight", new CostFieldProcessor());
fieldProcessors.put("twilight", new TwilightCostFieldProcessor());
fieldProcessors.put("strength", new StrengthFieldProcessor());
fieldProcessors.put("vitality", new VitalityFieldProcessor());
fieldProcessors.put("resistance", new ResistanceFieldProcessor());

View File

@@ -0,0 +1,13 @@
package com.gempukku.lotro.cards.build;
import com.gempukku.lotro.common.Result;
import com.gempukku.lotro.game.PhysicalCard;
import com.gempukku.lotro.game.PhysicalCardImpl;
import com.gempukku.lotro.game.state.LotroGame;
import java.util.List;
public interface PreGameDeckValidation {
Result validatePreGameDeckCheck(List<PhysicalCardImpl> freeps, List<PhysicalCardImpl> shadow,
List<PhysicalCardImpl> sites, PhysicalCardImpl rb, PhysicalCardImpl ring, PhysicalCardImpl map);
}

View File

@@ -22,6 +22,7 @@ public class EffectFieldProcessor implements FieldProcessor {
effectProcessors.put("activatedtrigger", new ActivatedTriggerEffectProcessor());
effectProcessors.put("aidcost", new AidCost());
effectProcessors.put("copycard", new CopyCard());
effectProcessors.put("deckbuildingrestriction", new DeckBuildingRestrictionGameTextProcessor());
effectProcessors.put("discardedfromplaytrigger", new DiscardedFromPlayTriggerEffectProcessor());
effectProcessors.put("discount", new PotentialDiscount());
effectProcessors.put("event", new EventEffectProcessor());

View File

@@ -21,7 +21,7 @@ public class FieldUtils {
if (value == null)
return defaultValue;
if (!(value instanceof Number))
throw new InvalidCardDefinitionException("Unknown type in " + key + " field");
throw new InvalidCardDefinitionException("Value in " + key + " field must be an integer, not an evaluated value.");
return ((Number) value).intValue();
}

View File

@@ -5,7 +5,7 @@ import com.gempukku.lotro.cards.build.CardGenerationEnvironment;
import com.gempukku.lotro.cards.build.FieldProcessor;
import com.gempukku.lotro.cards.build.InvalidCardDefinitionException;
public class CostFieldProcessor implements FieldProcessor {
public class TwilightCostFieldProcessor implements FieldProcessor {
@Override
public void processField(String key, Object value, BuiltLotroCardBlueprint blueprint, CardGenerationEnvironment environment) throws InvalidCardDefinitionException {
blueprint.setCost(FieldUtils.getInteger(value, key));

View File

@@ -0,0 +1,99 @@
package com.gempukku.lotro.cards.build.field.effect;
import com.gempukku.lotro.cards.build.*;
import com.gempukku.lotro.cards.build.field.EffectProcessor;
import com.gempukku.lotro.cards.build.field.FieldUtils;
import com.gempukku.lotro.common.PileType;
import com.gempukku.lotro.common.Filterable;
import com.gempukku.lotro.common.Result;
import com.gempukku.lotro.filters.Filter;
import com.gempukku.lotro.filters.Filters;
import com.gempukku.lotro.game.LotroCardBlueprint;
import com.gempukku.lotro.game.PhysicalCardImpl;
import com.gempukku.lotro.logic.GameUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class DeckBuildingRestrictionGameTextProcessor implements EffectProcessor {
@Override
public void processEffect(JSONObject effectObj, BuiltLotroCardBlueprint blueprint, CardGenerationEnvironment environment) throws InvalidCardDefinitionException {
FieldUtils.validateAllowedFields(effectObj, "filter", "pile", "min", "max", "text");
final int max = FieldUtils.getInteger(effectObj.get("max"), "max", 1000);
final int min = FieldUtils.getInteger(effectObj.get("max"), "min", 0);
final String pileStr = FieldUtils.getString(effectObj.get("pile"), "pile");
final PileType pileType = PileType.Parse(pileStr);
final String failureMsg = FieldUtils.getString(effectObj.get("text"), "text");
if(StringUtils.isBlank(failureMsg))
throw new InvalidCardDefinitionException("'text' field is required for deck validation operations to communicate to the player what validation they are faling in the deckbuilder. Ensure it is user friendly.");
if(max < min)
throw new InvalidCardDefinitionException("'max' field may not be less than 'min' field for deck validations.");
if(min < 0)
throw new InvalidCardDefinitionException("'min' field may not be less than zero for deck validations.");
final String filterStr = FieldUtils.getString(effectObj.get("filter"), "attachedTo", "any");
final var filterSource = environment.getFilterFactory().generateFilter(filterStr, environment);
blueprint.setDeckValidation(
(freeps, shadow, sites, rb, ring, map) -> {
var filter = Filters.and(filterSource.getFilterable(null));
var found = new ArrayList<LotroCardBlueprint>();
if(pileType == null || pileType == PileType.FREE_PEOPLES) {
found.addAll(validate(freeps, filter));
}
if(pileType == null || pileType == PileType.SHADOW) {
found.addAll(validate(shadow, filter));
}
if(pileType == null || pileType == PileType.ADVENTURE) {
found.addAll(validate(sites, filter));
}
if(pileType == null || pileType == PileType.RING_BEARER) {
found.addAll(validate(Collections.singletonList(rb), filter));
}
if(pileType == null || pileType == PileType.RING) {
found.addAll(validate(Collections.singletonList(ring), filter));
}
if(pileType == null || pileType == PileType.MAP) {
found.addAll(validate(Collections.singletonList(map), filter));
}
final String successMsg = "Deck meets restrictions placed by " + GameUtils.getFullName(blueprint) + ".";
final String fullFailureMsg = "Deck does not meet restrictions placed by " + GameUtils.getFullName(blueprint) + ": "
+ failureMsg + ". ";
if(found.size() > max || found.size() < min)
return new Result(false, fullFailureMsg + "Found " + found.size() + ": "
+ found.stream().map(GameUtils::getFullName).collect(Collectors.joining(", ")));
return new Result(true, successMsg);
});
blueprint.setExtraPossessionClassTest(
(game, self, attachedTo) -> {
DefaultActionContext actionContext = new DefaultActionContext(self.getOwner(), game, self, null, null);
final Filterable attachedFilterable = filterSource.getFilterable(actionContext);
return Filters.and(attachedFilterable).accepts(game, attachedTo);
});
}
protected List<LotroCardBlueprint> validate(List<PhysicalCardImpl> cards, Filter filter) {
var found = new ArrayList<LotroCardBlueprint>();
for(var card : cards) {
if(filter.accepts(null, card)) {
found.add(card.getBlueprint());
}
}
return found;
}
}

View File

@@ -129,4 +129,9 @@ public interface LotroCardBlueprint {
void appendAidCosts(LotroGame game, CostToEffectAction action, PhysicalCard self);
boolean skipUniquenessCheck();
default Result validatePreGameDeckCheck(List<PhysicalCardImpl> freeps, List<PhysicalCardImpl> shadow,
List<PhysicalCardImpl> sites, PhysicalCardImpl rb, PhysicalCardImpl ring, PhysicalCardImpl map) {
return new Result(true, null);
}
}

View File

@@ -8,61 +8,62 @@ import java.util.List;
import java.util.Map;
public interface LotroFormat {
public boolean isOrderedSites();
boolean isOrderedSites();
public boolean canCancelRingBearerSkirmish();
boolean canCancelRingBearerSkirmish();
public boolean hasRuleOfFour();
boolean hasRuleOfFour();
boolean usesMaps();
public boolean hasMulliganRule();
boolean hasMulliganRule();
public boolean winWhenShadowReconciles();
boolean winWhenShadowReconciles();
public boolean discardPileIsPublic();
boolean discardPileIsPublic();
public boolean winOnControlling5Sites();
boolean winOnControlling5Sites();
public boolean isPlaytest();
public boolean hallVisible();
boolean isPlaytest();
boolean hallVisible();
public String getName();
String getName();
public String getCode();
public int getOrder();
String getCode();
int getOrder();
public String validateCard(String cardId);
String validateCard(String cardId);
public List<String> validateDeck(LotroDeck deck);
public String validateDeckForHall(LotroDeck deck);
List<String> validateDeck(LotroDeck deck);
String validateDeckForHall(LotroDeck deck);
public LotroDeck applyErrata(LotroDeck deck);
LotroDeck applyErrata(LotroDeck deck);
public List<Integer> getValidSets();
List<Integer> getValidSets();
public List<String> getBannedCards();
List<String> getBannedCards();
public List<String> getRestrictedCards();
List<String> getRestrictedCards();
public List<String> getValidCards();
List<String> getValidCards();
public List<String> getLimit2Cards();
List<String> getLimit2Cards();
public List<String> getLimit3Cards();
List<String> getLimit3Cards();
public List<String> getRestrictedCardNames();
List<String> getRestrictedCardNames();
public Map<String,String> getErrataCardMap();
Map<String,String> getErrataCardMap();
public String applyErrata(String bpID);
String applyErrata(String bpID);
public List<String> findBaseCards(String bpID);
List<String> findBaseCards(String bpID);
public SitesBlock getSiteBlock();
SitesBlock getSiteBlock();
public String getSurveyUrl();
String getSurveyUrl();
public int getHandSize();
int getHandSize();
public Adventure getAdventure();
public JSONDefs.Format Serialize();
Adventure getAdventure();
JSONDefs.Format Serialize();
}

View File

@@ -94,7 +94,7 @@ public class EventSerializer {
deckElement.setAttribute("rb", deck.getRingBearer());
deckElement.setAttribute("ring", deck.getRing());
deckElement.setAttribute("sites", String.join(",", deck.getSites()));
deckElement.setAttribute("deck", String.join(",", deck.getAdventureCards()));
deckElement.setAttribute("deck", String.join(",", deck.getDrawDeckCards()));
eventElem.appendChild(deckElement);
}

View File

@@ -62,7 +62,7 @@ public class DefaultLotroGame implements LotroGame {
LotroDeck lotroDeck = decks.get(playerId);
deck.addAll(lotroDeck.getSites());
deck.addAll(lotroDeck.getAdventureCards());
deck.addAll(lotroDeck.getDrawDeckCards());
cards.put(playerId, deck);
ringBearers.put(playerId, lotroDeck.getRingBearer());

View File

@@ -7,6 +7,7 @@ import java.util.List;
public class LotroDeck {
private String _ringBearer;
private String _ring;
private String _map;
private final List<String> _siteCards = new ArrayList<>();
private final List<String> _nonSiteCards = new ArrayList<>();
private final String _deckName;
@@ -29,6 +30,7 @@ public class LotroDeck {
public void setRing(String ring) {
_ring = ring;
}
public void setMap(String map) { _map = map; }
public void addCard(String card) {
_nonSiteCards.add(card);
@@ -38,7 +40,7 @@ public class LotroDeck {
_siteCards.add(card);
}
public List<String> getAdventureCards() {
public List<String> getDrawDeckCards() {
return Collections.unmodifiableList(_nonSiteCards);
}
@@ -54,6 +56,10 @@ public class LotroDeck {
return _ring;
}
public String getMap() {
return _map;
}
public String getTargetFormat() { return _targetFormat; }
public void setTargetFormat(String value) { _targetFormat = value; }

View File

@@ -1,7 +1,6 @@
package com.gempukku.lotro.collection;
import com.gempukku.lotro.common.Names;
import com.gempukku.lotro.competitive.PlayerStanding;
import com.gempukku.lotro.game.*;
import com.gempukku.lotro.game.formats.LotroFormatLibrary;
import com.gempukku.lotro.logic.GameUtils;
@@ -137,7 +136,7 @@ public class DeckRenderer {
}
DefaultCardCollection deckCards = new DefaultCardCollection();
for (String card : deck.getAdventureCards()) {
for (String card : deck.getDrawDeckCards()) {
deckCards.addItem(_bpLibrary.getBaseBlueprintId(card), 1);
}
for (String site : deck.getSites()) {
@@ -216,7 +215,7 @@ public class DeckRenderer {
}
DefaultCardCollection deckCards = new DefaultCardCollection();
for (String card : deck.getAdventureCards()) {
for (String card : deck.getDrawDeckCards()) {
deckCards.addItem(_bpLibrary.getBaseBlueprintId(card), 1);
}
for (String site : deck.getSites()) {

View File

@@ -17,10 +17,14 @@ public class DeckSerialization {
sb.append(deck.getSites().get(i));
}
sb.append("|");
for (int i = 0; i < deck.getAdventureCards().size(); i++) {
for (int i = 0; i < deck.getDrawDeckCards().size(); i++) {
if (i > 0)
sb.append(",");
sb.append(deck.getAdventureCards().get(i));
sb.append(deck.getDrawDeckCards().get(i));
}
if (deck.getMap() != null) {
sb.append("|");
sb.append(deck.getMap());
}
return sb.toString();
@@ -47,6 +51,11 @@ public class DeckSerialization {
if (!card.equals(""))
deck.addCard(card);
}
if (parts.length > 4)
for (String card : parts[4].split(",")) {
if (!card.equals(""))
deck.setMap(card);
}
return deck;
}

View File

@@ -17,7 +17,7 @@ public class CollectionUtils {
incrementCardCount(counts, ringBearer, 1);
for (String site : deck.getSites())
incrementCardCount(counts, site, 1);
for (String adventureCard : deck.getAdventureCards())
for (String adventureCard : deck.getDrawDeckCards())
incrementCardCount(counts, adventureCard, 1);
return counts;
}

View File

@@ -199,7 +199,7 @@ public class GameRecorder {
deckElement.setAttribute("rb", deck.getRingBearer());
deckElement.setAttribute("ring", deck.getRing());
deckElement.setAttribute("sites", String.join(",", deck.getSites()));
deckElement.setAttribute("deck", String.join(",", deck.getAdventureCards()));
deckElement.setAttribute("deck", String.join(",", deck.getDrawDeckCards()));
info.appendChild(deckElement);
}

View File

@@ -188,7 +188,7 @@ public class LotroServer extends AbstractServer {
cnt++;
}
if (cnt != 3)
if (cnt < 3 || cnt > 4)
return null;
return _deckDao.buildDeckFromContents(deckName, contents, targetFormat, notes);

View File

@@ -57,7 +57,7 @@ public class ReplayMetadata {
TargetFormat = deck.getTargetFormat();
DeckName = deck.getDeckName();
AdventureDeck = deck.getSites();
DrawDeck = deck.getAdventureCards();
DrawDeck = deck.getDrawDeckCards();
RingBearer = deck.getRingBearer();
Ring = deck.getRing();
}};

View File

@@ -21,6 +21,7 @@ public class DefaultLotroFormat implements LotroFormat {
private boolean _validateShadowFPCount = true;
private int _maximumSameName = 4;
private final boolean _mulliganRule;
private final boolean _usesMaps;
private final boolean _canCancelRingBearerSkirmish;
private final boolean _hasRuleOfFour;
private final boolean _winAtEndOfRegroup;
@@ -40,10 +41,28 @@ public class DefaultLotroFormat implements LotroFormat {
private final List<String> _limit3Cards = new ArrayList<>();
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.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);
_adventure = adventureLibrary.getAdventure(def.adventure);
_library = library;
_name = def.name;
_code = def.code;
_order = def.order;
_surveyUrl = def.surveyUrl;
_siteBlock = SitesBlock.findBlock(def.sites);
_validateShadowFPCount = def.validateShadowFPCount;
_minimumDeckSize = def.minimumDeckSize;
_maximumSameName = def.maximumSameName;
_mulliganRule = def.mulliganRule;
_usesMaps = def.usesMaps;
_canCancelRingBearerSkirmish = def.cancelRingBearerSkirmish;
_hasRuleOfFour = def.ruleOfFour;
_winAtEndOfRegroup = def.winAtEndOfRegroup;
_discardPileIsPublic = def.discardPileIsPublic;
_winOnControlling5Sites = def.winOnControlling5Sites;
_isPlaytest = def.playtest;
_hallVisible = def.hall;
if(def.set != null)
def.set.forEach(this::addValidSet);
@@ -68,32 +87,6 @@ public class DefaultLotroFormat implements LotroFormat {
def.errata.forEach(this::addCardErrata);
}
public DefaultLotroFormat(LotroCardBlueprintLibrary library,
Adventure adventure, String name, String code, int order, String surveyUrl,
SitesBlock siteBlock,
boolean validateShadowFPCount, int minimumDeckSize, int maximumSameName, boolean mulliganRule,
boolean canCancelRingBearerSkirmish, boolean hasRuleOfFour, boolean winAtEndOfRegroup, boolean discardPileIsPublic,
boolean winOnControlling5Sites, boolean playtest, boolean hallVisible) {
_adventure = adventure;
_library = library;
_name = name;
_code = code;
_order = order;
_surveyUrl = surveyUrl;
_siteBlock = siteBlock;
_validateShadowFPCount = validateShadowFPCount;
_minimumDeckSize = minimumDeckSize;
_maximumSameName = maximumSameName;
_mulliganRule = mulliganRule;
_canCancelRingBearerSkirmish = canCancelRingBearerSkirmish;
_hasRuleOfFour = hasRuleOfFour;
_winAtEndOfRegroup = winAtEndOfRegroup;
_discardPileIsPublic = discardPileIsPublic;
_winOnControlling5Sites = winOnControlling5Sites;
_isPlaytest = playtest;
_hallVisible = hallVisible;
}
@Override
public Adventure getAdventure() {
return _adventure;
@@ -144,6 +137,11 @@ public class DefaultLotroFormat implements LotroFormat {
return _hasRuleOfFour;
}
@Override
public boolean usesMaps() {
return _usesMaps;
}
@Override
public boolean winOnControlling5Sites() {
return _winOnControlling5Sites;
@@ -296,7 +294,7 @@ public class DefaultLotroFormat implements LotroFormat {
if (_validCards.contains(blueprintId) || _errataCardMap.containsValue(blueprintId))
return null;
if (_validSets.size() > 0 && !isValidInSets(blueprintId))
if (!_validSets.isEmpty() && !isValidInSets(blueprintId))
return "Deck contains card not from valid set: " + GameUtils.getFullName(_library.getLotroCardBlueprint(blueprintId));
// Banned cards
@@ -362,6 +360,12 @@ public class DefaultLotroFormat implements LotroFormat {
result.add(valid);
}
// Map
valid = validateMap(deck);
if(valid != null && !valid.isEmpty()) {
result.add(valid);
}
// Deck
valid = validateDeckStructure(deck);
if(valid != null && !valid.isEmpty()) {
@@ -370,7 +374,7 @@ public class DefaultLotroFormat implements LotroFormat {
String prevLine = "";
String newLine = "";
for (String card : deck.getAdventureCards()){
for (String card : deck.getDrawDeckCards()){
newLine = validateCard(card);
if(newLine == null || newLine.isEmpty())
continue;
@@ -413,7 +417,7 @@ public class DefaultLotroFormat implements LotroFormat {
if (deck.getRingBearer() != null) {
processCardCounts(deck.getRingBearer(), cardCountByName, cardCountByBaseBlueprintId);
}
for (String blueprintId : deck.getAdventureCards())
for (String blueprintId : deck.getDrawDeckCards())
processCardCounts(blueprintId, cardCountByName, cardCountByBaseBlueprintId);
for (String blueprintId : deck.getSites())
processCardCounts(blueprintId, cardCountByName, cardCountByBaseBlueprintId);
@@ -475,7 +479,7 @@ public class DefaultLotroFormat implements LotroFormat {
if (deck.getRing() != null) {
deckWithErrata.setRing(applyErrata(deck.getRing()));
}
for (String card : deck.getAdventureCards()) {
for (String card : deck.getDrawDeckCards()) {
deckWithErrata.addCard(applyErrata(card));
}
for (String site : deck.getSites()) {
@@ -510,13 +514,13 @@ public class DefaultLotroFormat implements LotroFormat {
private String validateDeckStructure(LotroDeck deck) {
String result = "";
if (deck.getAdventureCards().size() < _minimumDeckSize) {
result += "Deck contains below minimum number of cards: " + deck.getAdventureCards().size() + "<" + _minimumDeckSize + ".\n";
if (deck.getDrawDeckCards().size() < _minimumDeckSize) {
result += "Deck contains below minimum number of cards: " + deck.getDrawDeckCards().size() + "<" + _minimumDeckSize + ".\n";
}
if (_validateShadowFPCount) {
int shadow = 0;
int fp = 0;
for (String blueprintId : deck.getAdventureCards()) {
for (String blueprintId : deck.getDrawDeckCards()) {
try {
LotroCardBlueprint card = _library.getLotroCardBlueprint(blueprintId);
if (card.getSide() == Side.SHADOW)
@@ -673,6 +677,52 @@ public class DefaultLotroFormat implements LotroFormat {
return validateCard(ringbp);
}
private String validateMap(LotroDeck deck) {
String mapBP = deck.getMap();
if (!_usesMaps)
return null;
if (mapBP == null)
return "Deck doesn't have a Map (try the King map if converting a Movie deck).";
try {
var map = _library.getLotroCardBlueprint(mapBP);
if (map.getCardType() != CardType.MAP)
return "Card assigned as Map is not a Map card.";
var check = validateCard(mapBP);
if(check != null)
return check;
var freeps = new ArrayList<PhysicalCardImpl>();
var shadow = new ArrayList<PhysicalCardImpl>();
var sites = new ArrayList<PhysicalCardImpl>();
for(var bp : deck.getDrawDeckCards()) {
var blueprint = _library.getLotroCardBlueprint(bp);
if(blueprint.getSide() == Side.FREE_PEOPLE) {
freeps.add(new PhysicalCardImpl(0, bp, "", blueprint));
}
else if(blueprint.getSide() == Side.SHADOW) {
shadow.add(new PhysicalCardImpl(0, bp, "", blueprint));
}
}
for(var bp : deck.getSites()) {
sites.add(new PhysicalCardImpl(0, bp, "", _library.getLotroCardBlueprint(bp)));
}
var rb = new PhysicalCardImpl(0, deck.getRingBearer(), "", _library.getLotroCardBlueprint(deck.getRingBearer()));
var ring = new PhysicalCardImpl(0, deck.getRing(), "", _library.getLotroCardBlueprint(deck.getRing()));
var result = map.validatePreGameDeckCheck(freeps, shadow, sites, rb, ring, new PhysicalCardImpl(0, mapBP, "", map));
if(!result.success())
return result.message();
return null;
}
catch(CardNotFoundException exception)
{
return CardRemovedError + ": " + mapBP;
}
}
private String validateRingBearer(LotroDeck deck) {
String rb = deck.getRingBearer();
if (rb == null)
@@ -730,6 +780,7 @@ public class DefaultLotroFormat implements LotroFormat {
minimumDeckSize = _minimumDeckSize;
maximumSameName = _maximumSameName;
mulliganRule = _mulliganRule;
usesMaps = _usesMaps;
set = null;
banned = null;
restricted = null;

View File

@@ -23,7 +23,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.sql.SQLException;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
@@ -602,7 +601,7 @@ public class HallServer extends AbstractServer {
for (String site : lotroDeck.getSites())
filteredSpecialCardsDeck.addSite(filterCard(site, ownedCollection));
for (Map.Entry<String, Integer> cardCount : CollectionUtils.getTotalCardCount(lotroDeck.getAdventureCards()).entrySet()) {
for (Map.Entry<String, Integer> cardCount : CollectionUtils.getTotalCardCount(lotroDeck.getDrawDeckCards()).entrySet()) {
String blueprintId = cardCount.getKey();
int count = cardCount.getValue();