/** * 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.util.List; import java.util.Map; import java.util.TreeMap; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.RequestLogger; import net.sourceforge.kolmafia.StaticEntity; import net.sourceforge.kolmafia.objectpool.FamiliarPool; import net.sourceforge.kolmafia.objectpool.IntegerPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.objectpool.OutfitPool; import net.sourceforge.kolmafia.persistence.HolidayDatabase; import net.sourceforge.kolmafia.persistence.QuestDatabase.Quest; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.CampgroundRequest; import net.sourceforge.kolmafia.request.NPCPurchaseRequest; import net.sourceforge.kolmafia.request.PurchaseRequest; import net.sourceforge.kolmafia.request.QuestLogRequest; import net.sourceforge.kolmafia.request.StandardRequest; import net.sourceforge.kolmafia.session.EquipmentManager; import net.sourceforge.kolmafia.session.InventoryManager; import net.sourceforge.kolmafia.utilities.FileUtilities; import net.sourceforge.kolmafia.utilities.HashMultimap; import net.sourceforge.kolmafia.utilities.StringUtilities; public class NPCStoreDatabase { private static final HashMultimap<NPCPurchaseRequest> NPC_ITEMS = new HashMultimap<NPCPurchaseRequest>(); private static final HashMultimap<NPCPurchaseRequest> ROW_ITEMS = new HashMultimap<NPCPurchaseRequest>(); private static final AdventureResult RABBIT_HOLE = new AdventureResult( "Down the Rabbit Hole", 1, true ); private static final Map<String, String> storeNameById = new TreeMap<String, String>(); static { BufferedReader reader = FileUtilities.getVersionedReader( "npcstores.txt", KoLConstants.NPCSTORES_VERSION ); String[] data; while ( ( data = FileUtilities.readData( reader ) ) != null ) { if ( data.length < 4 ) { continue; } String storeName = new String( data[0] ); String storeId = new String( data[1] ); if ( !storeId.equals( "bartlebys" ) ) { NPCStoreDatabase.storeNameById.put( storeId, storeName ); } String itemName = data[ 2 ]; int itemId = ItemDatabase.getItemId( itemName ); if ( itemId == -1 ) { RequestLogger.printLine( "Unknown item in store \"" + data[ 0 ] + "\": " + itemName ); continue; } int price = StringUtilities.parseInt( data[ 3 ] ); int row = ( data.length > 4 && data[ 4 ].startsWith( "ROW" ) ) ? IntegerPool.get( StringUtilities.parseInt( data[ 4 ].substring( 3 ) ) ) : 0; // Make the purchase request for this item int quantity = NPCStoreDatabase.limitQuantity( itemId ); NPCPurchaseRequest purchaseRequest = new NPCPurchaseRequest( storeName, storeId, itemId, row, price, quantity ); // Map from item id -> purchase request NPCStoreDatabase.NPC_ITEMS.put( itemId, purchaseRequest ); // Map from row -> purchase request if ( row != 0 ) { NPCStoreDatabase.ROW_ITEMS.put( row, purchaseRequest ); } } try { reader.close(); } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); } } public static final String getStoreName( final String storeId ) { return storeId.equals( "bartlebys" ) ? ( KoLCharacter.inBeecore() ? "Barrrtleby's Barrrgain Books (Bees Hate You)" : "Barrrtleby's Barrrgain Books" ) : NPCStoreDatabase.storeNameById.get( storeId ); } public static final PurchaseRequest getPurchaseRequest( final int itemId ) { NPCPurchaseRequest foundItem = null; List<NPCPurchaseRequest> items = NPCStoreDatabase.NPC_ITEMS.get( itemId ); if ( items == null ) { return null; } for ( NPCPurchaseRequest item : items ) { foundItem = item; if ( !NPCStoreDatabase.canPurchase( item.getStoreId(), item.getShopName(), itemId ) ) { continue; } item.setCanPurchase( true ); return item; } if ( foundItem == null ) { return null; } foundItem.setCanPurchase( false ); return foundItem; } private static final int limitQuantity( int itemId ) { switch( itemId ) { case ItemPool.ABRIDGED: case ItemPool.ZEPPELIN_TICKET: case ItemPool.FORGED_ID_DOCUMENTS: case ItemPool.SPARE_KIDNEY: return 1; } return PurchaseRequest.MAX_QUANTITY; } private static final boolean canPurchase( final String storeId, final String shopName, final int itemId ) { if ( storeId == null ) { return false; } // Check for whether or not the purchase can be made from a // guild store. Store #1 is moxie classes, store #2 is for // mysticality classes, and store #3 is for muscle classes. String classType = KoLCharacter.getClassType(); if ( storeId.equals( "gnoll" ) ) { // Degrassi Knoll Bakery and Hardware Store return KoLCharacter.knollAvailable(); } else if ( storeId.equals( "tweedle" ) ) { // The Tweedleporium return KoLConstants.activeEffects.contains( NPCStoreDatabase.RABBIT_HOLE ); } else if ( storeId.equals( "bugbear" ) ) { if ( KoLCharacter.inNuclearAutumn() ) { return false; } // Bugbear Bakery return EquipmentManager.hasOutfit( OutfitPool.BUGBEAR_COSTUME ); } else if ( storeId.equals( "madeline" ) ) { // Bugbear Bakery return QuestDatabase.isQuestFinished( Quest.ARMORER ); } else if ( storeId.equals( "bartender" ) ) { // The Typical Tavern return !KoLCharacter.inZombiecore() && QuestLogRequest.isTavernAvailable(); } else if ( storeId.equals( "blackmarket" ) ) { // Black Market if ( ! QuestLogRequest.isBlackMarketAvailable() ) { return false; } switch ( itemId ) { case ItemPool.ZEPPELIN_TICKET: return !InventoryManager.hasItem( itemId ); case ItemPool.SPARE_KIDNEY: // Should check for whether your kidney has been stolen return KoLCharacter.inBadMoon() && !InventoryManager.hasItem( itemId ); case ItemPool.FORGED_ID_DOCUMENTS: return !QuestDatabase.isQuestLaterThan( Quest.MACGUFFIN, "step1" ); } return true; } else if ( storeId.equals( "chateau" ) ) { // Chateau Mantenga return Preferences.getBoolean( "chateauAvailable" ) && StandardRequest.isAllowed( "Items", "Chateau Mantegna room key" ); } else if ( storeId.equals( "chinatown" ) ) { // Chinatown Shops return KoLConstants.inventory.contains( ItemPool.get( ItemPool.STRANGE_GOGGLES, 1 ) ) && KoLConstants.campground.contains( ItemPool.get( ItemPool.SUSPICIOUS_JAR, 1 ) ); } else if ( storeId.equals( "guildstore1" ) ) { // Shadowy Store return KoLCharacter.isMoxieClass() && KoLCharacter.getGuildStoreOpen(); } else if ( storeId.equals( "guildstore2" ) ) { // Gouda's Grimoire and Grocery return ( KoLCharacter.isMysticalityClass() || ( classType.equals( KoLCharacter.ACCORDION_THIEF ) && KoLCharacter.getLevel() >= 9) ) && KoLCharacter.getGuildStoreOpen(); } else if ( storeId.equals( "guildstore3" ) ) { // Smacketeria return ( ( KoLCharacter.isMuscleClass() && !KoLCharacter.isAvatarOfBoris() ) || ( classType.equals( KoLCharacter.ACCORDION_THIEF ) && KoLCharacter.getLevel() >= 9 ) ) && KoLCharacter.getGuildStoreOpen(); } else if ( storeId.equals( "hippy" ) ) { int level = KoLCharacter.getLevel(); if ( shopName.equals( "Hippy Store (Pre-War)" ) ) { if ( !KoLCharacter.mysteriousIslandAccessible() || !EquipmentManager.hasOutfit( OutfitPool.HIPPY_OUTFIT ) ) { return false; } if ( Preferences.getInteger( "lastFilthClearance" ) == KoLCharacter.getAscensions() ) { return false; } if ( level < 12 ) { return true; } return QuestLogRequest.isHippyStoreAvailable(); } // Here, you insert any logic which is able to detect // the completion of the filthworm infestation and // which outfit was used to complete it. if ( Preferences.getInteger( "lastFilthClearance" ) != KoLCharacter.getAscensions() ) { return false; } int outfit = OutfitPool.NONE; if ( shopName.equals( "Hippy Store (Hippy)" ) ) { if ( !Preferences.getString( "currentHippyStore" ).equals( "hippy" ) ) { return false; } outfit = OutfitPool.WAR_HIPPY_OUTFIT; } else if ( shopName.equals( "Hippy Store (Fratboy)" ) ) { if ( !Preferences.getString( "currentHippyStore" ).equals( "fratboy" ) ) { return false; } outfit = OutfitPool.WAR_FRAT_OUTFIT; } else { // What is this? return false; } return QuestLogRequest.isHippyStoreAvailable() || EquipmentManager.hasOutfit( outfit ); } else if ( storeId.equals( "knobdisp" ) ) { // The Knob Dispensary return KoLCharacter.getDispensaryOpen(); } else if ( storeId.equals( "jewelers" ) ) { // Little Canadia Jewelers return !KoLCharacter.inZombiecore() && KoLCharacter.canadiaAvailable(); } else if ( storeId.equals( "generalstore" ) ) { // General Store if ( KoLCharacter.inNuclearAutumn() ) { return false; } // Some items restricted, often because of holidays String holiday = HolidayDatabase.getHoliday(); switch ( itemId ) { case ItemPool.MARSHMALLOW: return holiday.contains( "Yuletide" ); case ItemPool.OYSTER_BASKET: return holiday.contains( "Oyster Egg Day" ); case ItemPool.PARTY_HAT: return holiday.contains( "Festival of Jarlsberg" ); case ItemPool.M282: case ItemPool.SNAKE: case ItemPool.SPARKLER: return holiday.contains( "Dependence Day" ); case ItemPool.FOAM_NOODLE: case ItemPool.INFLATABLE_DUCK: case ItemPool.WATER_WINGS: return holiday.contains( "Generic Summer Holiday" ); case ItemPool.DESERT_BUS_PASS: return !KoLCharacter.desertBeachAccessible(); case ItemPool.FOLDER_01: case ItemPool.FOLDER_02: case ItemPool.FOLDER_03: { AdventureResult folderHolder = ItemPool.get( ItemPool.FOLDER_HOLDER ); return folderHolder.getCount( KoLConstants.inventory ) > 0 || folderHolder.getCount( KoLConstants.closet ) > 0 || folderHolder.getCount( KoLConstants.collection ) > 0 || KoLCharacter.hasEquipped( folderHolder ); } case ItemPool.WATER_WINGS_FOR_BABIES: case ItemPool.MINI_LIFE_PRESERVER: case ItemPool.HEAVY_DUTY_UMBRELLA: case ItemPool.POOL_SKIMMER: return KoLCharacter.inRaincore(); case ItemPool.FISHING_LINE: return InventoryManager.hasItem( ItemPool.FISHING_POLE ); case ItemPool.TRICK_TOT_UNICORN: case ItemPool.TRICK_TOT_CANDY: return KoLCharacter.findFamiliar( FamiliarPool.TRICK_TOT ) != null; } } else if ( storeId.equals( "town_giftshop.php" ) ) { // Gift Shop // Some items restricted, because of holidays or number of ascensions String holiday = HolidayDatabase.getHoliday(); int asc = KoLCharacter.getAscensions(); switch ( itemId ) { case ItemPool.VALENTINE: case ItemPool.CHOCOLATE_COVERED_DIAMOND_STUDDED_ROSES: case ItemPool.BOUQUET_OF_CIRCULAR_SAW_BLADES: case ItemPool.BETTER_THAN_CUDDLING_CAKE: case ItemPool.STUFFED_NINJA_SNOWMAN: return holiday.contains( "Valentine's Day" ); case ItemPool.POTTED_FERN: case ItemPool.HAPPY_BIRTHDAY_CLAUDE_CAKE: return asc >= 1; case ItemPool.STUFFED_GHUOL_WHELP: case ItemPool.HEART_SHAPED_BALLOON: return asc >= 2; case ItemPool.TULIP: case ItemPool.PERSONALIZED_BIRTHDAY_CAKE: return asc >= 4; case ItemPool.STUFFED_ZMOBIE: case ItemPool.ANNIVERSARY_BALLOON: return asc >= 5; case ItemPool.VENUS_FLYTRAP: case ItemPool.THREE_TIERED_WEDDING_CAKE: return asc >= 7; case ItemPool.RAGGEDY_HIPPY_DOLL: case ItemPool.MYLAR_BALLOON: return asc >= 8; case ItemPool.ALL_PURPOSE_FLOWER: case ItemPool.BABYCAKES: return asc >= 10; case ItemPool.STUFFED_STAB_BAT: case ItemPool.KEVLAR_BALLOON: return asc >= 11; case ItemPool.EXOTIC_ORCHID: case ItemPool.BLUE_VELVET_CAKE: return asc >= 13; case ItemPool.APATHETIC_LIZARDMAN_DOLL: case ItemPool.THOUGHT_BALLOON: return asc >= 14; case ItemPool.LONG_STEMMED_ROSE: case ItemPool.CONGRATULATORY_CAKE: return asc >= 16; case ItemPool.STUFFED_YETI: case ItemPool.RAT_BALLOON: return asc >= 17; case ItemPool.GILDED_LILY: case ItemPool.ANGEL_FOOD_CAKE: return asc >= 19; case ItemPool.STUFFED_MOB_PENGUIN: case ItemPool.MINI_ZEPPELIN: return asc >= 20; case ItemPool.DEADLY_NIGHTSHADE: case ItemPool.DEVILS_FOOD_CAKE: return asc >= 22; case ItemPool.STUFFED_SABRE_TOOTHED_LIME: case ItemPool.MR_BALLOON: return asc >= 23; case ItemPool.BLACK_LOTUS: case ItemPool.BIRTHDAY_PARTY_JELLYBEAN_CHEESECAKE: return asc >= 25; case ItemPool.GIANT_STUFFED_BUGBEAR: case ItemPool.RED_BALLOON: return asc >= 26; } } else if ( storeId.equals( "gnomart" ) ) { // Gno-Mart return !KoLCharacter.inZombiecore() && KoLCharacter.gnomadsAvailable(); } else if ( storeId.equals( "mayoclinic" ) ) { // The Mayo Clinic boolean available = false; AdventureResult workshedItem = CampgroundRequest.getCurrentWorkshedItem(); if ( workshedItem != null ) { available = workshedItem.getItemId() == ItemPool.MAYO_CLINIC && StandardRequest.isAllowed( "Items", "portable Mayo Clinic" ); if ( itemId == ItemPool.MIRACLE_WHIP ) { return available && !Preferences.getBoolean( "_mayoDeviceRented" ) && !Preferences.getBoolean( "itemBoughtPerAscension8266" ); } if ( itemId == ItemPool.SPHYGMAYOMANOMETER || itemId == ItemPool.REFLEX_HAMMER || itemId == ItemPool.MAYO_LANCE ) { return available && !Preferences.getBoolean( "_mayoDeviceRented" ); } } return available; } else if ( storeId.equals( "unclep" ) ) { // Uncle P's Antiques return !KoLCharacter.inZombiecore() && !KoLCharacter.inNuclearAutumn() && KoLCharacter.desertBeachAccessible(); } else if ( storeId.equals( "bartlebys" ) ) { boolean available = shopName.equals( NPCStoreDatabase.getStoreName( storeId ) ); if ( !available ) { return false; } if ( itemId == ItemPool.ABRIDGED && ( InventoryManager.hasItem( ItemPool.ABRIDGED ) || InventoryManager.hasItem( ItemPool.DICTIONARY ) || QuestDatabase.isQuestFinished( Quest.LOL ) ) ) { return false; } String itemName = ItemDatabase.getItemName( itemId ); if ( Preferences.getInteger( "lastPirateEphemeraReset" ) == KoLCharacter.getAscensions() && !Preferences.getString( "lastPirateEphemera" ).equals( itemName ) ) { if ( NPCPurchaseRequest.PIRATE_EPHEMERA_PATTERN.matcher( itemName ).matches() ) { return false; } } return EquipmentManager.hasOutfit( OutfitPool.SWASHBUCKLING_GETUP ) || InventoryManager.hasItem( ItemPool.PIRATE_FLEDGES ); } else if ( storeId.equals( "meatsmith" ) ) { // Meatsmith's Shop return !KoLCharacter.inZombiecore() && !KoLCharacter.inNuclearAutumn(); } else if ( storeId.equals( "whitecitadel" ) ) { return QuestLogRequest.isWhiteCitadelAvailable(); } else if ( storeId.equals( "nerve" ) ) { // Nervewrecker's Store return KoLCharacter.inBadMoon(); } else if ( storeId.equals( "armory" ) ) { if ( KoLCharacter.inZombiecore() || KoLCharacter.inNuclearAutumn() ) { return false; } // Armory and Leggery if ( itemId == ItemPool.FISHING_HAT ) { return InventoryManager.hasItem( ItemPool.FISHING_POLE ); } } else if ( storeId.equals( "fdkol" ) ) { return false; } else if ( shopName.equals( "Gift Shop" ) ) { return !KoLCharacter.inBadMoon(); } else if ( storeId.equals( "hiddentavern" ) ) { return Preferences.getInteger( "hiddenTavernUnlock" ) == KoLCharacter.getAscensions(); } else if ( storeId.equals( "doc" ) ) { if ( KoLCharacter.inZombiecore() || KoLCharacter.inNuclearAutumn() ) { return false; } if ( itemId == ItemPool.DOC_VITALITY_SERUM ) { return QuestDatabase.isQuestFinished( Quest.DOC ); } } else if ( storeId.equals( "mystic" ) ) { if ( itemId == ItemPool.YELLOW_SUBMARINE ) { return !KoLCharacter.desertBeachAccessible(); } else if ( itemId == ItemPool.DIGITAL_KEY ) { return !InventoryManager.hasItem( ItemPool.DIGITAL_KEY ); } } else if ( storeId.equals( "vault1" ) ) { // Fallout Shelter Medical Supply if ( !KoLCharacter.inNuclearAutumn() || Preferences.getInteger( "falloutShelterLevel" ) < 2 ) { return false; } if ( itemId == ItemPool.TRICK_TOT_CANDY || itemId == ItemPool.TRICK_TOT_EYEBALL ) { return KoLCharacter.findFamiliar( FamiliarPool.TRICK_TOT ) != null; } } else if ( storeId.equals( "vault2" ) ) { if ( !KoLCharacter.inNuclearAutumn() || Preferences.getInteger( "falloutShelterLevel" ) < 4 ) { return false; } if ( itemId == ItemPool.TRICK_TOT_KNIGHT || itemId == ItemPool.TRICK_TOT_ROBOT ) { return KoLCharacter.findFamiliar( FamiliarPool.TRICK_TOT ) != null; } } else if ( storeId.equals( "vault3" ) ) { if ( !KoLCharacter.inNuclearAutumn() || Preferences.getInteger( "falloutShelterLevel" ) < 7 ) { return false; } if ( itemId == ItemPool.TRICK_TOT_LIBERTY || itemId == ItemPool.TRICK_TOT_UNICORN ) { return KoLCharacter.findFamiliar( FamiliarPool.TRICK_TOT ) != null; } } // If it gets this far, then the item is definitely available // for purchase from the NPC store. return true; } public static final int itemIdByRow( final String shopId, final int row ) { List<NPCPurchaseRequest> items = NPCStoreDatabase.ROW_ITEMS.get( row ); if ( items == null ) { return -1; } for ( NPCPurchaseRequest item : items ) { if ( shopId.equals( item.getStoreId() ) ) { return item.getItemId(); } } return -1; } public static final boolean contains( final int itemId ) { return NPCStoreDatabase.contains( itemId, true ); } public static final int price( final int itemId ) { PurchaseRequest request = NPCStoreDatabase.getPurchaseRequest( itemId ); return request == null ? 0 : request.getPrice(); } public static final int availablePrice( final int itemId ) { PurchaseRequest request = NPCStoreDatabase.getPurchaseRequest( itemId ); return request == null || !request.canPurchase() ? 0 : request.getPrice(); } public static final boolean contains( final int itemId, boolean validate ) { PurchaseRequest item = NPCStoreDatabase.getPurchaseRequest( itemId ); return item != null && ( !validate || item.canPurchaseIgnoringMeat() ); } }