/** * Copyright (c) 2005-2017, KoLmafia development team * http://kolmafia.sourceforge.net/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * [1] Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * [2] Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * [3] Neither the name "KoLmafia" nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.sourceforge.kolmafia.persistence; import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.io.IOException; import java.io.PrintStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.Modifiers; import net.sourceforge.kolmafia.Modifiers.Modifier; import net.sourceforge.kolmafia.Modifiers.ModifierList; import net.sourceforge.kolmafia.ModifierExpression; import net.sourceforge.kolmafia.RequestLogger; import net.sourceforge.kolmafia.RequestThread; import net.sourceforge.kolmafia.SpecialOutfit; import net.sourceforge.kolmafia.StaticEntity; import net.sourceforge.kolmafia.objectpool.IntegerPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.request.ApiRequest; import net.sourceforge.kolmafia.request.DisplayCaseRequest; import net.sourceforge.kolmafia.request.EquipmentRequest; import net.sourceforge.kolmafia.request.FamiliarRequest; import net.sourceforge.kolmafia.request.GenericRequest; import net.sourceforge.kolmafia.request.MonsterManuelRequest; import net.sourceforge.kolmafia.request.ZapRequest; import net.sourceforge.kolmafia.session.DisplayCaseManager; import net.sourceforge.kolmafia.session.EquipmentManager; import net.sourceforge.kolmafia.session.InventoryManager; import net.sourceforge.kolmafia.utilities.ByteBufferUtilities; import net.sourceforge.kolmafia.utilities.FileUtilities; import net.sourceforge.kolmafia.utilities.LogStream; import net.sourceforge.kolmafia.utilities.StringArray; import net.sourceforge.kolmafia.utilities.StringUtilities; import net.sourceforge.kolmafia.utilities.WikiUtilities; import org.json.JSONException; import org.json.JSONObject; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class DebugDatabase { //private static final Pattern WIKI_ITEMID_PATTERN = Pattern.compile( "Item number</a>:</b> (\\d+)<br />" ); //private static final Pattern WIKI_DESCID_PATTERN = Pattern.compile( "<b>Description ID:</b> (\\d+)<br />" ); private static final Pattern WIKI_PLURAL_PATTERN = Pattern.compile( "\\(.*?In-game plural</a>: <i>(.*?)</i>\\)", Pattern.DOTALL ); //private static final Pattern WIKI_AUTOSELL_PATTERN = Pattern.compile( "Selling Price: <b>(\\d+) Meat.</b>" ); /** * Takes an item name and constructs the likely Wiki equivalent of that item name. */ private static final String readWikiData( final String name ) { try { String url = WikiUtilities.getWikiLocation( name, WikiUtilities.ITEM_TYPE ); HttpURLConnection connection = (HttpURLConnection) new URL( null, url ).openConnection(); connection.setRequestProperty( "Connection", "close" ); // no need to keep-alive InputStream istream = connection.getInputStream(); if ( connection.getResponseCode() != 200 ) { return ""; } byte[] bytes = ByteBufferUtilities.read( istream ); String string = StringUtilities.getEncodedString( bytes, "UTF-8" ); return string; } catch ( IOException e ) { return ""; } } private static final String readApiPlural( final int itemId ) { GenericRequest request = new ApiRequest( "item", itemId ); RequestThread.postRequest( request ); String plural = ""; JSONObject json; try { json = new JSONObject( request.responseText ); plural = (String) json.get( "plural" ); } catch ( JSONException ex ) { } return plural; } /** * Utility method which searches for the plural version of the item on the KoL wiki. */ /*public static final void determineWikiData( final String name ) { String wikiData = DebugDatabase.readWikiData( name ); Matcher itemMatcher = DebugDatabase.WIKI_ITEMID_PATTERN.matcher( wikiData ); if ( !itemMatcher.find() ) { RequestLogger.printLine( name + " did not match a valid an item entry." ); return; } Matcher descMatcher = DebugDatabase.WIKI_DESCID_PATTERN.matcher( wikiData ); if ( !descMatcher.find() ) { RequestLogger.printLine( name + " did not match a valid an item entry." ); return; } RequestLogger.printLine( "item: " + name + " (#" + itemMatcher.group( 1 ) + ")" ); RequestLogger.printLine( "desc: " + descMatcher.group( 1 ) ); Matcher pluralMatcher = DebugDatabase.WIKI_PLURAL_PATTERN.matcher( wikiData ); if ( pluralMatcher.find() ) { RequestLogger.printLine( "plural: " + pluralMatcher.group( 1 ) ); } Matcher sellMatcher = DebugDatabase.WIKI_AUTOSELL_PATTERN.matcher( wikiData ); if ( sellMatcher.find() ) { RequestLogger.printLine( "autosell: " + sellMatcher.group( 1 ) ); } }*/ // ********************************************************** // Support for the "checkitems" command, which compares KoLmafia's // internal item data from what can be mined from the item description. private static final String ITEM_HTML = "itemhtml.txt"; private static final String ITEM_DATA = "itemdata.txt"; private static final StringArray rawItems = new StringArray(); private static class ItemMap { private final String tag; private final int type; private final Map<String, String> map; public ItemMap( final String tag, final int type ) { this.tag = tag; this.type = type; this.map = new TreeMap<String, String>( KoLConstants.ignoreCaseComparator ); } public String getTag() { return this.tag; } public int getType() { return this.type; } public Map<String, String> getMap() { return this.map; } public void clear() { this.map.clear(); } public void put( String name, String text ) { this.map.put( name, text ); } @Override public String toString() { return this.tag; } } private static final ItemMap [] ITEM_MAPS = { new ItemMap( "Food", KoLConstants.CONSUME_EAT ), new ItemMap( "Booze", KoLConstants.CONSUME_DRINK ), new ItemMap( "Spleen Toxins", KoLConstants.CONSUME_SPLEEN ), new ItemMap( "Hats", KoLConstants.EQUIP_HAT ), new ItemMap( "Weapons", KoLConstants.EQUIP_WEAPON ), new ItemMap( "Off-hand Items", KoLConstants.EQUIP_OFFHAND ), new ItemMap( "Shirts", KoLConstants.EQUIP_SHIRT ), new ItemMap( "Pants", KoLConstants.EQUIP_PANTS ), new ItemMap( "Accessories", KoLConstants.EQUIP_ACCESSORY ), new ItemMap( "Containers", KoLConstants.EQUIP_CONTAINER ), new ItemMap( "Familiar Items", KoLConstants.EQUIP_FAMILIAR ), new ItemMap( "Everything Else", -1 ), }; private static final ItemMap findItemMap( final int type ) { ItemMap other = null; for ( int i = 0; i < DebugDatabase.ITEM_MAPS.length; ++i ) { ItemMap map = DebugDatabase.ITEM_MAPS[ i ]; int mapType = map.getType(); if ( mapType == type ) { return map; } if ( mapType == -1 ) { other = map; } } return other; } public static final void checkItems( final int itemId ) { RequestLogger.printLine( "Loading previous data..." ); DebugDatabase.loadScrapeData( rawItems, ITEM_HTML ); RequestLogger.printLine( "Checking internal data..." ); PrintStream report = DebugDatabase.openReport( ITEM_DATA ); for ( int i = 0; i < DebugDatabase.ITEM_MAPS.length; ++i ) { ItemMap map = DebugDatabase.ITEM_MAPS[ i ]; map.clear(); } // Check item names, desc ID, consumption type if ( itemId == 0 ) { DebugDatabase.checkItems( report ); } else { DebugDatabase.checkItem( itemId, report ); } // Check level limits, equipment, modifiers DebugDatabase.checkConsumableItems( report ); DebugDatabase.checkEquipment( report ); DebugDatabase.checkItemModifiers( report ); report.close(); } private static final void checkItems( final PrintStream report ) { Set<Integer> keys = ItemDatabase.descriptionIdKeySet(); int lastId = 0; for ( Integer id : keys ) { if ( id < 1 ) { continue; } while ( ++lastId < id ) { report.println( lastId ); } DebugDatabase.checkItem( id, report ); } DebugDatabase.saveScrapeData( keys.iterator(), rawItems, ITEM_HTML ); } private static final void checkItem( final int itemId, final PrintStream report ) { Integer id = IntegerPool.get( itemId ); String name = ItemDatabase.getItemDataName( id ); if ( name == null ) { report.println( itemId ); return; } String rawText = DebugDatabase.rawItemDescriptionText( itemId ); if ( rawText == null ) { report.println( "# *** " + name + " (" + itemId + ") has no description." ); return; } String text = DebugDatabase.itemDescriptionText( rawText ); if ( text == null ) { report.println( "# *** " + name + " (" + itemId + ") has malformed description text." ); DebugDatabase.rawItems.set( itemId, null ); return; } String descriptionName = DebugDatabase.parseName( text ); if ( !name.equals( descriptionName ) ) { report.println( "# *** " + name + " (" + itemId + ") has description of " + descriptionName + "." ); DebugDatabase.rawItems.set( itemId, null ); return; } int type = ItemDatabase.getConsumptionType( itemId ); String descType = DebugDatabase.parseType( text ); int descPrimary = DebugDatabase.typeToPrimary( descType, false ); if ( !typesMatch( type, descPrimary ) ) { String primary = ItemDatabase.typeToPrimaryUsage( type ); report.println( "# *** " + name + " (" + itemId + ") has primary usage of " + primary + " but is described as " + descType + "." ); } int attrs = ItemDatabase.getAttributes( itemId ); int descAttrs = DebugDatabase.typeToSecondary( descType, descPrimary, text, false ); if ( !DebugDatabase.attributesMatch( attrs, descAttrs ) ) { String secondary = ItemDatabase.attrsToSecondaryUsage( attrs ); String descSecondary = ItemDatabase.attrsToSecondaryUsage( descAttrs ); report.println( "# *** " + name + " (" + itemId + ") has secondary usage of " + secondary + " but is described as " + descSecondary + "." ); } int price = ItemDatabase.getPriceById( itemId ); int descPrice = DebugDatabase.parsePrice( text ); if ( price != descPrice && ( price >= 0 || descPrice != 0 ) ) { report.println( "# *** " + name + " (" + itemId + ") has price of " + price + " but should be " + descPrice + "." ); } String access = ItemDatabase.getAccessById( id ); String descAccess = DebugDatabase.parseAccess( text ); if ( !access.equals( descAccess ) ) { report.println( "# *** " + name + " (" + itemId + ") has access of " + access + " but should be " + descAccess + "." ); } String image = ItemDatabase.getImage( id ); String descImage = DebugDatabase.parseImage( rawText ); if ( !image.equals( descImage ) ) { report.println( "# *** " + name + " (" + itemId + ") has image of " + image + " but should be " + descImage + "." ); } ItemMap map = DebugDatabase.findItemMap( type ); map.put( name, text ); String descId = ItemDatabase.getDescriptionId( id ); // Intentionally get a null if there is not an explicit plural in the database String plural = ItemDatabase.getPluralById( id ); // In fact, if the plural is simply the name + "s", suppress it. if ( plural != null && plural.equals( name + "s" ) ) { plural = null; } report.println( ItemDatabase.itemString( itemId, name, descId, image, type, attrs, access, price, plural ) ); } private static final GenericRequest DESC_ITEM_REQUEST = new GenericRequest( "desc_item.php" ); public static final String itemDescriptionText( final int itemId, boolean forceReload ) { return DebugDatabase.itemDescriptionText( DebugDatabase.rawItemDescriptionText( ItemDatabase.getDescriptionId( itemId ), forceReload ) ); } public static final String rawItemDescriptionText( final int itemId ) { return DebugDatabase.rawItemDescriptionText( ItemDatabase.getDescriptionId( itemId ), false ); } public static final String rawItemDescriptionText( final String descId, boolean forceReload ) { if ( descId == null ) { return ""; } int itemId = ItemDatabase.getItemIdFromDescription( descId ); String previous = null; if ( itemId != -1 ) { previous = DebugDatabase.rawItems.get( itemId ); } if ( !forceReload && previous != null && !previous.equals( "" ) ) { return previous; } DebugDatabase.DESC_ITEM_REQUEST.clearDataFields(); DebugDatabase.DESC_ITEM_REQUEST.addFormField( "whichitem", descId ); RequestThread.postRequest( DebugDatabase.DESC_ITEM_REQUEST ); if ( itemId == -1 ) { itemId = DebugDatabase.parseItemId( DebugDatabase.DESC_ITEM_REQUEST.responseText ); } DebugDatabase.rawItems.set( itemId, DebugDatabase.DESC_ITEM_REQUEST.responseText ); return DebugDatabase.DESC_ITEM_REQUEST.responseText; } private static final Pattern ITEM_DATA_PATTERN = Pattern.compile( "<div id=\"description\"[^>]*>(.*?)<script", Pattern.DOTALL ); public static final String itemDescriptionText( final String rawText ) { if ( rawText == null ) { return null; } Matcher matcher = DebugDatabase.ITEM_DATA_PATTERN.matcher( rawText ); return matcher.find() ? matcher.group( 1 ) : null; } // <!-- itemid: 806 --> private static final Pattern ITEMID_PATTERN = Pattern.compile( "<!-- itemid: ([\\d]*) -->" ); public static final int parseItemId( final String text ) { Matcher matcher = DebugDatabase.ITEMID_PATTERN.matcher( text ); if ( !matcher.find() ) { return 0; } return StringUtilities.parseInt( matcher.group( 1 ) ); } private static final Pattern NAME_PATTERN = Pattern.compile( "<b>(.*?)</b>" ); public static final String parseName( final String text ) { Matcher matcher = DebugDatabase.NAME_PATTERN.matcher( text ); if ( !matcher.find() ) { return ""; } // One item is known to have an extra internal space return StringUtilities.globalStringReplace( matcher.group( 1 ), " ", " " ).trim(); } private static final Pattern PRICE_PATTERN = Pattern.compile( "Selling Price: <b>(\\d+) Meat.</b>" ); public static final int parsePrice( final String text ) { Matcher matcher = DebugDatabase.PRICE_PATTERN.matcher( text ); if ( !matcher.find() ) { return 0; } return StringUtilities.parseInt( matcher.group( 1 ) ); } private static final StringBuilder appendAccessTypes( StringBuilder accessTypes, String accessType ) { if ( accessTypes.length() > 0 ) { return accessTypes.append( "," ).append( accessType ); } return accessTypes.append( accessType ); } public static final String parseAccess( final String text ) { StringBuilder accessTypes = new StringBuilder(); if ( text.contains( "Quest Item" ) || text.contains( "This item will disappear at the end of the day." ) ) { accessTypes = appendAccessTypes( accessTypes, ItemDatabase.QUEST_FLAG ); } // Quest items cannot be gifted or traded else if ( text.contains( "Gift Item" ) && !text.contains( "gift package" ) ) { accessTypes = appendAccessTypes( accessTypes, ItemDatabase.GIFT_FLAG ); } // Gift items cannot be (normally) traded else if ( !text.contains( "Cannot be traded" ) ) { accessTypes = appendAccessTypes( accessTypes, ItemDatabase.TRADE_FLAG ); } //We shouldn't just check for "discarded", in case "discarded" appears somewhere else in the description. if ( !text.contains( "Cannot be discarded" ) && !text.contains( "Cannot be traded or discarded" ) ) { accessTypes = appendAccessTypes( accessTypes, ItemDatabase.DISCARD_FLAG ); } return accessTypes.toString(); } private static final Pattern TYPE_PATTERN = Pattern.compile( "Type: <b>(.*?)</b>" ); public static final String parseType( final String text ) { Matcher matcher = DebugDatabase.TYPE_PATTERN.matcher( text ); String type = matcher.find() ? matcher.group( 1 ) : ""; return type.equals( "back item" ) ? "container" : type; } public static final int typeToPrimary( final String type, final boolean multi ) { // Type: <b>food <font color=#999999>(crappy)</font></b> // Type: <b>food (decent)</b> // Type: <b>booze <font color=green>(good)</font></b> // Type: <b>food <font color=blue>(awesome)</font></b> // Type: <b>food <font color=blueviolet>(EPIC)</font></b> if ( type.equals( "" ) || type.equals( "crafting item" ) ) { return KoLConstants.NO_CONSUME; } if ( type.startsWith( "food" ) || type.startsWith( "beverage" ) ) { return KoLConstants.CONSUME_EAT; } if ( type.startsWith( "booze" ) ) { return KoLConstants.CONSUME_DRINK; } if ( type.startsWith( "spleen item" ) ) { return KoLConstants.CONSUME_SPLEEN; } if ( type.contains( "self or others" ) ) { // Curse items are special return KoLConstants.NO_CONSUME; } if ( type.startsWith( "usable" ) || type.contains( " usable" ) || type.equals( "gift package" ) || type.equals( "potion" ) ) { // Although most potions end up being multi-usable, KoL // almost always forgets to add that flag when the item // is first introduced. // // We'll assume they are single-usable unless we are // explicitly told otherwise in a "rel" string return multi ? KoLConstants.CONSUME_MULTIPLE : KoLConstants.CONSUME_USE; } if ( type.equals( "familiar" ) ) { return KoLConstants.GROW_FAMILIAR; } if ( type.equals( "familiar equipment" ) ) { return KoLConstants.EQUIP_FAMILIAR; } if ( type.startsWith( "accessory" ) ) { return KoLConstants.EQUIP_ACCESSORY; } if ( type.startsWith( "container" ) ) { return KoLConstants.EQUIP_CONTAINER; } if ( type.startsWith( "hat" ) ) { return KoLConstants.EQUIP_HAT; } if ( type.startsWith( "shirt" ) ) { return KoLConstants.EQUIP_SHIRT; } if ( type.startsWith( "pants" ) ) { return KoLConstants.EQUIP_PANTS; } if ( type.contains( "weapon" ) ) { return KoLConstants.EQUIP_WEAPON; } if ( type.startsWith( "off-hand item" ) ) { return KoLConstants.EQUIP_OFFHAND; } return KoLConstants.NO_CONSUME; } public static final int typeToSecondary( final String type, final int primary, final String text, final boolean multi ) { int attributes = 0; boolean usable = type.startsWith( "usable" ) || type.contains( " usable" ) || type.contains( "spleen" ) || type.contains( "potion" ) || type.equals( "gift package" ); if ( type.contains( "combat" ) && type.contains( "reusable" ) ) { attributes |= ItemDatabase.ATTR_COMBAT_REUSABLE; } else if ( type.contains( "combat" ) ) { attributes |= ItemDatabase.ATTR_COMBAT; } else if ( type.contains( "reusable" ) ) { attributes |= ItemDatabase.ATTR_REUSABLE; } if ( multi && primary != KoLConstants.CONSUME_MULTIPLE && usable ) { attributes |= ItemDatabase.ATTR_MULTIPLE; } if ( !multi && primary != KoLConstants.CONSUME_USE && usable ) { attributes |= ItemDatabase.ATTR_USABLE; } if ( type.contains( "self or others" ) ) { attributes |= ItemDatabase.ATTR_CURSE; } if ( text.contains( "(Fancy" ) ) { attributes |= ItemDatabase.ATTR_FANCY; } return attributes; } private static final boolean typesMatch( final int type, final int descType ) { switch ( type ) { case KoLConstants.NO_CONSUME: // We intentionally disallow certain items from being // "used" through the GUI. return descType == KoLConstants.NO_CONSUME || descType == KoLConstants.CONSUME_USE; case KoLConstants.CONSUME_EAT: case KoLConstants.CONSUME_DRINK: case KoLConstants.CONSUME_SPLEEN: case KoLConstants.GROW_FAMILIAR: case KoLConstants.EQUIP_FAMILIAR: case KoLConstants.EQUIP_ACCESSORY: case KoLConstants.EQUIP_CONTAINER: case KoLConstants.EQUIP_HAT: case KoLConstants.EQUIP_PANTS: case KoLConstants.EQUIP_SHIRT: case KoLConstants.EQUIP_WEAPON: case KoLConstants.EQUIP_OFFHAND: return descType == type; case KoLConstants.MESSAGE_DISPLAY: case KoLConstants.CONSUME_USE: case KoLConstants.CONSUME_MULTIPLE: case KoLConstants.CONSUME_AVATAR: case KoLConstants.INFINITE_USES: return descType == KoLConstants.CONSUME_USE || descType == KoLConstants.CONSUME_MULTIPLE || descType == KoLConstants.CONSUME_EAT || descType == KoLConstants.CONSUME_DRINK || descType == KoLConstants.CONSUME_AVATAR || descType == KoLConstants.NO_CONSUME; case KoLConstants.CONSUME_FOOD_HELPER: case KoLConstants.CONSUME_DRINK_HELPER: case KoLConstants.CONSUME_STICKER: case KoLConstants.CONSUME_FOLDER: return descType == KoLConstants.NO_CONSUME || descType == KoLConstants.CONSUME_USE; case KoLConstants.CONSUME_CARD: case KoLConstants.CONSUME_SPHERE: case KoLConstants.CONSUME_ZAP: return descType == KoLConstants.NO_CONSUME; } return true; } private static final boolean attributesMatch( final int attrs, final int descAttrs ) { // If the description says an item is "combat", "(reusable)" or "(on self or others)", // our database must mark the item as ATTR_COMBAT, ATTR_COMBAT_REUSABLE, ATTR_CURSE, // // However, there are quite a few items that we mark with those secondary attributes that are // not tagged that way by KoL itself. Assume those are correct. if ( ( descAttrs & ItemDatabase.ATTR_COMBAT ) != 0 && ( attrs & ItemDatabase.ATTR_COMBAT ) == 0 ) { return false; } if ( ( descAttrs & ItemDatabase.ATTR_COMBAT_REUSABLE ) != 0 && ( attrs & ItemDatabase.ATTR_COMBAT_REUSABLE ) == 0 ) { return false; } if ( ( descAttrs & ItemDatabase.ATTR_CURSE ) != 0 && ( attrs & ItemDatabase.ATTR_CURSE ) == 0 ) { return false; } // If the item is a (Fancy Cooking ingredient) or (Fancy Cocktailcrafting ingredient) // we must mark the item with ATTR_FANCY if ( ( descAttrs & ItemDatabase.ATTR_FANCY ) != ( attrs & ItemDatabase.ATTR_FANCY ) ) { return false; } return true; } private static final void checkConsumableItems( final PrintStream report ) { RequestLogger.printLine( "Checking level requirements..." ); DebugDatabase.checkConsumableMap( report, DebugDatabase.findItemMap( KoLConstants.CONSUME_EAT ) ); DebugDatabase.checkConsumableMap( report, DebugDatabase.findItemMap( KoLConstants.CONSUME_DRINK ) ); DebugDatabase.checkConsumableMap( report, DebugDatabase.findItemMap( KoLConstants.CONSUME_SPLEEN ) ); } private static final void checkConsumableMap( final PrintStream report, final ItemMap imap ) { Map<String, String> map = imap.getMap(); if ( map.size() == 0 ) { return; } String tag = imap.getTag(); int type = imap.getType(); String file = type == KoLConstants.CONSUME_EAT ? "fullness" : type == KoLConstants.CONSUME_DRINK ? "inebriety" : "spleenhit"; RequestLogger.printLine( "Checking " + tag + "..." ); report.println( "" ); report.println( "# Level requirements in " + file + ".txt" ); Object[] keys = map.keySet().toArray(); for ( int i = 0; i < keys.length; ++i ) { String name = (String) keys[ i ]; String text = map.get( name ); DebugDatabase.checkConsumableDatum( name, type, text, report ); } } private static final void checkConsumableDatum( final String name, final int type, final String text, final PrintStream report ) { Integer requirement = ConsumablesDatabase.getLevelReqByName( name ); int level = requirement == null ? 0 : requirement.intValue(); int descLevel = DebugDatabase.parseLevel( text ); if ( level != descLevel ) { report.println( "# *** " + name + " requires level " + level + " but should be " + descLevel + "." ); } int size = ( type == KoLConstants.CONSUME_EAT ) ? ConsumablesDatabase.getFullness( name ) : ( type == KoLConstants.CONSUME_DRINK ) ? ConsumablesDatabase.getInebriety( name ) : ( type == KoLConstants.CONSUME_SPLEEN ) ? ConsumablesDatabase.getSpleenHit( name ) : 1; int descSize = DebugDatabase.parseSize( text ); if ( size != descSize ) { report.println( "# *** " + name + " is size " + size + " but should be " + descSize + "." ); } String quality = ConsumablesDatabase.getQuality( name ); String descQuality = DebugDatabase.parseQuality( text ); if ( !quality.equals( descQuality ) ) { report.println( "# *** " + name + " is quality " + quality + " but should be " + descQuality + "." ); } } private static final Pattern LEVEL_PATTERN = Pattern.compile( "Level required: <b>(.*?)</b>" ); public static final int parseLevel( final String text ) { Matcher matcher = DebugDatabase.LEVEL_PATTERN.matcher( text ); if ( !matcher.find() ) { return 1; } return StringUtilities.parseInt( matcher.group( 1 ) ); } private static final Pattern SIZE_PATTERN = Pattern.compile( "(?:Size|Potency|Toxicity): <b>(.*?)</b>" ); public static final int parseSize( final String text ) { Matcher matcher = DebugDatabase.SIZE_PATTERN.matcher( text ); if ( !matcher.find() ) { return 1; } return StringUtilities.parseInt( matcher.group( 1 ) ); } private static final void checkEquipment( final PrintStream report ) { RequestLogger.printLine( "Checking equipment..." ); DebugDatabase.checkEquipmentMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_HAT ) ); DebugDatabase.checkEquipmentMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_PANTS ) ); DebugDatabase.checkEquipmentMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_SHIRT ) ); DebugDatabase.checkEquipmentMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_WEAPON ) ); DebugDatabase.checkEquipmentMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_OFFHAND ) ); DebugDatabase.checkEquipmentMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_ACCESSORY ) ); DebugDatabase.checkEquipmentMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_CONTAINER ) ); } private static final void checkEquipmentMap( final PrintStream report, ItemMap imap ) { Map<String, String> map = imap.getMap(); if ( map.size() == 0 ) { return; } String tag = imap.getTag(); RequestLogger.printLine( "Checking " + tag + "..." ); report.println( "" ); report.println( "# " + tag + " section of equipment.txt" ); report.println(); Object[] keys = map.keySet().toArray(); for ( int i = 0; i < keys.length; ++i ) { String name = (String) keys[ i ]; String text = map.get( name ); DebugDatabase.checkEquipmentDatum( name, text, report ); } } private static final void checkEquipmentDatum( final String name, final String text, final PrintStream report ) { String type = DebugDatabase.parseType( text ); boolean isWeapon = false, isShield = false, hasPower = false; if ( type.contains( "weapon" ) ) { isWeapon = true; } else if ( type.contains( "shield" ) ) { isShield = true; } else if ( type.contains( "hat" ) || type.contains( "pants" ) || type.contains( "shirt" ) ) { hasPower = true; } int itemId = ItemDatabase.getItemId( name ); int power = 0; if ( isWeapon || hasPower ) { power = DebugDatabase.parsePower( text ); } else { // Until KoL puts off-hand and accessory power into the // description, use hand-entered "secret" value. power = EquipmentDatabase.getPower( itemId ); } // Now check against what we already have int oldPower = EquipmentDatabase.getPower( itemId ); if ( power != oldPower ) { report.println( "# *** " + name + " has power " + oldPower + " but should be " + power + "." ); } String weaponType = isWeapon ? DebugDatabase.parseWeaponType( type ) : ""; String req = DebugDatabase.parseReq( text, type ); String oldReq = EquipmentDatabase.getEquipRequirement( itemId ); if ( !req.equals( oldReq ) ) { report.println( "# *** " + name + " has requirement " + oldReq + " but should be " + req + "." ); } if ( isWeapon ) { int spaceIndex = weaponType.indexOf( " " ); String oldHanded = EquipmentDatabase.getHands( itemId ) + "-handed"; if ( spaceIndex != -1 && !weaponType.startsWith( oldHanded ) ) { String handed = weaponType.substring( 0, spaceIndex ); report.println( "# *** " + name + " is marked as " + oldHanded + " but should be " + handed + "." ); } } EquipmentDatabase.writeEquipmentItem( report, name, power, req, weaponType, isWeapon, isShield ); } private static final Pattern POWER_PATTERN = Pattern.compile( "Power: <b>(\\d+)</b>" ); private static final Pattern DAMAGE_PATTERN_WEAPON = Pattern.compile( "Damage: <b>[\\d]+ - (\\d+)</b>" ); public static final int parsePower( final String text ) { Matcher matcher = DebugDatabase.POWER_PATTERN.matcher( text ); // This should match non-weapon power if ( matcher.find() ) { return StringUtilities.parseInt( matcher.group( 1 ) ); } // This will match weapon damage and use it to calculate power matcher = DebugDatabase.DAMAGE_PATTERN_WEAPON.matcher( text ); return matcher.find() ? ( StringUtilities.parseInt( matcher.group( 1 ) ) * 5 ) : 0; } private static final Pattern WEAPON_PATTERN = Pattern.compile( "weapon [(](.*?)[)]" ); public static final String parseWeaponType( final String text ) { Matcher matcher = DebugDatabase.WEAPON_PATTERN.matcher( text ); return matcher.find() ? matcher.group( 1 ) : ""; } private static final Pattern REQ_PATTERN = Pattern.compile( "(\\w+) Required: <b>(\\d+)</b>" ); public static final String parseReq( final String text, final String type ) { Matcher matcher = DebugDatabase.REQ_PATTERN.matcher( text ); if ( matcher.find() ) { String stat = matcher.group( 1 ); if ( stat.equals( "Muscle" ) ) { return "Mus: " + matcher.group( 2 ); } if ( stat.equals( "Mysticality" ) ) { return "Mys: " + matcher.group( 2 ); } if ( stat.equals( "Moxie" ) ) { return "Mox: " + matcher.group( 2 ); } } if ( type.contains( "weapon" ) ) { if ( type.contains( "ranged" ) ) { return "Mox: 0"; } else if ( type.contains( "utensil" ) || type.contains( "saucepan" ) || type.contains( "chefstaff" ) ) { return "Mys: 0"; } else { return "Mus: 0"; } } return "none"; } private static final Pattern FULLNESS_PATTERN = Pattern.compile( "Size: <b>(\\d+)</b>" ); public static final int parseFullness( final String text ) { Matcher matcher = DebugDatabase.FULLNESS_PATTERN.matcher( text ); return matcher.find() ? ( StringUtilities.parseInt( matcher.group( 1 ) ) ) : 0; } private static final Pattern INEBRIETY_PATTERN = Pattern.compile( "Potency: <b>(\\d+)</b>" ); public static final int parseInebriety( final String text ) { Matcher matcher = DebugDatabase.INEBRIETY_PATTERN.matcher( text ); return matcher.find() ? ( StringUtilities.parseInt( matcher.group( 1 ) ) ) : 0; } private static final Pattern TOXICITY_PATTERN = Pattern.compile( "Toxicity: <b>(\\d+)</b>" ); public static final int parseToxicity( final String text ) { Matcher matcher = DebugDatabase.TOXICITY_PATTERN.matcher( text ); return matcher.find() ? ( StringUtilities.parseInt( matcher.group( 1 ) ) ) : 0; } private static final Pattern FAMILIAR_PATTERN = Pattern.compile( "Familiar: <b>(.*?)</b>" ); public static final String parseFamiliar( final String text ) { Matcher matcher = DebugDatabase.FAMILIAR_PATTERN.matcher( text ); return matcher.find() ? ( matcher.group( 1 ) ) : "any"; } private static final void checkItemModifiers( final PrintStream report ) { RequestLogger.printLine( "Checking modifiers..." ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_HAT ) ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_PANTS ) ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_SHIRT ) ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_WEAPON ) ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_OFFHAND ) ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_ACCESSORY ) ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_CONTAINER ) ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.EQUIP_FAMILIAR ) ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.CONSUME_EAT ), false ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.CONSUME_DRINK ), false ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( KoLConstants.CONSUME_SPLEEN ), false ); DebugDatabase.checkItemModifierMap( report, DebugDatabase.findItemMap( -1 ), false ); } private static final void checkItemModifierMap( final PrintStream report, final ItemMap imap ) { DebugDatabase.checkItemModifierMap( report, imap, true ); } private static final void checkItemModifierMap( final PrintStream report, final ItemMap imap, final boolean showAll ) { Map<String, String> map = imap.getMap(); if ( map.size() == 0 ) { return; } String tag = imap.getTag(); RequestLogger.printLine( "Checking " + tag + "..." ); report.println(); report.println( "# " + tag + " section of modifiers.txt" ); report.println(); Object[] keys = map.keySet().toArray(); int type = imap.getType(); for ( int i = 0; i < keys.length; ++i ) { String name = (String) keys[ i ]; String text = map.get( name ); DebugDatabase.checkItemModifierDatum( name, text, type, report, showAll ); } } private static final void checkItemModifierDatum( final String name, final String text, final int type, final PrintStream report, final boolean showAll ) { ModifierList known = new ModifierList(); ArrayList<String> unknown = new ArrayList<String>(); // Get the known and unknown modifiers from the item description DebugDatabase.parseItemEnchantments( text, known, unknown, type ); // Compare to what is already registered, logging differences // and substituting expressions, as appropriate. DebugDatabase.checkModifiers( "Item", name, known, true, report ); // Print the modifiers in the format modifiers.txt expects. if ( showAll || known.size() > 0 || unknown.size() > 0 ) { DebugDatabase.logModifierDatum( "Item", name, known, unknown, report ); } } private static final void checkModifiers( final String type, final String name, final ModifierList known, final boolean appendCurrent, final PrintStream report ) { // - Keep modifiers in the same order they are listed in the item description // - If a modifier is variable (has an expression), evaluate // the expression and compare to the number in the description // - List extra modifiers (Familiar Effect, for example) at end // of parsed modifiers in the order they appear in modifiers.txt // Get the existing modifiers for the name ModifierList existing = Modifiers.getModifierList( type, name ); // Look at each modifier in known for ( Modifier modifier : known ) { String key = modifier.getName(); String value = modifier.getValue(); Modifier current = existing.removeModifier( key ); if ( current != null ) { String currentValue = current.getValue(); if ( currentValue == null ) { continue; // No value } if ( currentValue.contains( "[" ) ) { int lbracket = currentValue.indexOf( "[" ); int rbracket = currentValue.indexOf( "]" ); if ( Modifiers.isNumericModifier( key ) ) { // Evaluate the expression String expression = currentValue.substring( lbracket + 1, rbracket ); // Kludge: KoL no longer takes Reagent Potion duration // into account in item descriptions. if ( key.equals( "Effect Duration" ) && expression.contains( "R" ) ) { expression = StringUtilities.singleStringReplace( expression, "R", "5" ); } ModifierExpression expr = new ModifierExpression( expression, Modifiers.getLookupName( type, name ) ); if ( expr.hasErrors() ) { report.println( expr.getExpressionErrors() ); } else { int descValue = StringUtilities.parseInt( value ); int modValue = (int)expr.eval(); if ( descValue != modValue ) { report.println( "# *** modifier " + key + ": " + currentValue + " evaluates to " + modValue + " but description says " + descValue ); } } // Keep the expression, regardless modifier.setValue( currentValue ); continue; } if ( key.equals( "Effect" ) || key.equals( "Rollover Effect" ) ) { // Remove initial effect ID String effect = currentValue.substring( 0, lbracket ) + currentValue.substring( rbracket + 1 ); if ( !value.equals( effect ) ) { // Effect does not match report.println( "# *** modifier " + key + ": " + currentValue + " should be " + key + ": " + value ); } else { modifier.setValue( currentValue ); } continue; } } // If the value is not an expression, it must match exactly if ( !value.equals( currentValue ) ) { report.println( "# *** modifier " + key + ": " + currentValue + " should be " + key + ": " + value ); } } else if ( value == null ) { report.println( "# *** new enchantment: " + key + " seen" ); } else { report.println( "# *** new enchantment: " + key + ": " + value + " seen" ); } } if ( appendCurrent ) { // Add all modifiers in existing list that were not seen in description to "known" known.addAll( existing ); } else { for ( Modifier modifier : existing ) { String key = modifier.getName(); String value = modifier.getValue(); if ( value == null ) { report.println( "# *** bogus enchantment: " + key ); } else { report.println( "# *** bogus enchantment: " + key + ": " + value ); } } } } private static final void logModifierDatum( final String type, final String name, final ModifierList known, final ArrayList<String> unknown, final PrintStream report ) { for ( int i = 0; i < unknown.size(); ++i ) { Modifiers.writeModifierComment( report, null, name, unknown.get( i ) ); } if ( known.size() == 0 ) { if ( unknown.size() == 0 ) { Modifiers.writeModifierComment( report, null, name ); } } else { Modifiers.writeModifierString( report, type, name, DebugDatabase.createModifierString( known ) ); } } private static final String createModifierString( final ModifierList modifiers ) { StringBuilder buffer = new StringBuilder(); for ( Modifier modifier : modifiers ) { if ( buffer.length() > 0 ) { buffer.append( ", " ); } buffer.append( modifier.toString() ); } return buffer.toString(); } private static final Pattern ITEM_ENCHANTMENT_PATTERN = Pattern.compile( "<font color=blue>(?!\\(awesome\\))(.*)(?:<br>)?</font>", Pattern.DOTALL ); public static final void parseItemEnchantments( String text, final ModifierList known, final ArrayList<String> unknown, final int type ) { // KoL now includes the enchantments of the effect in the item // descriptions. Strip them out. int eindex = text.indexOf( "Effect:" ); if ( eindex != -1 ) { int spanstart = text.indexOf( "<span", eindex ); int spanend = text.indexOf( "</span>", eindex ); if ( spanstart != -1 && spanend != -1 ) { String span = text.substring( spanstart, spanend + 7 ); text = StringUtilities.singleStringDelete( text, span ); } } DebugDatabase.parseStandardEnchantments( text, known, unknown, DebugDatabase.ITEM_ENCHANTMENT_PATTERN ); // Several modifiers can appear outside the "Enchantments" // section of the item description. // If we extracted Damage Reduction from the enchantments, we // included shield DR as well, but for shields that have no // enchantments, get DR here. if ( !known.containsModifier( "Damage Reduction" ) ) { DebugDatabase.appendModifier( known, Modifiers.parseDamageReduction( text ) ); } DebugDatabase.appendModifier( known, Modifiers.parseSkill( text ) ); DebugDatabase.appendModifier( known, Modifiers.parseSingleEquip( text ) ); DebugDatabase.appendModifier( known, Modifiers.parseSoftcoreOnly( text ) ); DebugDatabase.appendModifier( known, Modifiers.parseLastsOneDay( text ) ); DebugDatabase.appendModifier( known, Modifiers.parseFreePull( text ) ); DebugDatabase.appendModifier( known, Modifiers.parseEffect( text ) ); DebugDatabase.appendModifier( known, Modifiers.parseEffectDuration( text ) ); DebugDatabase.appendModifier( known, Modifiers.parseSongDuration( text ) ); if ( type == KoLConstants.EQUIP_FAMILIAR ) { String familiar = DebugDatabase.parseFamiliar( text ); if ( familiar.equals( "any" ) ) { DebugDatabase.appendModifier( known, "Generic" ); } } } private static final Pattern RESTORE_RANGE_PATTERN = Pattern.compile( "(\\d+)-(\\d+) (?:HP|MP|Hit Points)" ); private static final Pattern RESTORE_RANGE2_PATTERN = Pattern.compile( "(\\d+)-(\\d+) HP and (\\d+)-(\\d+) MP" ); private static final Pattern RESTORE_UPTO_PATTERN = Pattern.compile( "up to (.*?) (?:HP|MP)" ); public static final void parseRestores( final String name, final String text ) { Matcher enchantMatcher = DebugDatabase.ITEM_ENCHANTMENT_PATTERN.matcher( text ); if ( !enchantMatcher.find() ) { return; } String enchant = enchantMatcher.group( 1 ); if ( !enchant.contains( "Restores" ) && !enchant.contains( "Heals" ) ) { return; } String hpmin = "0"; String hpmax = "0"; String mpmin = "0"; String mpmax = "0"; Matcher matcher = DebugDatabase.RESTORE_RANGE_PATTERN.matcher( enchant ); if ( matcher.find() ) { if ( enchant.contains( "HP" ) || enchant.contains( "Hit Points" ) ) { hpmin = matcher.group( 1 ); hpmax = matcher.group( 2 ); } if ( enchant.contains( "MP" ) ) { mpmin = matcher.group( 1 ); mpmax = matcher.group( 2 ); } } matcher = DebugDatabase.RESTORE_RANGE2_PATTERN.matcher( enchant ); if ( matcher.find() ) { hpmin = matcher.group( 1 ); hpmax = matcher.group( 2 ); mpmin = matcher.group( 3 ); mpmax = matcher.group( 4 ); } matcher = DebugDatabase.RESTORE_UPTO_PATTERN.matcher( enchant ); if ( matcher.find() ) { if ( enchant.contains( "HP" ) ) { hpmin = hpmax = matcher.group( 1 ); } if ( enchant.contains( "MP" ) ) { mpmin = mpmax = matcher.group( 1 ); } } if ( enchant.contains( "all" ) ) { if ( enchant.contains( "HP" ) ) { hpmin = hpmax = "[HP]"; } if ( enchant.contains( "MP" ) ) { mpmin = mpmax = "[MP]"; } } RestoresDatabase.setValue( name, "item", hpmin, hpmax, mpmin, mpmax, 0, -1, null ); String printMe = name + "\titem\t" + hpmin + "\t" + hpmax + "\t" + mpmin + "\t" + mpmax + "\t0"; RequestLogger.printLine( printMe ); RequestLogger.updateSessionLog( printMe ); } public static final String parseItemEnchantments( final String text, final ArrayList<String> unknown, final int type ) { ModifierList known = new ModifierList(); DebugDatabase.parseItemEnchantments( text, known, unknown, type ); return DebugDatabase.createModifierString( known ); } private static final void parseStandardEnchantments( final String text, final ModifierList known, final ArrayList<String> unknown, final Pattern pattern ) { Matcher matcher = pattern.matcher( text ); if ( !matcher.find() ) { return; } StringBuffer enchantments = new StringBuffer( matcher.group(1) ); StringUtilities.globalStringDelete( enchantments, "<b>NOTE:</b> Items that reduce the MP cost of skills will not do so by more than 3 points, in total." ); StringUtilities.globalStringReplace( enchantments, "<br>", "\n" ); StringUtilities.globalStringReplace( enchantments, "<Br>", "\n" ); String[] mods = enchantments.toString().split( "\n+" ); for ( int i = 0; i < mods.length; ++i ) { String enchantment = mods[i].trim(); if ( enchantment.equals( "" ) ) { continue; } // Unfortunately, since KoL has removed any indication // other than blue font to indicate what is an // enchantment, "awesome" as a food quality matches. if ( enchantment.equals( "(awesome)" ) ) { continue; } String mod = Modifiers.parseModifier( enchantment ); if ( mod != null ) { // Rollover Effect and Rollover Effect Duration come together // Modifiers parses the numeric modifier first if ( mod.startsWith( "Rollover Effect Duration" ) ) { String effect = Modifiers.parseStringModifier( enchantment ); if ( effect != null ) { DebugDatabase.appendModifier( known, effect ); } } // Damage Reduction can appear in several // places. Combine them all. else if ( mod.startsWith( "Damage Reduction" ) ) { mod = Modifiers.parseDamageReduction( text ); } DebugDatabase.appendModifier( known, mod ); continue; } if ( !unknown.contains( enchantment ) ) { unknown.add( enchantment ); } } } private static final void appendModifier( final ModifierList known, final String mod ) { if ( mod != null ) { // If the value contains a quoted string, it can contain commas if ( mod.contains( "\"" ) || !mod.contains( "," ) ) { known.addModifier( DebugDatabase.makeModifier( mod ) ); return; } // Otherwise, certain modifiers - "All Attributes: +5" - turn into multiple modifiers String[] mods = mod.split( "," ); for ( int i = 0; i < mods.length; ++i ) { known.addModifier( DebugDatabase.makeModifier( mods[ i ] ) ); } } } private static final Modifier makeModifier( final String mod ) { int colon = mod.indexOf( ":" ); String key = colon == -1 ? mod.trim() : mod.substring( 0, colon ).trim(); String value = colon == -1 ? null : mod.substring( colon + 1 ).trim(); return new Modifier( key, value ); } // ********************************************************** // Support for the "checkoutfits" command, which compares KoLmafia's // internal outfit data from what can be mined from the outfit // description. private static final String OUTFIT_HTML = "outfithtml.txt"; private static final String OUTFIT_DATA = "outfitdata.txt"; private static final StringArray rawOutfits = new StringArray(); private static final ItemMap outfits = new ItemMap( "Outfits", 0 ); public static final void checkOutfits() { RequestLogger.printLine( "Loading previous data..." ); DebugDatabase.loadScrapeData( rawOutfits, OUTFIT_HTML ); RequestLogger.printLine( "Checking internal data..." ); PrintStream report = DebugDatabase.openReport( OUTFIT_DATA ); DebugDatabase.outfits.clear(); DebugDatabase.checkOutfits( report ); DebugDatabase.checkOutfitModifierMap( report, DebugDatabase.outfits ); report.close(); } private static final void checkOutfits(final PrintStream report ) { Set<Integer> keys = EquipmentDatabase.normalOutfits.keySet(); int lastId = 0; for ( Integer id : keys ) { if ( id < 1 ) { continue; } while ( ++lastId < id ) { report.println( lastId ); } DebugDatabase.checkOutfit( id, report ); } DebugDatabase.saveScrapeData( keys.iterator(), rawOutfits, OUTFIT_HTML ); } private static final void checkOutfit( final int outfitId, final PrintStream report ) { SpecialOutfit outfit = EquipmentDatabase.normalOutfits.get( outfitId ); String name = outfit.getName(); if ( name == null ) { report.println( outfitId ); return; } String rawText = DebugDatabase.rawOutfitDescriptionText( outfitId ); if ( rawText == null ) { report.println( "# *** " + name + " (" + outfitId + ") has no description." ); return; } String text = DebugDatabase.outfitDescriptionText( rawText ); if ( text == null ) { report.println( "# *** " + name + " (" + outfitId + ") has malformed description text." ); return; } String image = outfit.getImage(); String descImage = DebugDatabase.parseImage( rawText ); if ( image != null && !image.equals( descImage ) ) { report.println( "# *** " + name + " (" + outfitId + ") has image of " + image + " but should be " + descImage + "." ); } report.println( EquipmentDatabase.outfitString( outfitId, name, descImage ) ); DebugDatabase.outfits.put( name, text ); } private static final GenericRequest DESC_OUTFIT_REQUEST = new GenericRequest( "desc_outfit.php" ); public static final String outfitDescriptionText( final int outfitId ) { return DebugDatabase.outfitDescriptionText( DebugDatabase.rawOutfitDescriptionText( outfitId ) ); } public static final String readOutfitDescriptionText( final int outfitId ) { DebugDatabase.DESC_OUTFIT_REQUEST.clearDataFields(); DebugDatabase.DESC_OUTFIT_REQUEST.addFormField( "whichoutfit", String.valueOf( outfitId ) ); RequestThread.postRequest( DebugDatabase.DESC_OUTFIT_REQUEST ); return DebugDatabase.DESC_OUTFIT_REQUEST.responseText; } public static final String rawOutfitDescriptionText( final int outfitId ) { String previous = DebugDatabase.rawOutfits.get( outfitId ); if ( previous != null && !previous.equals( "" ) ) { return previous; } String text = DebugDatabase.readOutfitDescriptionText( outfitId ); DebugDatabase.rawOutfits.set( outfitId, text ); return text; } private static final Pattern OUTFIT_DATA_PATTERN = Pattern.compile( "<div id=\"description\"[^>]*>(.*?)</div>", Pattern.DOTALL ); public static final String outfitDescriptionText( final String rawText ) { if ( rawText == null ) { return null; } Matcher matcher = DebugDatabase.OUTFIT_DATA_PATTERN.matcher( rawText ); if ( !matcher.find() ) { return null; } return matcher.group( 1 ); } private static final void checkOutfitModifierMap( final PrintStream report, final ItemMap imap ) { Map<String, String> map = imap.getMap(); if ( map.size() == 0 ) { return; } String tag = imap.getTag(); report.println(); report.println( "# " + tag + " section of modifiers.txt" ); report.println(); Object[] keys = map.keySet().toArray(); for ( int i = 0; i < keys.length; ++i ) { String name = (String) keys[ i ]; String text = map.get( name ); DebugDatabase.checkOutfitModifierDatum( name, text, report ); } } private static final void checkOutfitModifierDatum( final String name, final String text, final PrintStream report ) { ModifierList known = new ModifierList(); ArrayList<String> unknown = new ArrayList<String>(); // Get the known and unknown modifiers from the outfit description DebugDatabase.parseOutfitEnchantments( text, known, unknown ); // Compare to what is already registered. // Log differences and substitute formulas, as appropriate. DebugDatabase.checkModifiers( "Outfit", name, known, false, report ); // Print the modifiers in the format modifiers.txt expects. DebugDatabase.logModifierDatum( "Outfit", name, known, unknown, report ); } private static final Pattern OUTFIT_ENCHANTMENT_PATTERN = Pattern.compile( "<b><font color=blue>(.*)</font></b>", Pattern.DOTALL ); public static final void parseOutfitEnchantments( final String text, final ModifierList known, final ArrayList<String> unknown ) { DebugDatabase.parseStandardEnchantments( text, known, unknown, DebugDatabase.OUTFIT_ENCHANTMENT_PATTERN ); } public static final String parseOutfitEnchantments( final String text, final ArrayList<String> unknown ) { ModifierList known = new ModifierList(); DebugDatabase.parseOutfitEnchantments( text, known, unknown ); return DebugDatabase.createModifierString( known ); } // ********************************************************** // Support for the "checkeffects" command, which compares KoLmafia's // internal status effect data from what can be mined from the effect // description. private static final String EFFECT_HTML = "effecthtml.txt"; private static final String EFFECT_DATA = "effectdata.txt"; private static final StringArray rawEffects = new StringArray(); private static final ItemMap effects = new ItemMap( "Status Effects", 0 ); public static final void checkEffects( final int effectId ) { RequestLogger.printLine( "Loading previous data..." ); DebugDatabase.loadScrapeData( rawEffects, EFFECT_HTML ); RequestLogger.printLine( "Checking internal data..." ); PrintStream report = DebugDatabase.openReport( EFFECT_DATA ); DebugDatabase.effects.clear(); if ( effectId == 0 ) { DebugDatabase.checkEffects( report ); } else { DebugDatabase.checkEffect( effectId, report ); } DebugDatabase.checkEffectModifiers( report ); report.close(); } private static final void checkEffects(final PrintStream report ) { Set<Integer> keys = EffectDatabase.descriptionIdKeySet(); Iterator<Integer> it = keys.iterator(); while ( it.hasNext() ) { int id = it.next().intValue(); if ( id < 1 ) { continue; } DebugDatabase.checkEffect( id, report ); } DebugDatabase.saveScrapeData( keys.iterator(), rawEffects, EFFECT_HTML ); } private static final void checkEffect( final int effectId, final PrintStream report ) { String name = EffectDatabase.getEffectName( effectId ); if ( name == null ) { return; } String rawText = DebugDatabase.rawEffectDescriptionText( effectId ); if ( rawText == null ) { report.println( "# *** " + name + " (" + effectId + ") has no description." ); return; } String text = DebugDatabase.effectDescriptionText( rawText ); if ( text == null ) { report.println( "# *** " + name + " (" + effectId + ") has malformed description text." ); return; } int id = DebugDatabase.parseEffectId( text ); if ( id != effectId ) { report.println( "# *** " + name + " (" + effectId + ") should have effectId " + id + "." ); } String descriptionName = DebugDatabase.parseName( text ); if ( !name.equalsIgnoreCase( StringUtilities.getCanonicalName( descriptionName ) ) ) { report.println( "# *** " + name + " (" + effectId + ") has description of " + descriptionName + "." ); return; } String descriptionImage = DebugDatabase.parseImage( rawText ); if ( !descriptionImage.equals( EffectDatabase.getImageName( id ) ) ) { report.println( "# *** " + name + " (" + effectId + ") has image of " + descriptionImage + "." ); } DebugDatabase.effects.put( name, text ); } // <!-- effectid: 806 --> private static final Pattern EFFECTID_PATTERN = Pattern.compile( "<!-- effectid: ([\\d]*) -->" ); public static final int parseEffectId( final String text ) { Matcher matcher = DebugDatabase.EFFECTID_PATTERN.matcher( text ); if ( !matcher.find() ) { return 0; } return StringUtilities.parseInt( matcher.group( 1 ) ); } // http://images.kingdomofloathing.com/itemimages/hp.gif // http://images.kingdomofloathing.com/otherimages/folders/folder22.gif // http://images.kingdomofloathing.com/otherimages/sigils/workouttat.gif private static final Pattern IMAGE_PATTERN = Pattern.compile( "images.kingdomofloathing.com/(.*?\\.gif)" ); public static final String parseImage( final String text ) { Matcher matcher = DebugDatabase.IMAGE_PATTERN.matcher( text ); String path = matcher.find() ? matcher.group( 1 ) : ""; String prefix1 = "itemimages/"; String prefix2 = "otherimages/sigils/"; return path.startsWith( prefix1 ) ? path.substring( prefix1.length() ) : path.startsWith( prefix2 ) ? path.substring( prefix2.length() ) : path; } // Grants Skill: <a class=hand onClick='javascript:poop("desc_skill.php?whichskill=163&self=true","skill", 350, 300)'><b>Gingerbread Mob Hit</b></a> private static final Pattern SKILL_ID_PATTERN = Pattern.compile( "whichskill=(\\d+)" ); public static final int parseSkillId( final String text ) { Matcher matcher = DebugDatabase.SKILL_ID_PATTERN.matcher( text ); return matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; } private static final Pattern SKILL_TYPE_PATTERN = Pattern.compile( "<b>Type:</b> (.*?)<br>" ); public static final String parseSkillType( final String text ) { Matcher matcher = DebugDatabase.SKILL_TYPE_PATTERN.matcher( text ); return matcher.find() ? matcher.group( 1 ) : ""; } private static final Pattern SKILL_MP_COST_PATTERN = Pattern.compile( "<b>MP Cost:</b> (\\d+)" ); public static final int parseSkillMPCost( final String text ) { Matcher matcher = DebugDatabase.SKILL_MP_COST_PATTERN.matcher( text ); return matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; } // Gives Effect: <b><a class=nounder href="desc_effect.php?whicheffect=69dcf3d8fe46c29e7fb6075d06448c95">Your Fifteen Minutes</a> private static final Pattern SKILL_EFFECT_PATTERN = Pattern.compile( "Gives Effect: .*?whicheffect=([^\">]*).*?>([^<]*)" ); public static final String parseSkillEffectName( final String text ) { Matcher matcher = DebugDatabase.SKILL_EFFECT_PATTERN.matcher( text ); return matcher.find() ? matcher.group( 2 ) : ""; } public static final String parseSkillEffectId( final String text ) { Matcher matcher = DebugDatabase.SKILL_EFFECT_PATTERN.matcher( text ); return matcher.find() ? matcher.group( 1 ) : ""; } private static final Pattern SKILL_EFFECT_DURATION_PATTERN = Pattern.compile( "\\((\\d+) Adventures?\\)" ); public static final int parseSkillEffectDuration( final String text ) { Matcher matcher = DebugDatabase.SKILL_EFFECT_DURATION_PATTERN.matcher( text ); return matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; } private static final GenericRequest DESC_SKILL_REQUEST = new GenericRequest( "desc_skill.php" ); public static final String skillDescriptionText( final int skillId ) { return DebugDatabase.skillDescriptionText( DebugDatabase.rawSkillDescriptionText( skillId ) ); } public static final String readSkillDescriptionText( final int skillId ) { DebugDatabase.DESC_SKILL_REQUEST.clearDataFields(); DebugDatabase.DESC_SKILL_REQUEST.addFormField( "whichskill", String.valueOf( skillId ) );; RequestThread.postRequest( DebugDatabase.DESC_SKILL_REQUEST ); return DebugDatabase.DESC_SKILL_REQUEST.responseText; } private static final String rawSkillDescriptionText( final int skillId ) { String previous = DebugDatabase.rawSkills.get( skillId ); if ( previous != null && !previous.equals( "" ) ) { return previous; } String text = DebugDatabase.readSkillDescriptionText( skillId ); DebugDatabase.rawSkills.set( skillId, text ); return text; } private static final Pattern SKILL_DATA_PATTERN = Pattern.compile( "<div id=\"description\"[^>]*>(.*?)</div>", Pattern.DOTALL ); private static final String skillDescriptionText( final String rawText ) { if ( rawText == null ) { return null; } Matcher matcher = DebugDatabase.SKILL_DATA_PATTERN.matcher( rawText ); if ( !matcher.find() ) { return null; } return matcher.group( 1 ); } // href="desc_effect.php?whicheffect=138ba5cbeccb6334a1d473710372e8d6" private static final Pattern EFFECT_DESCID_PATTERN = Pattern.compile( "whicheffect=(.*?)\"" ); public static final String parseEffectDescid( final String text ) { Matcher matcher = DebugDatabase.EFFECT_DESCID_PATTERN.matcher( text ); return matcher.find() ? matcher.group( 1 ) : ""; } private static final GenericRequest DESC_EFFECT_REQUEST = new GenericRequest( "desc_effect.php" ); public static final String effectDescriptionText( final int effectId ) { return DebugDatabase.effectDescriptionText( DebugDatabase.rawEffectDescriptionText( effectId ) ); } public static final String readEffectDescriptionText( final String descId ) { DebugDatabase.DESC_EFFECT_REQUEST.clearDataFields(); DebugDatabase.DESC_EFFECT_REQUEST.addFormField( "whicheffect", descId ); RequestThread.postRequest( DebugDatabase.DESC_EFFECT_REQUEST ); return DebugDatabase.DESC_EFFECT_REQUEST.responseText; } private static final String rawEffectDescriptionText( final int effectId ) { String descId = EffectDatabase.getDescriptionId( effectId ); if ( descId == null || descId.equals( "" ) ) { return null; } String previous = DebugDatabase.rawEffects.get( effectId ); if ( previous != null && !previous.equals( "" ) ) { return previous; } String text = DebugDatabase.readEffectDescriptionText( descId ); DebugDatabase.rawEffects.set( effectId, text ); return text; } private static final Pattern EFFECT_DATA_PATTERN = Pattern.compile( "<div id=\"description\"[^>]*>(.*?)</div>", Pattern.DOTALL ); private static final String effectDescriptionText( final String rawText ) { if ( rawText == null ) { return null; } Matcher matcher = DebugDatabase.EFFECT_DATA_PATTERN.matcher( rawText ); if ( !matcher.find() ) { return null; } return matcher.group( 1 ); } private static final void checkEffectModifiers( final PrintStream report ) { RequestLogger.printLine( "Checking modifiers..." ); DebugDatabase.checkEffectModifierMap( report, DebugDatabase.effects ); } private static final void checkEffectModifierMap( final PrintStream report, final ItemMap imap ) { Map<String, String> map = imap.getMap(); if ( map.size() == 0 ) { return; } String tag = imap.getTag(); report.println(); report.println( "# " + tag + " section of modifiers.txt" ); report.println(); Object[] keys = map.keySet().toArray(); for ( int i = 0; i < keys.length; ++i ) { String name = (String) keys[ i ]; String text = map.get( name ); DebugDatabase.checkEffectModifierDatum( name, text, report ); } } private static final Pattern EFFECT_ENCHANTMENT_PATTERN = Pattern.compile( "<font color=blue><b>(.*)</b></font>", Pattern.DOTALL ); public static final void parseEffectEnchantments( final String text, final ModifierList known, final ArrayList<String> unknown ) { DebugDatabase.parseStandardEnchantments( text, known, unknown, DebugDatabase.EFFECT_ENCHANTMENT_PATTERN ); } public static final String parseEffectEnchantments( final String text, final ArrayList<String> unknown ) { ModifierList known = new ModifierList(); DebugDatabase.parseEffectEnchantments( text, known, unknown ); return DebugDatabase.createModifierString( known ); } private static final void checkEffectModifierDatum( final String name, final String text, final PrintStream report ) { ModifierList known = new ModifierList(); ArrayList<String> unknown = new ArrayList<String>(); // Get the known and unknown modifiers from the effect description DebugDatabase.parseEffectEnchantments( text, known, unknown ); // Compare to what is already registered. // Log differences and substitute formulas, as appropriate. DebugDatabase.checkModifiers( "Effect", name, known, true, report ); // Print the modifiers in the format modifiers.txt expects. DebugDatabase.logModifierDatum( "Effect", name, known, unknown, report ); } // ********************************************************** // Support for the "checkskills" command, which compares KoLmafia's // internal skill data with what can be mined from the skill // description. private static final String SKILL_HTML = "skillhtml.txt"; private static final String SKILL_DATA = "skilldata.txt"; private static final StringArray rawSkills = new StringArray(); private static final ItemMap skills = new ItemMap( "Skills", 0 ); // ********************************************************** // Utilities for dealing with KoL description data private static final PrintStream openReport( final String fileName ) { return LogStream.openStream( new File( KoLConstants.DATA_LOCATION, fileName ), true ); } private static final void loadScrapeData( final StringArray array, final String fileName ) { try { File saveData = new File( KoLConstants.DATA_LOCATION, fileName ); if ( !saveData.exists() ) { return; } String currentLine; StringBuilder currentHTML = new StringBuilder(); BufferedReader reader = FileUtilities.getReader( saveData ); int lines = 0; while ( ( currentLine = reader.readLine() ) != null && !currentLine.equals( "" ) ) { lines += 1; currentHTML.setLength( 0 ); int currentId = StringUtilities.parseInt( currentLine ); do { currentLine = reader.readLine(); currentHTML.append( currentLine ); currentHTML.append( KoLConstants.LINE_BREAK ); } while ( !currentLine.equals( "</html>" ) ); if ( array.get( currentId ).equals( "" ) ) { array.set( currentId, currentHTML.toString() ); } reader.readLine(); } reader.close(); } catch ( Exception e ) { // This shouldn't happen, but if it does, go ahead and // fall through. You're done parsing. } } private static final void saveScrapeData( final Iterator<Integer> it, final StringArray array, final String fileName ) { File file = new File( KoLConstants.DATA_LOCATION, fileName ); PrintStream livedata = LogStream.openStream( file, true ); while ( it.hasNext() ) { int id = it.next().intValue(); if ( id < 1 ) { continue; } String description = array.get( id ); if ( description != null && !description.equals( "" ) ) { livedata.println( id ); livedata.println( description ); } } livedata.close(); } // ********************************************************** public static final void checkPlurals( int itemId ) { RequestLogger.printLine( "Checking plurals..." ); PrintStream report = LogStream.openStream( new File( KoLConstants.DATA_LOCATION, "plurals.txt" ), true ); if ( itemId == 0 ) { for ( Integer id : ItemDatabase.descriptionIdKeySet() ) { if ( id < 0 ) { continue; } while ( ++itemId < id ) { report.println( itemId ); } DebugDatabase.checkPlural( id, report ); } } else { DebugDatabase.checkPlural( itemId, report ); } report.close(); } private static final void checkPlural( final int itemId, final PrintStream report ) { Integer id = IntegerPool.get( itemId ); String name = ItemDatabase.getItemDataName( id ); if ( name == null ) { report.println( itemId ); return; } String plural = ItemDatabase.getPluralById( itemId ); if ( plural == null ) { // If we don't have a plural, the default is to simply // add an "s". plural = ""; } else if ( plural.equals( name + "s" ) ) { // If we do explicitly list a plural which is the // default, suppress it. plural = ""; } String displayPlural = StringUtilities.getDisplayName( plural.equals( "" ) ? name + "s" : plural ); // Don't bother checking quest items String access = ItemDatabase.getAccessById( id ); boolean logit = false; if ( access != null && !access.contains( ItemDatabase.QUEST_FLAG ) ) { String otherPlural = ""; boolean checkApi = InventoryManager.getCount( itemId ) > 1; if ( checkApi ) { otherPlural = DebugDatabase.readApiPlural( itemId ); if ( otherPlural.equals( "" ) ) { otherPlural = name + "s"; } String test = plural; if ( test.equals( "" ) ) { test = name + "s"; } if ( !test.equals( otherPlural ) ) { RequestLogger.printLine( "*** " + name + ": KoLmafia plural = \"" + displayPlural + "\", KoL plural = \"" + otherPlural + "\"" ); plural = otherPlural; } } else { String wikiData = DebugDatabase.readWikiData( name ); Matcher matcher = DebugDatabase.WIKI_PLURAL_PATTERN.matcher( wikiData ); otherPlural = matcher.find() ? matcher.group( 1 ) : ""; if ( otherPlural.equals( "" ) ) { // The Wiki does not list a plural. If ours is // non-default, log discrepancy and keep ours. if ( !plural.equals( "" ) ) { logit = true; } } else if ( otherPlural.equalsIgnoreCase( "I am a Fish" ) ) { RequestLogger.printLine( "*** " + name + " has bogus Wiki plural: \"" + otherPlural + "\". Ignoring." ); } else if ( plural.equals( "" ) ) { // The Wiki has a plural, but ours is the // default. If the Wiki's is NOT the default, // log it and tentatively accept it if ( !displayPlural.equals( otherPlural ) ) { logit = true; plural = "*** " + otherPlural; } } else { // Both we and the Wiki have plurals. If they // do not agree, log it, but keep ours. if ( !displayPlural.equals( otherPlural ) ) { logit = true; } } if ( logit ) { RequestLogger.printLine( "*** " + name + ": KoLmafia plural = \"" + displayPlural + "\", Wiki plural = \"" + otherPlural + "\"" ); } } } if ( plural.equals( "" ) ) { report.println( itemId + "\t" + name ); } else { report.println( itemId + "\t" + name + "\t" + plural ); } } // ********************************************************** public static final void checkPowers( final String option ) { // We can check the power of any items in inventory or closet. // We'll assume that any item with a non-zero power is correct. // Off-hand items and accessories don't have visible power and // might be 0 in the database. Look them up and fix them. if ( StringUtilities.isNumeric( option ) ) { DebugDatabase.checkPower( StringUtilities.parseInt( option ), true ); return; } TreeSet<AdventureResult> items = new TreeSet<AdventureResult>(); items.addAll( KoLConstants.inventory ); items.addAll( KoLConstants.closet ); items.addAll( EquipmentManager.allEquipmentAsList() ); // items.addAll( KoLConstants.storage ); if ( KoLCharacter.hasDisplayCase() && !DisplayCaseManager.collectionRetrieved ) { RequestThread.postRequest( new DisplayCaseRequest() ); } items.addAll( KoLConstants.collection ); DebugDatabase.checkPowers( items, option.equals( "all" ) ); } private static final void checkPowers( final Collection<AdventureResult> items, final boolean force ) { for ( AdventureResult item : items ) { int itemId = item.getItemId(); int type = ItemDatabase.getConsumptionType( itemId ); if ( type == KoLConstants.EQUIP_OFFHAND || type == KoLConstants.EQUIP_ACCESSORY || type == KoLConstants.EQUIP_CONTAINER ) { DebugDatabase.checkPower( itemId, force ); } } } private static final void checkPower( final int itemId, final boolean force ) { int current = EquipmentDatabase.getPower( itemId ); if ( !force && current != 0 ) { return; } // Look it up and register it anew ApiRequest request = new ApiRequest( "item", itemId ); RequestThread.postRequest( request ); JSONObject JSON = request.JSON; if ( JSON == null ) { AdventureResult item = ItemPool.get( itemId ); String location = KoLConstants.inventory.contains( item ) ? "inventory" : KoLConstants.closet.contains( item ) ? "closet" : KoLConstants.storage.contains( item ) ? "storage" : KoLConstants.collection.contains( item ) ? "display case" : "nowhere"; KoLmafia.updateDisplay( "Could not look up item " + item + " from " + location ); return; } try { int power = JSON.getInt( "power" ); // Yes, some items really are power 0 if ( power == 0 || power == current ) { return; } String name = JSON.getString( "name" ); String descid = JSON.getString( "descid" ); RequestLogger.printLine( "Item \"" + name +"\" power incorrect: " + current + " should be " + power ); ItemDatabase.registerItem( itemId, name, descid, null, power, false ); } catch ( JSONException e ) { KoLmafia.updateDisplay( "Error parsing JSON string!" ); StaticEntity.printStackTrace( e ); } } // ********************************************************** public static final void checkShields() { DebugDatabase.checkShields( KoLConstants.inventory ); DebugDatabase.checkShields( KoLConstants.closet ); DebugDatabase.checkShields( KoLConstants.storage ); } public static final void checkShields( final Collection items ) { Iterator it = items.iterator(); while ( it.hasNext() ) { AdventureResult item = (AdventureResult)it.next(); int itemId = item.getItemId(); if ( !EquipmentDatabase.getItemType( itemId ).equals( "shield" ) ) { continue; } ApiRequest request = new ApiRequest( "item", itemId ); RequestThread.postRequest( request ); JSONObject JSON = request.JSON; if ( JSON == null ) { continue; } try { int oldPower = EquipmentDatabase.getPower( itemId ); int correctPower = JSON.getInt( "power" ); if ( oldPower == correctPower ) { continue; } String name = JSON.getString( "name" ); String descid = JSON.getString( "descid" ); RequestLogger.printLine( "Shield \"" + name +"\" power incorrect: " + oldPower + " should be " + correctPower ); ItemDatabase.registerItem( itemId, name, descid, null, correctPower, false ); } catch ( JSONException e ) { KoLmafia.updateDisplay( "Error parsing JSON string!" ); StaticEntity.printStackTrace( e ); } } } // ********************************************************** public static final void checkPotions() { RequestLogger.printLine( "Loading previous data..." ); DebugDatabase.loadScrapeData( rawItems, ITEM_HTML ); Set keys = ItemDatabase.descriptionIdKeySet(); Iterator it = keys.iterator(); while ( it.hasNext() ) { Integer id = ( (Integer) it.next() ); int itemId = id.intValue(); if ( itemId < 1 || !ItemDatabase.isUsable( itemId ) || ItemDatabase.isEquipment( itemId ) ) { continue; } // Potions grant an effect. Check for a new effect. String itemName = ItemDatabase.getItemDataName( id ); String effectName = Modifiers.getStringModifier( "Item", itemId, "Effect" ); if ( !effectName.equals( "" ) && EffectDatabase.getEffectId( effectName, true ) == -1 ) { String rawText = DebugDatabase.rawItemDescriptionText( itemId ); String effectDescid = DebugDatabase.parseEffectDescid( rawText ); EffectDatabase.registerEffect( effectName, effectDescid, "use 1 " + itemName ); } } } // ********************************************************** private static final String CONSUMABLE_DATA = "consumables.txt"; public static final void checkConsumables() { RequestLogger.printLine( "Loading previous data..." ); DebugDatabase.loadScrapeData( rawItems, ITEM_HTML ); RequestLogger.printLine( "Checking internal data..." ); PrintStream report = DebugDatabase.openReport( CONSUMABLE_DATA ); DebugDatabase.checkConsumables( report ); report.close(); } private static final void checkConsumables( final PrintStream report ) { DebugDatabase.checkConsumables( report, ConsumablesDatabase.fullnessByName, "fullness" ); DebugDatabase.checkConsumables( report, ConsumablesDatabase.inebrietyByName, "inebriety" ); DebugDatabase.checkConsumables( report, ConsumablesDatabase.spleenHitByName, "spleenhit" ); } private static final void checkConsumables( final PrintStream report, final Map map, final String tag ) { if ( map.size() == 0 ) { return; } report.println( "" ); report.println( "# Consumption data in " + tag + ".txt" ); report.println( "#" ); Object[] keys = map.keySet().toArray(); for ( int i = 0; i < keys.length; ++i ) { String name = (String) keys[ i ]; int size = ((Integer) map.get( name ) ).intValue(); DebugDatabase.checkConsumable( report, name, size ); } } private static final void checkConsumable( final PrintStream report, final String name, final int size ) { int itemId = ItemDatabase.getItemId( name ); // It is valid for items to have no itemId: sushi, Cafe offerings, and so on String text = itemId == -1 ? "" : DebugDatabase.itemDescriptionText( itemId, false ); if ( text == null ) { return; } int level = ConsumablesDatabase.getLevelReqByName( name ).intValue(); String adv = ConsumablesDatabase.getAdvRangeByName( name ); String quality = ( itemId == -1 ) ? ConsumablesDatabase.getQuality( name ) : DebugDatabase.parseQuality( text ); String mus = ConsumablesDatabase.getMuscleByName( name ); String mys = ConsumablesDatabase.getMysticalityByName( name ); String mox = ConsumablesDatabase.getMoxieByName( name ); String notes = ConsumablesDatabase.getNotes( name ); ConsumablesDatabase.writeConsumable( report, name, size, level, quality, adv, mus, mys, mox, notes ); } // Type: <b>food <font color=#999999>(crappy)</font></b> // Type: <b>food (decent)</b> // Type: <b>booze <font color=green>(good)</font></b> // Type: <b>food <font color=blue>(awesome)</font></b> // Type: <b>food <font color=blueviolet>(EPIC)</font></b> private static final Pattern QUALITY_PATTERN = Pattern.compile( "Type: <b>.*?\\((.*?)\\).*?</b>" ); public static final String parseQuality( final String text ) { Matcher matcher = DebugDatabase.QUALITY_PATTERN.matcher( text ); return ConsumablesDatabase.qualityValue( matcher.find() ? matcher.group( 1 ) : "" ); } // ********************************************************** // <tr class="frow " data-stats="1" data-meat="1" data-items="1"><td valign=center><input type=radio name=newfam value=192></td><td valign=center><img src="/images/itemimages/goldmonkey.gif" class="hand fam" onClick='fam(192)'></td><td valign=top style='padding-top: .45em;'><b>Ignominious Uncguary</b>, the 20-pound Golden Monkey (400 exp, 6,107 kills) <font size="1"><br />    <a class="fave" href="familiar.php?group=0&action=fave&famid=192&pwd">[unfavorite]</a>  <a class="fave" href="familiar.php?&action=newfam&newfam=192&pwd">[take with you]</a></font></td><td valign=center nowrap><center><b>(</b><img src="/images/itemimages/goldbanana.gif" class=hand onClick='descitem(986943479)' align=middle><b>)</b><br><font size=1><a href='familiar.php?pwd&action=unequip&famid=192'>[unequip]</a></font></center></td></tr> private static final Pattern FAMILIAR_ROW_PATTERN = Pattern.compile( "<tr class=\"frow ?\"([^>]*)>.*?onClick='fam\\(([\\d]+)\\)'.*?</tr>" ); public static final void checkFamiliarsInTerrarium( boolean showVariable ) { FamiliarRequest request = new FamiliarRequest(); RequestThread.postRequest( request ); TreeMap<Integer,String> map = new TreeMap<Integer,String>(); Matcher matcher = DebugDatabase.FAMILIAR_ROW_PATTERN.matcher( request.responseText ); while ( matcher.find() ) { int id = StringUtilities.parseInt( matcher.group( 2 ) ); String powers = matcher.group( 1 ).trim(); map.put( id, powers ); } for ( Entry<Integer,String> entry : map.entrySet() ) { int id = entry.getKey().intValue(); String powers = entry.getValue(); DebugDatabase.checkTerrariumFamiliar( id, powers, showVariable ); } } private static final void checkTerrariumFamiliar( int id, String powers, boolean showVariable ) { // KoL familiar categories boolean dataAttack = powers.contains( "data-attack" ); boolean dataDefense = powers.contains( "data-defense" ); boolean dataHPRestore = powers.contains( "data-hp_restore" ); boolean dataItemDrops = powers.contains( "data-itemdrops" ); boolean dataItems = powers.contains( "data-items" ); boolean dataMeat = powers.contains( "data-meat" ); boolean dataMPRestore = powers.contains( "data-mp_restore" ); boolean dataOther = powers.contains( "data-other" ); boolean dataStats = powers.contains( "data-stats" ); boolean dataUnderwater = powers.contains( "data-underwater" ); // KoLmafia familiar categories boolean block = FamiliarDatabase.isBlockType( id ); boolean combat0 = FamiliarDatabase.isCombat0Type( id ); boolean combat1 = FamiliarDatabase.isCombat1Type( id ); boolean delevel = FamiliarDatabase.isDelevelType( id ); boolean drop = FamiliarDatabase.isDropType( id ); boolean hp0 = FamiliarDatabase.isHp0Type( id ); boolean hp1 = FamiliarDatabase.isHp1Type( id ); boolean item0 = FamiliarDatabase.isFairyType( id ); boolean meat0 = FamiliarDatabase.isMeatDropType( id ); boolean meat1 = FamiliarDatabase.isMeat1Type( id ); boolean mp0 = FamiliarDatabase.isMp0Type( id ); boolean mp1 = FamiliarDatabase.isMp1Type( id ); boolean none = FamiliarDatabase.isNoneType( id ); boolean other0 = FamiliarDatabase.isOther0Type( id ); boolean other1 = FamiliarDatabase.isOther1Type( id ); boolean passive = FamiliarDatabase.isPassiveType( id ); boolean stat0 = FamiliarDatabase.isVolleyType( id ); boolean stat1 = FamiliarDatabase.isSombreroType( id ); boolean stat2 = FamiliarDatabase.isStat2Type( id ); boolean stat3 = FamiliarDatabase.isStat3Type( id ); boolean underwater = FamiliarDatabase.isUnderwaterType( id ); boolean variable = FamiliarDatabase.isVariableType( id ); String name = FamiliarDatabase.getFamiliarName( id ); String prefix = "*** familiar #" + id + " (" + name + "): KoL says "; // Check KoL categories if ( dataAttack && !( combat0 || combat1 ) ) { String message = !variable ? "'attack' but we have neither 'combat0' nor 'combat1'" : showVariable ? "'attack' but we say 'variable'" : null; if ( message != null ) { RequestLogger.printLine( prefix + message ); } } if ( dataDefense && !( block || delevel || other0 ) ) { String message = !variable ? "'defense' but we have none of 'block', 'delevel', or 'other0'" : showVariable ? "'defense' but we say 'variable'" : null; if ( message != null ) { RequestLogger.printLine( prefix + message ); } } if ( dataHPRestore && !( hp0 || hp1 ) ) { String message = !variable ? "'hp_restore' but we have neither 'hp0' nor 'hp1'" : showVariable ? "'hp_restore' but we say 'variable'" : null; if ( message != null ) { RequestLogger.printLine( prefix + message ); } } if ( dataItemDrops && !item0 ) { String message = !variable ? "'itemdrops' but we do not have 'item0'" : showVariable ? "'itemdrops' but we say 'variable'" : null; if ( message != null ) { RequestLogger.printLine( prefix + message ); } } if ( dataItems && !drop ) { String message = !variable ? "'item' but we do not have 'drop'" : showVariable ? "'item' but we say 'variable'" : null; if ( message != null ) { RequestLogger.printLine( prefix + message ); } } if ( dataMeat && !( meat0 || meat1 ) ) { String message = !variable ? "'meat' but we have neither 'meat0' nor 'meat1'" : showVariable ? "'meat' but we say 'variable'" : null; if ( message != null ) { RequestLogger.printLine( prefix + message ); } } if ( dataMPRestore && !( mp0 || mp1 ) ) { String message = !variable ? "'mp_restore' but we have neither 'mp0' nor 'mp1'" : showVariable ? "'mp_restore' but we say 'variable'" : null; if ( message != null ) { RequestLogger.printLine( prefix + message ); } } if ( dataOther && !( none || other0 || other1 || passive ) ) { String message = !variable ? "'other' but we have none of 'none', 'other0', 'other1',or 'passive'" : showVariable ? "'other' but we say 'variable'" : null; if ( message != null ) { RequestLogger.printLine( prefix + message ); } } if ( dataStats && !( stat0 || stat1 || stat2 || stat3 || passive ) ) { String message = !variable ? "'stats' but we have none of 'stat0', 'stat1', 'stat2', or 'stat3'" : showVariable ? "'stats' but we say 'variable'" : null; if ( message != null ) { RequestLogger.printLine( prefix + message ); } } if ( dataUnderwater && !underwater ) { String message = !variable ? "'underwater' but we do not have 'underwater'" : showVariable ? "'underwater' but we say 'variable'" : null; if ( message != null ) { RequestLogger.printLine( prefix + message ); } } // Check KoLmafia categories if ( block && !dataDefense ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'block' but KoL does not say 'defense'" ); } if ( combat0 && !dataAttack ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'combat0' but KoL does not say 'attack'" ); } if ( combat1 && !dataAttack ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'combat1' but KoL does not say 'attack'" ); } if ( delevel && !dataDefense ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'delevel' but KoL does not say 'defense'" ); } if ( drop && !dataItems ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'drop' but KoL does not say 'items'" ); } if ( hp0 && !dataHPRestore ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'hp0' but KoL does not say 'hp_restore'" ); } if ( hp1 && !dataHPRestore ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'hp1' but KoL does not say 'hp_restore'" ); } if ( item0 && !dataItemDrops ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'item0' but KoL does not say 'itemdrops'" ); } if ( meat0 && !dataMeat ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'meat0' but KoL does not say 'meat'" ); } if ( meat1 && !dataMeat ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'meat1' but KoL does not say 'meat'" ); } if ( mp0 && !dataMPRestore ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'mp0' but KoL does not say 'mp_restore'" ); } if ( mp1 && !dataMPRestore ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'mp1' but KoL does not say 'mp_restore'" ); } if ( none && !dataOther ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'none' but KoL does not say 'other'" ); } if ( other0 && !( dataOther || dataDefense ) ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'other0' but KoL does not say 'other' or 'defense'" ); } if ( other1 && !dataOther ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'other1' but KoL does not say 'other'" ); } if ( passive && !dataOther ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'passive' but KoL does not say 'other'" ); } if ( stat0 && !dataStats ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'stat0' but KoL does not say 'stats'" ); } if ( stat1 && !dataStats ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'stat1' but KoL does not say 'stats'" ); } if ( stat2 && !dataStats ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'stat2' but KoL does not say 'stats'" ); } if ( stat3 && !dataStats ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'stat3' but KoL does not say 'stats'" ); } if ( underwater && !dataUnderwater ) { RequestLogger.printLine( "*** familiar #" + id + " (" + name + "): KoLmafia has 'underwater' but KoL does not say 'underwater'" ); } } public static final void checkFamiliarImages() { // Get familiar images from the familiar description boolean changed = false; for ( int i = 1; i <= FamiliarDatabase.maxFamiliarId; ++i ) { changed |= DebugDatabase.checkFamiliarImage( i ); } // FamiliarDatabase.saveDataOverride(); } private static final Pattern FAMILIAR_IMAGE_PATTERN = Pattern.compile( "images\\.kingdomofloathing\\.com/itemimages/(.*?\\.gif)" ); private static final boolean checkFamiliarImage( final int id ) { String file = "desc_familiar.php?which=" + String.valueOf( id ); GenericRequest request = new GenericRequest( file ); RequestThread.postRequest( request ); String text = request.responseText; if ( text == null ) { RequestLogger.printLine( "*** no description for familiar #" + id ); return false; } boolean changed = false; Matcher matcher = FAMILIAR_IMAGE_PATTERN.matcher( text ); if ( matcher.find() ) { String oldImage = FamiliarDatabase.getFamiliarImageLocation( id ); String newImage = matcher.group( 1 ); if ( !oldImage.equals( newImage ) ) { RequestLogger.printLine( "*** familiar #" + id + " has image " + oldImage + " but KoL says it is " + newImage ); FamiliarDatabase.setFamiliarImageLocation( id, newImage ); changed = true; } } return changed; } // ********************************************************** public static final void checkConsumptionData() { RequestLogger.printLine( "Checking consumption data..." ); PrintStream writer = LogStream.openStream( new File( KoLConstants.DATA_LOCATION, "consumption.txt" ), true ); DebugDatabase.checkEpicure( writer ); DebugDatabase.checkMixologist( writer ); writer.close(); } private static final String EPICURE = "http://kol.coldfront.net/tools/epicure/export_data.php"; private static final void checkEpicure( final PrintStream writer ) { RequestLogger.printLine( "Connecting to Well-Tempered Epicure..." ); Document doc = getXMLDocument( EPICURE ); if ( doc == null ) { return; } writer.println( KoLConstants.FULLNESS_VERSION ); writer.println( "# Data provided courtesy of the Garden of Earthly Delights" ); writer.println( "# The Well-Tempered Epicure: " + EPICURE ); writer.println(); writer.println( "# Food" + "\t" + "Fullness" + "\t" + "Level Req" + "\t" + "Adv" + "\t" + "Musc" + "\t" + "Myst" + "\t" + "Moxie" ); writer.println(); NodeList elements = doc.getElementsByTagName( "iteminfo" ); for ( int i = 0; i < elements.getLength(); i++ ) { Node element = elements.item( i ); checkFood( element, writer ); } } private static final void checkFood( final Node element, final PrintStream writer ) { String name= ""; String advs= ""; String musc= ""; String myst= ""; String mox= ""; String fullness= ""; String level= ""; for ( Node node = element.getFirstChild(); node != null; node = node.getNextSibling() ) { String tag = node.getNodeName(); Node child = node.getFirstChild(); if ( tag.equals( "title" ) ) { name = DebugDatabase.getStringValue( child ); } else if ( tag.equals( "advs" ) ) { advs = DebugDatabase.getNumericValue( child ); } else if ( tag.equals( "musc" ) ) { musc = DebugDatabase.getNumericValue( child ); } else if ( tag.equals( "myst" ) ) { myst = DebugDatabase.getNumericValue( child ); } else if ( tag.equals( "mox" ) ) { mox = DebugDatabase.getNumericValue( child ); } else if ( tag.equals( "fullness" ) ) { fullness = DebugDatabase.getNumericValue( child ); } else if ( tag.equals( "level" ) ) { level = DebugDatabase.getNumericValue( child ); } } String line = name + "\t" + fullness + "\t" + level + "\t" + advs + "\t" + musc + "\t" + myst + "\t" + mox; int present = ConsumablesDatabase.getFullness( name ); if ( present == 0 ) { writer.println( "# Unknown food:" ); writer.print( "# " ); } else { String note = ConsumablesDatabase.getNotes( name ); if ( note != null ) { line = line + "\t" + note; } } writer.println( line ); } private static final String MIXOLOGIST = "http://kol.coldfront.net/tools/mixology/export_data.php"; private static final void checkMixologist( final PrintStream writer ) { RequestLogger.printLine( "Connecting to Well-Tempered Mixologist..." ); Document doc = getXMLDocument( MIXOLOGIST ); if ( doc == null ) { return; } writer.println( KoLConstants.INEBRIETY_VERSION ); writer.println( "# Data provided courtesy of the Garden of Earthly Delights" ); writer.println( "# The Well-Tempered Mixologist: " + MIXOLOGIST ); writer.println(); writer.println( "# Drink" + "\t" + "Inebriety" + "\t" + "Level Req" + "\t" + "Adv" + "\t" + "Musc" + "\t" + "Myst" + "\t" + "Moxie" ); writer.println(); NodeList elements = doc.getElementsByTagName( "iteminfo" ); for ( int i = 0; i < elements.getLength(); i++ ) { Node element = elements.item( i ); checkBooze( element, writer ); } } private static final void checkBooze( final Node element, final PrintStream writer ) { String name= ""; String advs= ""; String musc= ""; String myst= ""; String mox= ""; String drunk= ""; String level= ""; for ( Node node = element.getFirstChild(); node != null; node = node.getNextSibling() ) { String tag = node.getNodeName(); Node child = node.getFirstChild(); if ( tag.equals( "title" ) ) { name = DebugDatabase.getStringValue( child ); } else if ( tag.equals( "advs" ) ) { advs = DebugDatabase.getNumericValue( child ); } else if ( tag.equals( "musc" ) ) { musc = DebugDatabase.getNumericValue( child ); } else if ( tag.equals( "myst" ) ) { myst = DebugDatabase.getNumericValue( child ); } else if ( tag.equals( "mox" ) ) { mox = DebugDatabase.getNumericValue( child ); } else if ( tag.equals( "drunk" ) ) { drunk = DebugDatabase.getNumericValue( child ); } else if ( tag.equals( "level" ) ) { level = DebugDatabase.getNumericValue( child ); } } String line = name + "\t" + drunk + "\t" + level + "\t" + advs + "\t" + musc + "\t" + myst + "\t" + mox; int present = ConsumablesDatabase.getInebriety( name ); if ( present == 0 ) { writer.println( "# Unknown booze:" ); writer.print( "# " ); } else { String note = ConsumablesDatabase.getNotes( name ); if ( note != null ) { line = line + "\t" + note; } } writer.println( line ); } private static final String getStringValue( final Node node ) { return StringUtilities.getEntityEncode( node.getNodeValue().trim() ); } private static final String getNumericValue( final Node node ) { String value = node.getNodeValue().trim(); int sign = value.startsWith( "-" ) ? -1 : 1; if ( sign == -1 ) { value = value.substring( 1 ); } int dash = value.indexOf( "-" ); if ( dash == -1 ) { return String.valueOf( sign * StringUtilities.parseInt( value ) ); } int first = sign * StringUtilities.parseInt( value.substring( 0, dash) ); int second = StringUtilities.parseInt( value.substring( dash + 1 ) ); return String.valueOf( first ) + "-" + String.valueOf( second ); } private static final Document getXMLDocument( final String uri ) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { DocumentBuilder db = dbf.newDocumentBuilder(); return db.parse( uri ); } catch ( Exception e ) { RequestLogger.printLine( "Failed to parse XML document from \"" + uri + "\": " + e.getMessage() ); } return null; } public static final void checkPulverizationData() { RequestLogger.printLine( "Checking pulverization data..." ); PrintStream writer = LogStream.openStream( new File( KoLConstants.DATA_LOCATION, "pulvereport.txt" ), true ); DebugDatabase.checkAnvil( writer ); writer.close(); } private static final String ANVIL = "http://kol.coldfront.net/tools/anvil/export_data.php"; private static final void checkAnvil( final PrintStream writer ) { RequestLogger.printLine( "Connecting to Well-Tempered Anvil..." ); Document doc = getXMLDocument( ANVIL ); if ( doc == null ) { return; } writer.println( KoLConstants.PULVERIZE_VERSION ); writer.println( "# Data provided courtesy of the Garden of Earthly Delights" ); writer.println( "# The Well-Tempered Anvil: " + ANVIL ); writer.println(); NodeList elements = doc.getElementsByTagName( "iteminfo" ); HashSet<Integer> seen = new HashSet<Integer>(); for ( int i = 0; i < elements.getLength(); i++ ) { Node element = elements.item( i ); checkPulverize( element, writer, seen ); } for ( int id = 1; id <= ItemDatabase.maxItemId(); ++id ) { int pulver = EquipmentDatabase.getPulverization( id ); if ( pulver != -1 && !seen.contains( IntegerPool.get( id ) ) ) { String name = ItemDatabase.getItemName( id ); writer.println( name + ": not listed in anvil" ); } } } private static final void checkPulverize( final Node element, final PrintStream writer, HashSet<Integer> seen ) { String name= ""; int id = -1; int yield = -1; boolean cansmash = false; boolean confirmed = false; boolean twinkly = false; boolean hot = false; boolean cold = false; boolean stench = false; boolean spooky = false; boolean sleaze = false; for ( Node node = element.getFirstChild(); node != null; node = node.getNextSibling() ) { String tag = node.getNodeName(); Node child = node.getFirstChild(); if ( tag.equals( "cansmash" ) ) { cansmash = DebugDatabase.getStringValue( child ).equals( "y" ); } else if ( tag.equals( "confirmed" ) ) { confirmed = DebugDatabase.getStringValue( child ).equals( "y" ); } else if ( tag.equals( "title" ) ) { name = DebugDatabase.getStringValue( child ); } else if ( tag.equals( "kolid" ) ) { id = StringUtilities.parseInt( DebugDatabase.getNumericValue( child ) ); seen.add( IntegerPool.get( id ) ); } else if ( tag.equals( "yield" ) ) { yield = StringUtilities.parseInt( DebugDatabase.getNumericValue( child ) ); } else if ( tag.equals( "cold" ) ) { cold = !DebugDatabase.getStringValue( child ).equals( "0" ); } else if ( tag.equals( "hot" ) ) { hot = !DebugDatabase.getStringValue( child ).equals( "0" ); } else if ( tag.equals( "sleazy" ) ) { sleaze = !DebugDatabase.getStringValue( child ).equals( "0" ); } else if ( tag.equals( "spooky" ) ) { spooky = !DebugDatabase.getStringValue( child ).equals( "0" ); } else if ( tag.equals( "stinky" ) ) { stench = !DebugDatabase.getStringValue( child ).equals( "0" ); } else if ( tag.equals( "twinkly" ) ) { twinkly = !DebugDatabase.getStringValue( child ).equals( "0" ); } } if ( id < 1 ) { writer.println( name + ": anvil doesn't know ID, so can't check" ); return; } int pulver = EquipmentDatabase.getPulverization( id ); if ( !name.equalsIgnoreCase( ItemDatabase.getItemName( id ) ) ) { writer.println( name + ": doesn't match mafia name: " + ItemDatabase.getItemName( id ) ); } name = ItemDatabase.getItemName( id ); if ( !confirmed ) { name = "(unconfirmed) " + name; } if ( pulver == -1 ) { if ( cansmash ) { writer.println( name + ": anvil says this is smashable" ); } return; } if ( !cansmash ) { writer.println( name + ": anvil says this is not smashable" ); return; } if ( pulver == ItemPool.USELESS_POWDER ) { if ( yield != 1 || twinkly || hot || cold || stench || spooky || sleaze ) { writer.println( name + ": anvil says something other than useless powder" ); } return; } if ( yield == 1 && !(twinkly || hot || cold || stench || spooky || sleaze ) ) { writer.println( name + ": anvil says useless powder" ); return; } if ( pulver == ItemPool.EPIC_WAD ) { if ( yield != 10 ) { writer.println( name + ": anvil says something other than epic wad" ); } return; } if ( yield == 10 ) { writer.println( name + ": anvil says epic wad" ); return; } if ( pulver == ItemPool.ULTIMATE_WAD ) { if ( yield != 11 ) { writer.println( name + ": anvil says something other than ultimate wad" ); } return; } if ( yield == 11 ) { writer.println( name + ": anvil says ultimate wad" ); return; } if ( pulver == ItemPool.SEA_SALT_CRYSTAL ) { if ( yield != 12 ) { writer.println( name + ": anvil says something other than sea salt crystal" ); } return; } if ( yield == 12 ) { writer.println( name + ": anvil says sea salt crystal" ); return; } if ( pulver >= 0 ) { writer.println( name + ": I don't know how anvil would say " + ItemDatabase.getItemName( pulver ) ); return; } if ( yield < 1 || yield > 12 ) { writer.println( name + ": anvil said yield=" + yield + ", wut?" ); return; } if ( (pulver & EquipmentDatabase.ELEM_TWINKLY) != 0 ) { if ( !twinkly ) { writer.println( name + ": anvil didn't say twinkly" ); } return; } else if ( twinkly ) { writer.println( name + ": anvil said twinkly" ); return; } if ( (pulver & EquipmentDatabase.ELEM_HOT) != 0 ) { if ( !hot ) { writer.println( name + ": anvil didn't say hot" ); } return; } else if ( hot ) { writer.println( name + ": anvil said hot" ); return; } if ( (pulver & EquipmentDatabase.ELEM_COLD) != 0 ) { if ( !cold ) { writer.println( name + ": anvil didn't say cold" ); } return; } else if ( cold ) { writer.println( name + ": anvil said cold" ); return; } if ( (pulver & EquipmentDatabase.ELEM_STENCH) != 0 ) { if ( !stench ) { writer.println( name + ": anvil didn't say stench" ); } return; } else if ( stench ) { writer.println( name + ": anvil said stench" ); return; } if ( (pulver & EquipmentDatabase.ELEM_SPOOKY) != 0 ) { if ( !spooky ) { writer.println( name + ": anvil didn't say spooky" ); } return; } else if ( spooky ) { writer.println( name + ": anvil said spooky" ); return; } if ( (pulver & EquipmentDatabase.ELEM_SLEAZE) != 0 ) { if ( !sleaze ) { writer.println( name + ": anvil didn't say sleaze" ); } return; } else if ( sleaze ) { writer.println( name + ": anvil said sleaze" ); return; } int myyield = 1; while ( (pulver & EquipmentDatabase.YIELD_1P) == 0 ) { myyield++; } if ( yield != myyield ) { writer.println( name + ": anvil said yield is " + yield + ", not " + myyield ); return; } } private static final Pattern ZAPGROUP_PATTERN = Pattern.compile( "Template:ZAP .*?</a>.*?<td>.*?<td>" ); private static final Pattern ZAPITEM_PATTERN = Pattern.compile( ">([^<]+)</a>" ); public static final void checkZapGroups() { RequestLogger.printLine( "Checking zap groups..." ); PrintStream report = LogStream.openStream( new File( KoLConstants.DATA_LOCATION, "zapreport.txt" ), true ); String[] groups = DebugDatabase.ZAPGROUP_PATTERN.split( DebugDatabase.readWikiData( "Zapping" ) ); for ( int i = 1; i < groups.length; ++i ) { String group = groups[ i ]; int pos = group.indexOf( "</td>" ); if ( pos != -1 ) { group = group.substring( 0, pos ); } Matcher m = DebugDatabase.ZAPITEM_PATTERN.matcher( group ); ArrayList<String> items = new ArrayList<String>(); while ( m.find() ) { items.add( m.group( 1 ) ); } if ( items.size() > 1 ) { DebugDatabase.checkZapGroup( items, report ); } } report.close(); } private static void checkZapGroup( ArrayList<String> items, PrintStream report ) { String firstItem = items.get( 0 ); int itemId = ItemDatabase.getItemId( firstItem ); if ( itemId == -1 ) { report.println( "Group with unrecognized item: " + firstItem ); return; } String[] zapgroup = ZapRequest.getZapGroup( itemId ); if ( zapgroup.length == 0 ) { report.println( "New group:" ); Iterator<String> i = items.iterator(); while ( i.hasNext() ) { report.print( i.next() ); report.print( ", " ); } report.println( "" ); return; } ArrayList<String> existing = new ArrayList<String>(); existing.addAll( Arrays.asList( zapgroup ) ); existing.removeAll( items ); items.removeAll( Arrays.asList( zapgroup ) ); if ( items.size() == 0 && existing.size() == 0 ) { report.println( "Group OK: " + firstItem ); return; } report.println( "Modified group: " + firstItem ); report.println( "Added:" ); Iterator<String> i = items.iterator(); while ( i.hasNext() ) { report.print( i.next() ); report.print( ", " ); } report.println( "" ); report.println( "Removed:" ); i = existing.iterator(); while ( i.hasNext() ) { report.print( i.next() ); report.print( ", " ); } report.println( "" ); } // Check Monster Manuel public static final void checkManuel() { RequestLogger.printLine( "Checking Monster Manuel..." ); DebugDatabase.checkManuelPage( "a" ); DebugDatabase.checkManuelPage( "b" ); DebugDatabase.checkManuelPage( "c" ); DebugDatabase.checkManuelPage( "d" ); DebugDatabase.checkManuelPage( "e" ); DebugDatabase.checkManuelPage( "f" ); DebugDatabase.checkManuelPage( "g" ); DebugDatabase.checkManuelPage( "h" ); DebugDatabase.checkManuelPage( "i" ); DebugDatabase.checkManuelPage( "j" ); DebugDatabase.checkManuelPage( "k" ); DebugDatabase.checkManuelPage( "l" ); DebugDatabase.checkManuelPage( "m" ); DebugDatabase.checkManuelPage( "n" ); DebugDatabase.checkManuelPage( "o" ); DebugDatabase.checkManuelPage( "p" ); DebugDatabase.checkManuelPage( "q" ); DebugDatabase.checkManuelPage( "r" ); DebugDatabase.checkManuelPage( "s" ); DebugDatabase.checkManuelPage( "t" ); DebugDatabase.checkManuelPage( "u" ); DebugDatabase.checkManuelPage( "v" ); DebugDatabase.checkManuelPage( "w" ); DebugDatabase.checkManuelPage( "x" ); DebugDatabase.checkManuelPage( "y" ); DebugDatabase.checkManuelPage( "z" ); DebugDatabase.checkManuelPage( "-" ); } private static final void checkManuelPage( final String page ) { RequestLogger.printLine( "Page " + page.toUpperCase() ); MonsterManuelRequest request = new MonsterManuelRequest( page ); RequestThread.postRequest( request ); } }