diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/CollectionRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/CollectionRequestHandler.java index 775b2588a..bcd2436e1 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/CollectionRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/CollectionRequestHandler.java @@ -36,6 +36,7 @@ public class CollectionRequestHandler extends LotroServerRequestHandler implemen private LotroCardBlueprintLibrary _library; private LotroFormatLibrary _formatLibrary; private SortAndFilterCards _sortAndFilterCards; + private ImportCards _importCards; public CollectionRequestHandler(Map context) { super(context); @@ -47,12 +48,15 @@ public class CollectionRequestHandler extends LotroServerRequestHandler implemen _library = extractObject(context, LotroCardBlueprintLibrary.class); _formatLibrary = extractObject(context, LotroFormatLibrary.class); _sortAndFilterCards = new SortAndFilterCards(); + _importCards = new ImportCards(); } @Override public void handleRequest(String uri, HttpRequest request, Map context, ResponseWriter responseWriter, String remoteIp) throws Exception { if (uri.equals("") && request.getMethod() == HttpMethod.GET) { getCollectionTypes(request, responseWriter); + } else if (uri.startsWith("/import/") && request.getMethod() == HttpMethod.GET) { + importCollection(request, responseWriter); } else if (uri.startsWith("/") && request.getMethod() == HttpMethod.POST) { openPack(request, uri.substring(1), responseWriter); } else if (uri.startsWith("/") && request.getMethod() == HttpMethod.GET) { @@ -61,6 +65,50 @@ public class CollectionRequestHandler extends LotroServerRequestHandler implemen throw new HttpProcessingException(404); } } + + private void importCollection(HttpRequest request, ResponseWriter responseWriter) throws Exception { + QueryStringDecoder queryDecoder = new QueryStringDecoder(request.getUri()); + String participantId = getQueryParameterSafely(queryDecoder, "participantId"); + String rawDecklist = getQueryParameterSafely(queryDecoder, "decklist"); + + Player resourceOwner = getResourceOwnerSafely(request, participantId); + + CardCollection collection = constructCollection(resourceOwner, "default"); + + if (collection == null) + throw new HttpProcessingException(404); + + Iterable items = collection.getAll(); + List importResult = _importCards.process(rawDecklist, items, _library); + + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + + Document doc = documentBuilder.newDocument(); + + Element collectionElem = doc.createElement("collection"); + collectionElem.setAttribute("count", String.valueOf(importResult.size())); + doc.appendChild(collectionElem); + + for (int i = 0; i < importResult.size(); i++) { + CardCollection.Item item = importResult.get(i); + String blueprintId = item.getBlueprintId(); + if (item.getType() == CardCollection.Item.Type.CARD) { + Element card = doc.createElement("card"); + card.setAttribute("count", String.valueOf(item.getCount())); + card.setAttribute("blueprintId", blueprintId); + LotroCardBlueprint blueprint = _library.getLotroCardBlueprint(blueprintId); + appendCardSide(card, blueprint); + appendCardGroup(card, blueprint); + collectionElem.appendChild(card); + } + } + + Map headers = new HashMap(); + processDeliveryServiceNotification(request, headers); + + responseWriter.writeXmlResponse(doc, headers); + } private void getCollection(HttpRequest request, String collectionType, ResponseWriter responseWriter) throws Exception { QueryStringDecoder queryDecoder = new QueryStringDecoder(request.getUri()); diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set0.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set0.json index 536712871..99c10977c 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set0.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set0.json @@ -205,7 +205,7 @@ }, "0_64": { "title": "*Gandalf", - "subtitle": "Stormcraw", + "subtitle": "Stormcrow", "culture": "gandalf", "cost": 4, "type": "companion", diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-dwarven.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-dwarven.json index 82d90c4eb..525d2ba85 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-dwarven.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-dwarven.json @@ -373,7 +373,7 @@ }, "1_17": { "title": "*Grimir", - "subtitle": "Dwarven Elder,", + "subtitle": "Dwarven Elder", "culture": "dwarven", "cost": 1, "type": "ally", diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-moria.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-moria.json index d08eab124..de082c642 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-moria.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1-moria.json @@ -353,7 +353,7 @@ } }, "1_179": { - "title": "Goblin Scavenger", + "title": "Goblin Scavengers", "culture": "moria", "cost": 3, "type": "minion", @@ -976,4 +976,4 @@ } ] } -} \ No newline at end of file +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1.json index ebb14c455..a1e0646cf 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set1/set1.json @@ -1003,7 +1003,7 @@ } }, "1_354": { - "title": "Anduin Wilderlands", + "title": "Anduin Wilderland", "cost": 6, "type": "site", "site": 7, diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set19.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set19.json index 9badc3d49..e8edfca96 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set19.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set19.json @@ -514,7 +514,7 @@ "19_14": { "title": "Not Bound To His Fate", "culture": "gondor", - "cost": 3, + "cost": 2, "type": "condition", "strength": 1, "target": "culture(gondor),man", diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set9-2.json b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set9-2.json index 226e778fb..580a02e13 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/cards/set9-2.json +++ b/gemp-lotr/gemp-lotr-async/src/main/web/cards/set9-2.json @@ -75,7 +75,7 @@ ] }, "9_29": { - "title": "Slippery as Fished", + "title": "Slippery as Fishes", "side": "shadow", "culture": "gollum", "cost": 1, diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/cardFilter.js b/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/cardFilter.js index 528be6558..9ac4955d9 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/cardFilter.js +++ b/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/cardFilter.js @@ -107,10 +107,9 @@ var CardFilter = Class.extend({ this.fullFilterDiv = $("
"); this.setSelect = $(""); this.nameInput = $(""); this.sortSelect = $(""); + var getDecklistTextBut = $("").button(); + + var importDialogDiv = $("
"); + importDialogDiv.append(deckImport); + importDialogDiv.append(getDecklistTextBut); + that.deckImportDialog.append(importDialogDiv); + + getDecklistTextBut.click( + function () { + var decklist = $('textarea[decklist="decklist"]').val() + that.parseDecklist(decklist); + } + ); + that.deckImportDialog.dialog("open"); + }, + + parseDecklist:function(rawText) { + this.clearDeck(); + var that = this; + var rawTextList = rawText.split("\n"); + var formattedText = ""; + for (var i = 0; i < rawTextList.length; i++) { + if (rawTextList[i] != "") { + var line = that.removeNotes(rawTextList[i]).toLowerCase(); + line = line.replace(/[\*•]/g,"").replace(/’/g,"'") + .replace(/starting|start|ring-bearer:|ring:/g,"") + formattedText = formattedText + line.trim() + "~"; + } + } + + this.importDeckCollection(formattedText, function (xml) { + log(xml); + + var cards = xml.documentElement.getElementsByTagName("card"); + for (var i = 0; i < cards.length; i++) { + var cardElem = cards[i]; + var blueprintId = cardElem.getAttribute("blueprintId"); + var side = cardElem.getAttribute("side"); + var group = cardElem.getAttribute("group"); + if (group == "ringBearer") { + that.addCardToContainer(blueprintId, "special", that.ringBearerDiv, false).addClass("cardInDeck"); + that.layoutSpecialGroups(); + } + else if (group == "ring") { + that.addCardToContainer(blueprintId, "special", that.ringDiv, false).addClass("cardInDeck"); + that.layoutSpecialGroups(); + } + else { + that.addCardToDeckAndLayout(blueprintId, side); + } + } + $("#editingDeck").text("Imported Deck (unsaved)"); + }); + }, + + removeNotes:function(line) { + var processedLine = line; + var hasNotes = false; + var start = line.indexOf("("); + var end = line.indexOf(")", start); + if (start < 0 && end < 0) { + start = line.indexOf("["); + end = line.indexOf("]", start); + } + if (start > 0) { + processedLine = line.slice(0,start) + if (end > 0) { + processedLine = processedLine + line.slice(end+1); + } + } + else if (end > 0) { + processedLine = line.slice(end+1); + } + if (processedLine.indexOf("(") > -1 || processedLine.indexOf(")") > -1 || + processedLine.indexOf("[") > -1 || processedLine.indexOf("]") > -1) { + return this.removeNotes(processedLine); + } + return processedLine; + }, + + importDeckCollection:function (decklist, callback) { + this.comm.importCollection(decklist, function (xml) { + callback(xml); + }, { + "414":function () { + alert("Deck too large to import."); + } + }); + }, loadDeckList:function () { var that = this; diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/setConfig.json b/gemp-lotr/gemp-lotr-cards/src/main/resources/setConfig.json index 14d8fe335..03ba11955 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/setConfig.json +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/setConfig.json @@ -144,6 +144,7 @@ "rarityFile": "/set40-rarity.txt", "originalSet": false, "merchantable": false, - "needsLoading": false + "needsLoading": false, + "playable": false } ] diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/ImportCards.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/ImportCards.java new file mode 100644 index 000000000..dc8432132 --- /dev/null +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/ImportCards.java @@ -0,0 +1,149 @@ +package com.gempukku.lotro.game; + +import com.gempukku.lotro.common.*; +import com.gempukku.lotro.logic.GameUtils; + +import java.util.*; + +public class ImportCards { + private SitesBlock _siteBlock; + + //For a deck to be legal in a Pre-shadows format, it must contain one of these sites + private List _fellowshipSiteCheck = new ArrayList(Arrays.asList("Council Courtyard", + "Ford of Bruinen", "Frodo's Bedroom", "Rivendell Terrace", "Rivendell Valley", "Rivendell Waterfall", + "House of Elrond")); + private List _towersSiteCheck = new ArrayList(Arrays.asList("Derndingle", "Eastfold", + "Fangorn Forest", "Plains of Rohan Camp", "Rohirrim Village", "Uruk Camp", "Wold of Rohan")); + private List _kingSiteCheck = new ArrayList(Arrays.asList("King's Tent", "Rohirrim Camp", + "West Road")); + + public List process(String rawDecklist, Iterable items, LotroCardBlueprintLibrary cardLibrary) { + List decklist = getDecklist(rawDecklist); + _siteBlock = null; + + List result = new ArrayList(); + Set pendingSites = new HashSet(); + Map cardBlueprintMap = new HashMap<>(); + for (String line : decklist) { + for (T item : items) { + String blueprintId = item.getBlueprintId(); + if (isPack(blueprintId)) + continue; + try { + cardBlueprintMap.put(blueprintId, cardLibrary.getLotroCardBlueprint(blueprintId)); + int outcome = importCriteria(cardLibrary, cardBlueprintMap, blueprintId, line); + if (outcome == 1) { + result.add(item); + } + if (outcome == 0) + pendingSites.add(item); + } catch (CardNotFoundException e) { + // Ignore the card + } + } + } + + if (_siteBlock == null) + _siteBlock = SitesBlock.SHADOWS; + for (T siteItem : pendingSites) { + String siteId = siteItem.getBlueprintId(); + try { + LotroCardBlueprint siteBlueprint = cardLibrary.getLotroCardBlueprint(siteId); + if (siteBlockFilter(siteBlueprint)) + result.add(siteItem); + } catch (CardNotFoundException e) { + // Ignore the card + } + } + + return result; + } + + private int importCriteria(LotroCardBlueprintLibrary library, Map cardBlueprint, String blueprintId, String title) { + final LotroCardBlueprint blueprint = cardBlueprint.get(blueprintId); + if (exactMatch(blueprint,title)) { + if (setFilter(blueprintId)) { + if (blueprint.getCardType() == CardType.SITE) { + if (_siteBlock == null) + findSiteBlock(blueprint); + //add all sites to the set, as some formats would add duplicates otherwise + return 0; + } + return 1; + } + } + return -1; + } + + private boolean exactMatch(LotroCardBlueprint blueprint, String title) { + if (blueprint == null || !replaceSpecialCharacters(GameUtils.getFullName(blueprint).toLowerCase()).equals(title)) + return false; + return true; + } + + private boolean setFilter(String blueprintId) { + try { + int setNo = Integer.parseInt(blueprintId.split("_")[0]); + if (setNo < 20) + return true; + } catch (Exception e) { + //Not a card + } + return false; + } + + private boolean siteBlockFilter(LotroCardBlueprint blueprint) { + if (blueprint.getSiteBlock() == _siteBlock) + return true; + return false; + } + + private void findSiteBlock(LotroCardBlueprint cardBlueprint) { + String site = cardBlueprint.getTitle(); + if (_fellowshipSiteCheck.contains(site)) + _siteBlock = SitesBlock.FELLOWSHIP; + else if (_towersSiteCheck.contains(site)) + _siteBlock = SitesBlock.TWO_TOWERS; + else if (_kingSiteCheck.contains(site)) + _siteBlock = SitesBlock.KING; + } + + private List getDecklist(String rawDecklist) { + List result = new ArrayList(); + for (String line : rawDecklist.split("~")) { + if (line.length() == 0) + continue; + int quantity = 1; + line = line.toLowerCase(); + String cardLine = ""; + if (Character.isDigit(line.charAt(0))) { + quantity = Character.getNumericValue(line.charAt(0)); + cardLine = line.substring(line.indexOf(" ")); + } + else if (Character.isDigit(line.charAt(line.length()-1))) { + quantity = Character.getNumericValue(line.charAt(line.length()-1)); + cardLine = line.substring(0,line.indexOf(" ",line.length()-3)); + } + else { + quantity = 1; + cardLine = line; + } + for (int i = 0; i < quantity; i++) + result.add(replaceSpecialCharacters(cardLine).trim()); + } + return result; + } + + private String replaceSpecialCharacters(String text) { + return text + .replace('é', 'e') + .replace('ú', 'u') + .replace('ë', 'e') + .replace('û', 'u') + .replace('ó', 'o'); + } + + private static boolean isPack(String blueprintId) { + return !blueprintId.contains("_"); + } +} diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/SortAndFilterCards.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/SortAndFilterCards.java index fa37615f6..ce100c672 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/SortAndFilterCards.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/SortAndFilterCards.java @@ -165,19 +165,19 @@ public class SortAndFilterCards { final LotroCardBlueprint blueprint = cardBlueprint.get(blueprintId); if (blueprint.getCardType() == CardType.SITE) { if (blueprint.getSiteBlock() == SitesBlock.FELLOWSHIP) { - if ("fotr_block,lotr".contains(set)) { + if ("fotr_block".contains(set)) { return true; } return false; } if (blueprint.getSiteBlock() == SitesBlock.TWO_TOWERS) { - if ("towers_standard,ttt_block,lotr".contains(set)) { + if ("towers_standard,ttt_block".contains(set)) { return true; } return false; } if (blueprint.getSiteBlock() == SitesBlock.KING) { - if ("king_block,rotk_sta,movie,lotr".contains(set)) { + if ("king_block,rotk_sta,movie".contains(set)) { return true; } return false; 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 261134158..5394e088c 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/resources/lotrFormats.json +++ b/gemp-lotr/gemp-lotr-server/src/main/resources/lotrFormats.json @@ -293,7 +293,7 @@ "winAtEndOfRegroup":true, "ruleOfFour":false, "winOnControlling5Sites":true, - "hall":true + "hall":false }, { "name":"Hobbit: The Short Rest",