/** * 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.webui; import java.util.ArrayList; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.java.dev.spellcast.utilities.SortedListModel; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLConstants.CraftingRequirements; import net.sourceforge.kolmafia.KoLConstants.CraftingType; import net.sourceforge.kolmafia.Modifiers; import net.sourceforge.kolmafia.Speculation; import net.sourceforge.kolmafia.objectpool.EffectPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.persistence.ConcoctionDatabase; import net.sourceforge.kolmafia.persistence.ConsumablesDatabase; import net.sourceforge.kolmafia.persistence.EffectDatabase; import net.sourceforge.kolmafia.persistence.EquipmentDatabase; import net.sourceforge.kolmafia.persistence.ItemDatabase; import net.sourceforge.kolmafia.persistence.QuestDatabase; import net.sourceforge.kolmafia.persistence.QuestDatabase.Quest; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.CreateItemRequest; import net.sourceforge.kolmafia.request.EquipmentRequest; import net.sourceforge.kolmafia.request.FightRequest; import net.sourceforge.kolmafia.request.GenericRequest; import net.sourceforge.kolmafia.request.IslandRequest; import net.sourceforge.kolmafia.request.OrcChasmRequest; import net.sourceforge.kolmafia.request.UseItemRequest; import net.sourceforge.kolmafia.session.ChoiceManager; import net.sourceforge.kolmafia.session.EquipmentManager; import net.sourceforge.kolmafia.session.InventoryManager; import net.sourceforge.kolmafia.session.Limitmode; import net.sourceforge.kolmafia.session.ResultProcessor; import net.sourceforge.kolmafia.session.TurnCounter; import net.sourceforge.kolmafia.textui.command.SpeculateCommand; import net.sourceforge.kolmafia.utilities.StringUtilities; public abstract class UseLinkDecorator { private static final StringBuffer deferred = new StringBuffer(); public static final void decorate( final String location, final StringBuffer buffer ) { // You ain't doin' nothin' in Valhalla if ( location.startsWith( "afterlife.php" ) ) { return; } // If you are Ed in the Underworld, you have to wait until you // are on the surface again before you use anything String limitMode = KoLCharacter.getLimitmode(); if ( limitMode == Limitmode.ED ) { return; } // You CAN buy from the mall in Hardcore or Ronin, but any // results go into Hagnk's storage. if ( ( location.startsWith( "mallstore.php" ) || location.startsWith( "backoffice.php" ) ) && !KoLCharacter.canInteract() ) { return; } boolean inCombat = location.startsWith( "fight.php" ); boolean inChoice = location.startsWith( "choice.php" ); if ( !inCombat && !inChoice && buffer.indexOf( "You acquire" ) == -1 && buffer.indexOf( "O hai, I made dis" ) == -1 ) { return; } // Defer use links until later if this isn't the final combat/choice page String macro = FightRequest.lastMacroUsed; boolean usedNativeMacro = macro != null && !macro.equals( "" ) && !macro.equals( "0" ); boolean usedMafiaMacro = location.contains( "action=done" ); boolean usedMacro = inCombat && ( usedNativeMacro || usedMafiaMacro ); // Some combats lead to a non-optional choice boolean duringCombat = inCombat && ( FightRequest.getCurrentRound() != 0 || buffer.indexOf( "choice.php" ) != -1 ); // Some choices lead to a non-optional choice boolean duringChoice = inChoice && buffer.indexOf( "choice.php" ) != -1 && !ChoiceManager.canWalkAway(); // If we are currently in a combat or choice, we should consider deferring boolean deferrable = inCombat || inChoice; // If we are forced to continue to be in a combat or choice, continue deferring boolean deferring = duringCombat || duringChoice; String text = buffer.toString(); buffer.setLength( 0 ); boolean poetry = inCombat && ( FightRequest.haiku || FightRequest.anapest ); if ( poetry ) { UseLinkDecorator.addPoeticUseLinks( location, text, buffer, deferring ); } else { UseLinkDecorator.addNormalUseLinks( location, text, buffer, deferring, usedMacro ); } if ( inCombat ) { StringUtilities.singleStringReplace( buffer, "A sticker falls off your weapon, faded and torn.", "A sticker falls off your weapon, faded and torn. <font size=1>" + "[<a href=\"bedazzle.php\">bedazzle</a>]</font>" ); } if ( deferring ) { // discard all changes, the links aren't usable yet buffer.setLength( 0 ); buffer.append( text ); } else if ( deferrable ) { int pos = buffer.lastIndexOf( "</table>" ); if ( pos == -1 ) { return; } text = buffer.substring( pos ); buffer.setLength( pos ); if ( inCombat ) { buffer.append( "</table><table><tr><td>" ); if ( usedNativeMacro ) { buffer.append( "<form method=POST action=\"account_combatmacros.php\"><input type=HIDDEN name=action value=edit><input type=HIDDEN name=macroid value=\"" ); buffer.append( macro ); buffer.append( "\"><input type=SUBMIT value=\"Edit last macro\"></form>" ); FightRequest.lastMacroUsed = ""; } else { buffer.append( "[<a href=\"/account_combatmacros.php\">edit macros</a>]" ); } buffer.append( "</td></tr>" ); } if ( UseLinkDecorator.deferred.length() > 0 ) { String tag = inCombat ? "Found in this fight" : "Previously seen"; buffer.append( "</table><table><tr><td colspan=2>" ); buffer.append( tag ); buffer.append( ":</td></tr>" ); buffer.append( UseLinkDecorator.deferred ); UseLinkDecorator.deferred.setLength( 0 ); } buffer.append( text ); } } // <table class="item" style="float: none" rel="id=2334&s=0&q=1&d=0&g=0&t=0&n=1&m=0&p=0&u=."><tr><td><img src="http://images.kingdomofloathing.com/itemimages/macguffin.gif" alt="Holy MacGuffin" title="Holy MacGuffin" class=hand onClick='descitem(302128482)'></td><td valign=center class=effect>You acquire an item: <b>Holy MacGuffin</b></td></tr></table> private static final Pattern ACQUIRE_PATTERN = Pattern.compile( "(You acquire|O hai, I made dis)([^<]*?)<b>(.*?)</b>(.*?)</td>", Pattern.DOTALL ); private static final Pattern BOUNTY_COUNT_PATTERN = Pattern.compile( "\\((\\d+) of (\\d+) found\\)" ); private static final void addNormalUseLinks( String location, String text, StringBuffer buffer, boolean deferring, boolean usedMacro ) { // Get a list of of items via "rel" strings LinkedList<AdventureResult> items = ResultProcessor.parseItems( text ); // Get a list of effects via descid LinkedList<AdventureResult> effects = ResultProcessor.parseEffects( text ); Matcher useLinkMatcher = ACQUIRE_PATTERN.matcher( text ); int specialLinkId = 0; String specialLinkText = null; while ( useLinkMatcher.find() ) { // See if it's an effect if ( UseLinkDecorator.addEffectLink( location, useLinkMatcher, buffer, effects ) ) { continue; } // Get type of acquisition String type = useLinkMatcher.group( 2 ); int pos = buffer.length(); boolean link = false; String comment = useLinkMatcher.group( 4 ); if ( comment.contains( "Hagnk" ) || comment.contains( "automatically equipped" ) ) { // If the item ended up in Hagnk's storage or // was automatically equipped, no use link. continue; } // Special handling for bounty items if ( type.contains( "bounty item" ) ) { // Add link for visiting bounty hunter if the last bounty drops Matcher matcher = UseLinkDecorator.BOUNTY_COUNT_PATTERN.matcher( text ); if ( matcher.find() ) { String bountyCount = matcher.group( 1 ); String bountyTotal = matcher.group( 2 ); if ( bountyCount.equals( bountyTotal ) ) { UseLink useLink = new UseLink( "return to hunter", "bounty.php" ); useLinkMatcher.appendReplacement( buffer, "$1$2<b>$3</b> "+ useLink.getItemHTML() + "$4" ); link = true; } } } else { int itemId = -1; int itemCount = 1; String itemName = useLinkMatcher.group( 3 ); if ( itemName.equals( "Tales of the West: Cow Punching" ) ) { // Accommodate KoL bug: extra space in name itemName = "Tales of the West: Cow Punching"; } int spaceIndex = itemName.indexOf( " " ); if ( spaceIndex != -1 && !type.contains( ":" ) ) { itemCount = StringUtilities.parseInt( itemName.substring( 0, spaceIndex ) ); itemName = itemName.substring( spaceIndex + 1 ); } AdventureResult item = items.size() == 0 ? null : items.getFirst(); if ( item != null && itemName.equals( item.getName() ) ) { items.removeFirst(); itemId = item.getItemId(); itemCount = item.getCount(); } else { itemId = ItemDatabase.getItemId( itemName, itemCount, itemCount > 1 ); } if ( itemId == -1 ) { continue; } // Certain items get use special links to minimize the // amount of scrolling to find the item again. if ( location.startsWith( "inventory.php" ) || ( location.startsWith( "inv_use.php" ) && location.contains( "ajax=1" ) ) ) { switch ( itemId ) { case ItemPool.FOIL_BOW: case ItemPool.FOIL_RADAR: case ItemPool.FOIL_CAT_EARS: specialLinkId = itemId; specialLinkText = "fold"; break; } } // Possibly append a use link link = UseLinkDecorator.addUseLink( itemId, itemCount, location, useLinkMatcher, text, buffer ); } // If we added no link, copy in the text verbatim if ( !link ) { useLinkMatcher.appendReplacement( buffer, "$1$2<b>$3</b>$4</td>" ); } // If we are currently deferring use links or have // already deferred some during this combat, append to // list of items previously seen. // // During macro execution, append everything found, so // they accumulate at the bottom of the screen. if ( usedMacro || deferring || UseLinkDecorator.deferred.length() > 0 ) { // Find where the replacement was appended String match = useLinkMatcher.group( 1 ) + useLinkMatcher.group( 2 ) + "<b>" + useLinkMatcher.group( 3 ); pos = buffer.indexOf( match, pos ); if ( pos == -1 ) { continue; } // Find start of table containing it pos = buffer.lastIndexOf( "<table", pos ); if ( pos == -1 ) { continue; } UseLinkDecorator.deferred.append( "<tr>" ); UseLinkDecorator.deferred.append( buffer.substring( pos ) ); UseLinkDecorator.deferred.append( "</table>" ); UseLinkDecorator.deferred.append( "</tr>" ); } } useLinkMatcher.appendTail( buffer ); if ( !deferring && specialLinkText != null ) { StringUtilities.singleStringReplace( buffer, "</center></blockquote>", "<p><center><a href=\"inv_use.php?pwd=" + GenericRequest.passwordHash + "&which=2&whichitem=" + specialLinkId + "\">[" + specialLinkText + " it again]</a></center></blockquote>" ); } } // <img src="http://images.kingdomofloathing.com/itemimages/jerkcicle.gif" alt="dangerous jerkcicle" title="dangerous jerkcicle" class=hand onClick='descitem(726861308)'> private static final Pattern POETIC_ACQUIRE_PATTERN = Pattern.compile( "<img[^>]*?descitem\\((\\d+)\\)'>", Pattern.DOTALL ); private static final void addPoeticUseLinks( String location, String text, StringBuffer buffer, boolean deferring ) { Matcher useLinkMatcher = POETIC_ACQUIRE_PATTERN.matcher( text ); while ( useLinkMatcher.find() ) { String descId = useLinkMatcher.group( 1 ); String itemName = ItemDatabase.getItemName( descId ); int itemId = ItemDatabase.getItemIdFromDescription( descId ); if ( itemId == -1 ) { continue; } UseLinkDecorator.deferred.append( "<tr><td>" ); UseLinkDecorator.deferred.append( useLinkMatcher.group( 0 ) ); UseLinkDecorator.deferred.append( "</td><td>You acquire an item: <b>" ); UseLinkDecorator.deferred.append( itemName ); UseLinkDecorator.deferred.append( "</b>" ); UseLink link = UseLinkDecorator.generateUseLink( itemId, 1, location, text ); if ( link != null ) { UseLinkDecorator.deferred.append( " " ); UseLinkDecorator.deferred.append( link.getItemHTML() ); } UseLinkDecorator.deferred.append( "</td></tr>" ); } // Copy the text unchanged into the buffer buffer.append( text ); } private static final CraftingType shouldAddCreateLink( int itemId, String location ) { if ( location == null || ( location.contains( "craft.php" ) && !location.contains( "pulverize" ) ) ) { return CraftingType.NOCREATE; } // Retrieve the known ingredient uses for the item. SortedListModel creations = ConcoctionDatabase.getKnownUses( itemId ); if ( creations.isEmpty() ) { return CraftingType.NOCREATE; } // Skip items which are multi-use. int consumeMethod = ItemDatabase.getConsumptionType( itemId ); if ( consumeMethod == KoLConstants.CONSUME_MULTIPLE ) { return CraftingType.NOCREATE; } switch ( itemId ) { // If you find the wooden stakes, you want to equip them case ItemPool.WOODEN_STAKES: return CraftingType.NOCREATE; // If you find goat cheese, let the trapper link handle it. case ItemPool.GOAT_CHEESE: return CraftingType.NOCREATE; // If you find ore, let the trapper link handle it. case ItemPool.LINOLEUM_ORE: case ItemPool.ASBESTOS_ORE: case ItemPool.CHROME_ORE: return CraftingType.NOCREATE; // Dictionaries and bridges should link to the chasm quest. case ItemPool.DICTIONARY: case ItemPool.BRIDGE: return CraftingType.NOCREATE; // The eyepatch can be combined, but is usually an outfit piece // The dreadsack can be combined, but is usually an outfit piece // The frilly skirt is usually used for the frathouse blueprints case ItemPool.EYEPATCH: case ItemPool.DREADSACK: case ItemPool.FRILLY_SKIRT: return CraftingType.NOCREATE; // Spooky Fertilizer CAN be cooked, but almost always is used // for with the spooky temple map. case ItemPool.SPOOKY_FERTILIZER: return CraftingType.NOCREATE; // Enchanted beans are primarily used for the beanstalk quest. case ItemPool.ENCHANTED_BEAN: if ( KoLCharacter.getLevel() >= 10 && !InventoryManager.hasItem( ItemPool.SOCK ) ) { return CraftingType.NOCREATE; } break; } for ( int i = 0; i < creations.size(); ++i ) { AdventureResult creation = (AdventureResult) creations.get( i ); CraftingType mixingMethod = ConcoctionDatabase.getMixingMethod( creation ); EnumSet<CraftingRequirements> requirements = ConcoctionDatabase.getRequirements( creation.getItemId() ); if ( !ConcoctionDatabase.isPermittedMethod( mixingMethod, requirements ) ) { continue; } // Only accept if it's a creation method that the // editor kit currently understands and links. switch ( mixingMethod ) { case COMBINE: case ACOMBINE: case MIX: case MIX_FANCY: case COOK: case COOK_FANCY: case JEWELRY: break; default: continue; } CreateItemRequest irequest = CreateItemRequest.getInstance( creation ); if ( irequest != null && irequest.getQuantityPossible() > 0 ) { return mixingMethod; } } return CraftingType.NOCREATE; } private static final boolean addEffectLink( String location, Matcher useLinkMatcher, StringBuffer buffer, LinkedList<AdventureResult> effects ) { String message = useLinkMatcher.group(0); if ( !message.contains( "You acquire an effect" ) ) { return false; } String effectName = useLinkMatcher.group(3); AdventureResult effect = effects.size() == 0 ? null : effects.getFirst(); if ( effect != null && effectName.equals( effect.getName() ) ) { effects.removeFirst(); } else { effect = EffectPool.get( EffectDatabase.getEffectId( effectName ), 1 ); } UseLink link = null; switch ( effect.getEffectId() ) { case EffectPool.FILTHWORM_LARVA_STENCH: link = new UseLink( 0, "feeding chamber", "adventure.php?snarfblat=128" ); break; case EffectPool.FILTHWORM_DRONE_STENCH: link = new UseLink( 0, "guards' chamber", "adventure.php?snarfblat=129" ); break; case EffectPool.FILTHWORM_GUARD_STENCH: link = new UseLink( 0, "queen's chamber", "adventure.php?snarfblat=130" ); break; case EffectPool.KNOB_GOBLIN_PERFUME: link = new UseLink( 0, "throne room", "cobbsknob.php?action=throneroom" ); break; case EffectPool.DOWN_THE_RABBIT_HOLE: link = new UseLink( 0, "rabbit hole", "place.php?whichplace=rabbithole" ); break; case EffectPool.TRANSPONDENT: link = new UseLink( 0, "spaaace", "spaaace.php?arrive=1" ); break; case EffectPool.STONE_FACED: link = new UseLink( 0, "hidden temple", "adventure.php?snarfblat=280" ); break; case EffectPool.DIS_ABLED: link = new UseLink( 0, "portal to dis", "suburbandis.php" ); break; default: // There are several effect names which are also items. // We know that we're dealing with an effect here, so there's // no point in also checking for an item match. return true; } String useType = link.getUseType(); String useLocation = link.getUseLocation(); useLinkMatcher.appendReplacement( buffer, "$1$2<b>$3</b> <font size=1>[<a href=\"" + useLocation + "\">" + useType + "</a>]</font></td>" + "$4" ); return true; } private static final boolean addUseLink( int itemId, int itemCount, String location, Matcher useLinkMatcher, String text, StringBuffer buffer ) { UseLink link = UseLinkDecorator.generateUseLink( itemId, itemCount, location, text ); if ( link == null ) { return false; } useLinkMatcher.appendReplacement( buffer, "$1$2<b>$3</b> "+ link.getItemHTML() + "$4" ); buffer.append( "</td>" ); return true; } private static final UseLink generateUseLink( int itemId, int itemCount, String location, String text ) { int consumeMethod = ItemDatabase.getConsumptionType( itemId ); CraftingType mixingMethod = shouldAddCreateLink( itemId, location ); if ( mixingMethod != CraftingType.NOCREATE ) { return getCreateLink( itemId, itemCount, mixingMethod ); } if ( consumeMethod == KoLConstants.NO_CONSUME ) { return getNavigationLink( itemId, location ); } return getUseLink( itemId, itemCount, location, consumeMethod, text ); } private static final UseLink getCreateLink( final int itemId, final int itemCount, final CraftingType mixingMethod ) { switch ( mixingMethod ) { case COMBINE: case ACOMBINE: case JEWELRY: return new UseLink( itemId, itemCount, "combine", "craft.php?mode=combine&a=" ); case MIX: case MIX_FANCY: return new UseLink( itemId, itemCount, "mix", "craft.php?mode=cocktail&a=" ); case COOK: case COOK_FANCY: return new UseLink( itemId, itemCount, "cook", "craft.php?mode=cook&a=" ); } return null; } private static final UseLink getUseLink( int itemId, int itemCount, String location, int consumeMethod, final String text ) { if ( !ConsumablesDatabase.meetsLevelRequirement( ItemDatabase.getItemName( itemId ) ) ) { return null; } boolean combatResults = location.startsWith( "fight.php" ); switch ( consumeMethod ) { case KoLConstants.GROW_FAMILIAR: if ( itemId == ItemPool.MOSQUITO_LARVA ) { if ( KoLCharacter.isEd() ) { return new UseLink( itemId, "Amun", "council.php" ); } else { return new UseLink( itemId, "council", "council.php" ); } } if ( KoLCharacter.isPicky() ) { return null; } if ( KoLCharacter.inBeecore() && ItemDatabase.unusableInBeecore( itemId ) ) { return null; } return new UseLink( itemId, "grow", "inv_familiar.php?whichitem=" ); case KoLConstants.CONSUME_EAT: switch ( itemId ) { case ItemPool.GOAT_CHEESE: return new UseLink( itemId, InventoryManager.getCount( itemId ), "place.php?whichplace=mclargehuge&action=trappercabin" ); case ItemPool.FORTUNE_COOKIE: { ArrayList<UseLink> uses = new ArrayList<UseLink>(); if ( KoLCharacter.canEat() ) { uses.add( new UseLink( itemId, itemCount, "eat", "inv_eat.php?which=1&whichitem=" ) ); } uses.add( new UseLink( itemId, itemCount, "smash", "inv_use.php?which=1&whichitem=" ) ); if ( uses.size() == 1 ) { return uses.get( 0 ); } return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } case ItemPool.HOT_WING: { ArrayList<UseLink> uses = new ArrayList<UseLink>(); if ( KoLCharacter.canEat() ) { uses.add( new UseLink( itemId, itemCount, "eat", "inv_eat.php?which=1&whichitem=" ) ); } uses.add( new UseLink( itemId, InventoryManager.getCount( itemId ) ) ); if ( uses.size() == 1 ) { return uses.get( 0 ); } return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } case ItemPool.SNOW_BERRIES: case ItemPool.ICE_HARVEST: { ArrayList<UseLink> uses = new ArrayList<UseLink>(); if ( KoLCharacter.canEat() ) { uses.add( new UseLink( itemId, itemCount, "eat", "inv_eat.php?which=1&whichitem=" ) ); } uses.add( new UseLink( itemId, 1, "make stuff", "shop.php?whichshop=snowgarden" ) ); if ( uses.size() == 1 ) { return uses.get( 0 ); } return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } } if ( !KoLCharacter.canEat() ) { return null; } if ( KoLCharacter.inBeecore() && ItemDatabase.unusableInBeecore( itemId ) ) { return null; } if ( KoLCharacter.inNuclearAutumn() && ConsumablesDatabase.getFullness( ItemDatabase.getCanonicalName( itemId ) ) > 1 ) { return null; } if ( itemId == ItemPool.BLACK_PUDDING ) { return new UseLink( itemId, itemCount, "eat", "inv_eat.php?which=1&whichitem=", false ); } return new UseLink( itemId, itemCount, "eat", "inv_eat.php?which=1&whichitem=" ); case KoLConstants.CONSUME_DRINK: if ( !KoLCharacter.canDrink() ) { return null; } if ( KoLCharacter.inBeecore() && ItemDatabase.unusableInBeecore( itemId ) ) { return null; } if ( KoLCharacter.inNuclearAutumn() && ConsumablesDatabase.getInebriety( ItemDatabase.getCanonicalName( itemId ) ) > 1 ) { return null; } return new UseLink( itemId, itemCount, "drink", "inv_booze.php?which=1&whichitem=" ); case KoLConstants.CONSUME_FOOD_HELPER: if ( !KoLCharacter.canEat() ) { return null; } return new UseLink( itemId, 1, "eat with", "inv_use.php?which=1&whichitem=" ); case KoLConstants.CONSUME_DRINK_HELPER: if ( !KoLCharacter.canDrink() ) { return null; } return new UseLink( itemId, 1, "drink with", "inv_use.php?which=1&whichitem=" ); case KoLConstants.CONSUME_MULTIPLE: case KoLConstants.CONSUME_AVATAR: { int count = InventoryManager.getCount( itemId ); int useCount = Math.min( UseItemRequest.maximumUses( itemId ), count ); // If we are limited to 0 uses, no use link needed if ( useCount == 0 ) { return null; } if ( KoLCharacter.inBeecore() && ItemDatabase.unusableInBeecore( itemId ) ) { return null; } switch ( itemId ) { case ItemPool.RUSTY_HEDGE_TRIMMERS: // Not inline, since the redirection to a fight // doesn't work ajaxified. return new UseLink( itemId, 1, "use", "inv_use.php?which=3&whichitem=", false ); case ItemPool.DANCE_CARD: // No use link for a dance card if one is already active or another will expire in 3 turns. if ( TurnCounter.isCounting( "Dance Card" ) || TurnCounter.getCounters( "", 3, 3 ).length() > 0 ) { return null; } break; } if ( useCount == 1 ) { String page = ( consumeMethod == KoLConstants.CONSUME_MULTIPLE ) ? "3" : "1"; return new UseLink( itemId, useCount, getPotionSpeculation( "use", itemId ), "inv_use.php?which=" + page + "&whichitem=" ); } String use = getPotionSpeculation( "use multiple", itemId ); if ( Preferences.getBoolean( "relayUsesInlineLinks" ) ) { return new UseLink( itemId, useCount, use, "#" ); } return new UseLink( itemId, useCount, use, "multiuse.php?passitem=" ); } case KoLConstants.CONSUME_FOLDER: // Not inline, since the redirection to a choice // doesn't work ajaxified. return new UseLink( itemId, 1, "use", "inv_use.php?which=3&whichitem=", false ); case KoLConstants.CONSUME_SPLEEN: { int count = InventoryManager.getCount( itemId ); int useCount = Math.min( UseItemRequest.maximumUses( itemId ), count ); // If we are limited to 0 uses, no use link needed if ( useCount == 0 ) { return null; } if ( KoLCharacter.inBeecore() && ItemDatabase.unusableInBeecore( itemId ) ) { return null; } if ( KoLCharacter.inNuclearAutumn() && ConsumablesDatabase.getSpleenHit( ItemDatabase.getCanonicalName( itemId ) ) > 1 ) { return null; } return new UseLink( itemId, useCount, getPotionSpeculation( "chew", itemId ), "inv_spleen.php?whichitem=" ); } case KoLConstants.CONSUME_USE: case KoLConstants.MESSAGE_DISPLAY: case KoLConstants.INFINITE_USES: if ( KoLCharacter.inBeecore() && ItemDatabase.unusableInBeecore( itemId ) ) { return null; } switch ( itemId ) { case ItemPool.LOATHING_LEGION_KNIFE: case ItemPool.LOATHING_LEGION_TATTOO_NEEDLE: case ItemPool.LOATHING_LEGION_UNIVERSAL_SCREWDRIVER: { ArrayList<UseLink> uses = new ArrayList<UseLink>(); if ( itemId == ItemPool.LOATHING_LEGION_TATTOO_NEEDLE ) { uses.add( new UseLink( itemId, 1, "use", "inv_use.php?which=3&whichitem=" ) ); } else if ( itemId == ItemPool.LOATHING_LEGION_UNIVERSAL_SCREWDRIVER ) { uses.add( new UseLink( itemId, 1, "untinker", "inv_use.php?which=3&whichitem=" ) ); } uses.add( new UseLink( itemId, 1, "switch", "inv_use.php?which=3&switch=1&whichitem=" ) ); if ( uses.size() == 1 ) { return uses.get( 0 ); } return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } case ItemPool.MACGUFFIN_DIARY: case ItemPool.ED_DIARY: return new UseLink( itemId, 1, "read", "diary.php?textversion=1" ); case ItemPool.VOLCANO_MAP: return new UseLink( itemId, 1, "read", "volcanoisland.php?intro=1" ); case ItemPool.ENCHANTED_BEAN: return KoLCharacter.getLevel() < 10 ? null : new UseLink( itemId, "plant", "place.php?whichplace=plains&action=garbage_grounds" ); case ItemPool.SPOOKY_SAPLING: case ItemPool.SPOOKY_MAP: case ItemPool.SPOOKY_FERTILIZER: if ( !InventoryManager.hasItem( ItemPool.SPOOKY_MAP ) || !InventoryManager.hasItem( ItemPool.SPOOKY_SAPLING ) || !InventoryManager.hasItem( ItemPool.SPOOKY_FERTILIZER ) ) { return null; } return new UseLink( ItemPool.SPOOKY_MAP, 1, "map", "inv_use.php?which=3&whichitem=" ); case ItemPool.FRATHOUSE_BLUEPRINTS: case ItemPool.RONALD_SHELTER_MAP: case ItemPool.GRIMACE_SHELTER_MAP: case ItemPool.STAFF_GUIDE: case ItemPool.FUDGE_WAND: case ItemPool.REFLECTION_OF_MAP: case ItemPool.CSA_FIRE_STARTING_KIT: case ItemPool.CEO_OFFICE_CARD: case ItemPool.DREADSCROLL: case ItemPool.RUSTY_HEDGE_TRIMMERS: case ItemPool.WALKIE_TALKIE: case ItemPool.SUSPICIOUS_ADDRESS: case ItemPool.CHEF_BOY_BUSINESS_CARD: case ItemPool.GRIMSTONE_MASK: case ItemPool.THUNDER_THIGH: case ItemPool.AQUA_BRAIN: case ItemPool.LIGHTNING_MILK: case ItemPool.SNEAKY_WRAPPING_PAPER: case ItemPool.BARREL_MAP: case ItemPool.VYKEA_INSTRUCTIONS: case ItemPool.TONIC_DJINN: case ItemPool.COW_PUNCHING_TALES: case ItemPool.BEAN_SLINGING_TALES: case ItemPool.SNAKE_OILING_TALES: case ItemPool.MAYO_MINDER: case ItemPool.NO_SPOON: case ItemPool.TIME_SPINNER: case ItemPool.WAX_GLOB: case ItemPool.GUMMY_MEMORY: // Not inline, since the redirection to a choice // doesn't work ajaxified. case ItemPool.ABYSSAL_BATTLE_PLANS: case ItemPool.CARONCH_MAP: case ItemPool.CRUDE_SCULPTURE: case ItemPool.CURSED_PIECE_OF_THIRTEEN: case ItemPool.DOLPHIN_WHISTLE: case ItemPool.ENVYFISH_EGG: case ItemPool.ICE_SCULPTURE: case ItemPool.PHOTOCOPIED_MONSTER: case ItemPool.RAIN_DOH_MONSTER: case ItemPool.SHAKING_CAMERA: case ItemPool.SHAKING_CRAPPY_CAMERA: case ItemPool.SHAKING_SKULL: case ItemPool.SPOOKY_PUTTY_MONSTER: case ItemPool.WAX_BUGBEAR: case ItemPool.LYNYRD_SNARE: case ItemPool.WHITE_PAGE: case ItemPool.XIBLAXIAN_HOLOTRAINING_SIMCODE: case ItemPool.XIBLAXIAN_POLITICAL_PRISONER: case ItemPool.SCREENCAPPED_MONSTER: case ItemPool.TIME_RESIDUE: case ItemPool.MEME_GENERATOR: case ItemPool.MEGACOPIA: // Not inline, since the redirection to a fight // doesn't work ajaxified. case ItemPool.PLAINTIVE_TELEGRAM: // Not inline, since the redirection to an // adventure doesn't work ajaxified. return new UseLink( itemId, 1, "use", "inv_use.php?which=3&whichitem=", false ); case ItemPool.DRUM_MACHINE: if ( Preferences.getInteger( "desertExploration" ) == 100 ) { // This will redirect to a sandworm fight return new UseLink( itemId, 1, "use", "inv_use.php?which=3&whichitem=", false ); } if ( InventoryManager.getCount( ItemPool.WORM_RIDING_HOOKS ) > 0 ) { // This will explore the desert return new UseLink( itemId, 1, "wormride", "inv_use.php?which=3&whichitem=" ); } // *** what happens if you try to use a drum machine with no hooks? return null; case ItemPool.GONG: // No use link if already under influence. List active = KoLConstants.activeEffects; if ( active.contains( FightRequest.BIRDFORM ) || active.contains( FightRequest.MOLEFORM ) ) { return null; } // In-line use link does not work. return new UseLink( itemId, itemCount, "use", "inv_use.php?which=3&whichitem=", false ); case ItemPool.COBBS_KNOB_MAP: if ( !InventoryManager.hasItem( ItemPool.ENCRYPTION_KEY ) ) { return null; } return new UseLink( ItemPool.COBBS_KNOB_MAP, 1, "map", "inv_use.php?which=3&whichitem=" ); case ItemPool.DINGHY_PLANS: if ( InventoryManager.hasItem( ItemPool.DINGY_PLANKS ) ) { return new UseLink( itemId, 1, "use", "inv_use.php?which=3&whichitem=" ); } return new UseLink( ItemPool.DINGY_PLANKS, 1, "buy planks", "shop.php?&whichshop=generalstore&action=buyitem&quantity=1&whichrow=655", true ); case ItemPool.BARLEY: case ItemPool.HOPS: case ItemPool.FANCY_BEER_BOTTLE: case ItemPool.FANCY_BEER_LABEL: return new UseLink( itemId, 1, "Let's Brew", "shop.php?whichshop=beergarden" ); case ItemPool.WORSE_HOMES_GARDENS: return new UseLink( itemId, 1, "read", "shop.php?whichshop=junkmagazine" ); case ItemPool.ODD_SILVER_COIN: return new UseLink( itemId, 1, "spend", "shop.php?whichshop=cindy" ); case ItemPool.CASHEW: return new UseLink( itemId, 1, "trade", "shop.php?whichshop=thankshop" ); case ItemPool.LITTLE_FIRKIN: case ItemPool.NORMAL_BARREL: case ItemPool.BIG_TUN: case ItemPool.WEATHERED_BARREL: case ItemPool.DUSTY_BARREL: case ItemPool.DISINTEGRATING_BARREL: case ItemPool.MOIST_BARREL: case ItemPool.ROTTING_BARREL: case ItemPool.MOULDERING_BARREL: case ItemPool.BARNACLED_BARREL: ArrayList<UseLink> uses = new ArrayList<UseLink>(); uses.add( new UseLink( itemId, 1, "use", "inv_use.php?whichitem=" ) ); uses.add( new UseLink( itemId, 1, "smash party", "inv_use.php?choice=1&whichitem=", false ) ); return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); default: return new UseLink( itemId, itemCount, getPotionSpeculation( "use", itemId ), "inv_use.php?which=3&whichitem=" ); } case KoLConstants.CONSUME_GUARDIAN: if ( KoLCharacter.inBeecore() && ItemDatabase.unusableInBeecore( itemId ) ) { return null; } return new UseLink( itemId, 1, "use", "inv_use.php?which=3&whichitem=" ); case KoLConstants.EQUIP_HAT: case KoLConstants.EQUIP_WEAPON: case KoLConstants.CONSUME_SIXGUN: case KoLConstants.EQUIP_OFFHAND: case KoLConstants.EQUIP_SHIRT: case KoLConstants.EQUIP_PANTS: case KoLConstants.EQUIP_CONTAINER: case KoLConstants.EQUIP_ACCESSORY: case KoLConstants.EQUIP_FAMILIAR: switch ( itemId ) { case ItemPool.BATSKIN_BELT: case ItemPool.BONERDAGON_SKULL: // If we found it in a battle, take it to the // council to complete the quest. if ( combatResults ) { if ( KoLCharacter.isEd() ) { return new UseLink( itemId, "Amun", "council.php" ); } else { return new UseLink( itemId, "council", "council.php" ); } } break; case ItemPool.WORM_RIDING_HOOKS: // Can you even get the hooks if the desert is fully explored? if ( Preferences.getInteger( "desertExploration" ) == 100 ) { return null; } // If you have no drum machine yet, give a link to the Oasis if ( InventoryManager.getCount( ItemPool.DRUM_MACHINE ) == 0 ) { return new UseLink( 0, "oasis", "adventure.php?snarfblat=122" ); } return new UseLink( ItemPool.DRUM_MACHINE, 1, "wormride", "inv_use.php?which=3&whichitem=" ); case ItemPool.PIXEL_CHAIN_WHIP: case ItemPool.PIXEL_MORNING_STAR: // If we "acquire" the pixel whip upgrades in // the Chapel, they are autoequipped if ( location.startsWith( "adventure.php" ) ) { return null; } break; case ItemPool.SCALP_OF_GORGOLOK: case ItemPool.ELDER_TURTLE_SHELL: case ItemPool.COLANDER_OF_EMERIL: case ItemPool.ANCIENT_SAUCEHELM: case ItemPool.DISCO_FRO_PICK: case ItemPool.EL_SOMBRERO_DE_LOPEZ: { // If we "acquire" the Nemesis hat from // a fight, give a link to the guild to collect // the reward as well as "equip" link. UseLink equipLink = new UseLink( itemId, itemCount, getEquipmentSpeculation( "equip", itemId, -1 ), "inv_equip.php?which=2&action=equip&whichitem=" ); if ( combatResults ) { ArrayList<UseLink> uses = new ArrayList<UseLink>(); // scg = Same Class in Guild uses.add( new UseLink( itemId, "guild", "guild.php?place=scg" ) ); uses.add( equipLink ); return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } return equipLink; } case ItemPool.INFERNAL_SEAL_CLAW: case ItemPool.TURTLE_POACHER_GARTER: case ItemPool.SPAGHETTI_BANDOLIER: case ItemPool.SAUCEBLOB_BELT: case ItemPool.NEW_WAVE_BLING: case ItemPool.BELT_BUCKLE_OF_LOPEZ: // If we "acquire" the Nemesis accessories from // a fight, give a link to the guild to collect // the reward as well as "outfit" link. if ( combatResults ) { ArrayList<UseLink> uses = new ArrayList<UseLink>(); int outfit = EquipmentDatabase.getOutfitWithItem( itemId ); // scg = Same Class in Guild uses.add( new UseLink( itemId, "guild", "guild.php?place=scg" ) ); uses.add( new UseLink( itemId, itemCount, "outfit", "inv_equip.php?action=outfit&which=2&whichoutfit=" + outfit ) ); return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } break; case ItemPool.SPELUNKY_SPRING_BOOTS: case ItemPool.SPELUNKY_SPIKED_BOOTS: // Spelunky "accessories" need a single "equip" // link which goes to slot 1 return new UseLink( itemId, itemCount, "equip", "inv_equip.php?which=2&action=equip&slot=1&whichitem=" ); case ItemPool.HEIMZ_BEANS: case ItemPool.TESLA_BEANS: case ItemPool.MIXED_BEANS: case ItemPool.HELLFIRE_BEANS: case ItemPool.FRIGID_BEANS: case ItemPool.BLACKEST_EYED_PEAS: case ItemPool.STINKBEANS: case ItemPool.PORK_N_BEANS: case ItemPool.PREMIUM_BEANS: { UseLink equipLink = new UseLink( itemId, itemCount, getEquipmentSpeculation( "equip", itemId, -1 ), "inv_equip.php?which=2&action=equip&whichitem=" ); // inv_use.php?pwd&which=f-1&whichitem=xxx UseLink plateLink = new UseLink( itemId, itemCount, "plate", "inv_use.php?which=f-1&whichitem=" ); ArrayList<UseLink> uses = new ArrayList<UseLink>(); uses.add( equipLink ); uses.add( plateLink ); return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } case ItemPool.CODPIECE: { UseLink equipLink = new UseLink( itemId, itemCount, getEquipmentSpeculation( "equip", itemId, -1 ), "inv_equip.php?which=2&action=equip&whichitem=" ); // inv_use.php?pwd&which=f-1&whichitem=xxx UseLink wringOutLink = new UseLink( itemId, itemCount, "wring out", "inv_use.php?which=f-1&whichitem=" ); ArrayList<UseLink> uses = new ArrayList<UseLink>(); uses.add( equipLink ); uses.add( wringOutLink ); return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } case ItemPool.BASS_CLARINET: { UseLink equipLink = new UseLink( itemId, itemCount, getEquipmentSpeculation( "equip", itemId, -1 ), "inv_equip.php?which=2&action=equip&whichitem=" ); // inv_use.php?pwd&which=f-1&whichitem=xxx UseLink drainLink = new UseLink( itemId, itemCount, "drain spit", "inv_use.php?which=f-1&whichitem=" ); ArrayList<UseLink> uses = new ArrayList<UseLink>(); uses.add( equipLink ); uses.add( drainLink ); return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } case ItemPool.FISH_HATCHET: { UseLink equipLink = new UseLink( itemId, itemCount, getEquipmentSpeculation( "equip", itemId, -1 ), "inv_equip.php?which=2&action=equip&whichitem=" ); // inv_use.php?pwd&which=f-1&whichitem=xxx UseLink useLink = new UseLink( itemId, itemCount, "use", "inv_use.php?which=f-1&whichitem=" ); ArrayList<UseLink> uses = new ArrayList<UseLink>(); uses.add( equipLink ); uses.add( useLink ); return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } } // Don't offer an "equip" link for weapons or offhands // in Fistcore or Axecore if ( ( consumeMethod == KoLConstants.EQUIP_WEAPON || consumeMethod == KoLConstants.EQUIP_OFFHAND ) && ( KoLCharacter.inFistcore() || KoLCharacter.inAxecore() ) ) { return null; } int outfit = EquipmentDatabase.getOutfitWithItem( itemId ); ArrayList<UseLink> uses = new ArrayList<UseLink>(); if ( outfit != -1 && EquipmentManager.hasOutfit( outfit ) ) { uses.add( new UseLink( itemId, itemCount, "outfit", "inv_equip.php?action=outfit&which=2&whichoutfit=" + outfit ) ); } if ( consumeMethod == KoLConstants.EQUIP_ACCESSORY && !EquipmentManager.getEquipment( EquipmentManager.ACCESSORY1 ).equals( EquipmentRequest.UNEQUIP ) && !EquipmentManager.getEquipment( EquipmentManager.ACCESSORY2 ).equals( EquipmentRequest.UNEQUIP ) && !EquipmentManager.getEquipment( EquipmentManager.ACCESSORY3 ).equals( EquipmentRequest.UNEQUIP ) ) { uses.add( new UseLink( itemId, itemCount, getEquipmentSpeculation( "acc1", itemId, EquipmentManager.ACCESSORY1 ), "inv_equip.php?which=2&action=equip&slot=1&whichitem=" ) ); uses.add( new UseLink( itemId, itemCount, getEquipmentSpeculation( "acc2", itemId, EquipmentManager.ACCESSORY2 ), "inv_equip.php?which=2&action=equip&slot=2&whichitem=" ) ); uses.add( new UseLink( itemId, itemCount, getEquipmentSpeculation( "acc3", itemId, EquipmentManager.ACCESSORY3 ), "inv_equip.php?which=2&action=equip&slot=3&whichitem=" ) ); } else if ( consumeMethod == KoLConstants.CONSUME_SIXGUN ) { // Only as WOL class if ( KoLCharacter.getClassType() != KoLCharacter.COWPUNCHER && KoLCharacter.getClassType() != KoLCharacter.BEANSLINGER && KoLCharacter.getClassType() != KoLCharacter.SNAKE_OILER ) { return null; } uses.add( new UseLink( itemId, itemCount, getEquipmentSpeculation( "holster", itemId, -1 ), "inventory.php?which=2&action=holster&whichitem=", false ) ); } else { uses.add( new UseLink( itemId, itemCount, getEquipmentSpeculation( "equip", itemId, -1 ), "inv_equip.php?which=2&action=equip&whichitem=" ) ); // Quietly, stealthily, you reach out and steal the pants from your // unsuspecting self, and fade back into the mazy passages of the // Sleazy Back Alley before you notice what has happened. // // Then you make your way back out of the Alley, clutching your pants // triumphantly and trying really hard not to think about how oddly // chilly it has suddenly become. if ( consumeMethod == KoLConstants.EQUIP_PANTS && text.contains( "steal the pants from your unsuspecting self" ) ) { uses.add( new UseLink( itemId, "guild", "guild.php?place=challenge" ) ); } } if ( consumeMethod == KoLConstants.EQUIP_WEAPON && EquipmentDatabase.getHands( itemId ) == 1 && EquipmentDatabase.getHands( EquipmentManager.getEquipment( EquipmentManager.WEAPON ).getItemId() ) == 1 && KoLCharacter.hasSkill( "Double-Fisted Skull Smashing" ) ) { uses.add( new UseLink( itemId, itemCount, getEquipmentSpeculation( "offhand", itemId, EquipmentManager.OFFHAND ), "inv_equip.php?which=2&action=dualwield&whichitem=" ) ); } if ( consumeMethod != KoLConstants.EQUIP_FAMILIAR && KoLCharacter.getFamiliar().canEquip( ItemPool.get( itemId, 1 ) ) ) { uses.add( new UseLink( itemId, itemCount, getEquipmentSpeculation( "familiar", itemId, EquipmentManager.FAMILIAR ), "inv_equip.php?which=2&action=hatrack&whichitem=" ) ); } switch ( itemId ) { case ItemPool.LOATHING_LEGION_MANY_PURPOSE_HOOK: case ItemPool.LOATHING_LEGION_MOONDIAL: case ItemPool.LOATHING_LEGION_NECKTIE: case ItemPool.LOATHING_LEGION_ELECTRIC_KNIFE: case ItemPool.LOATHING_LEGION_CORKSCREW: case ItemPool.LOATHING_LEGION_CAN_OPENER: case ItemPool.LOATHING_LEGION_CHAINSAW: case ItemPool.LOATHING_LEGION_ROLLERBLADES: case ItemPool.LOATHING_LEGION_FLAMETHROWER: case ItemPool.LOATHING_LEGION_DEFIBRILLATOR: case ItemPool.LOATHING_LEGION_DOUBLE_PRISM: case ItemPool.LOATHING_LEGION_TAPE_MEASURE: case ItemPool.LOATHING_LEGION_KITCHEN_SINK: case ItemPool.LOATHING_LEGION_ABACUS: case ItemPool.LOATHING_LEGION_HELICOPTER: case ItemPool.LOATHING_LEGION_PIZZA_STONE: case ItemPool.LOATHING_LEGION_HAMMER: uses.add( new UseLink( itemId, 1, "switch", "inv_use.php?which=3&switch=1&whichitem=" ) ); break; case ItemPool.INSULT_PUPPET: case ItemPool.OBSERVATIONAL_GLASSES: case ItemPool.COMEDY_PROP: uses.add( new UseLink( itemId, itemCount, "visit mourn", "pandamonium.php?action=mourn&whichitem=" ) ); break; } if ( uses.size() == 1 ) { return uses.get( 0 ); } else { return new UsesLink( uses.toArray( new UseLink[ uses.size() ] ) ); } case KoLConstants.CONSUME_ZAP: return new UseLink( itemId, itemCount, "zap", "wand.php?whichwand=" ); } return null; } private static int equipSequence = 0; private static final String getSpeculation( String label, Modifiers mods ) { String id = "whatif" + UseLinkDecorator.equipSequence++; String table = SpeculateCommand.getHTML( mods, "id='" + id + "' style='background-color: white; visibility: hidden; position: absolute; right: 0px; top: 1.2em;'" ); if ( table == null ) return label; return "<span style='position: relative;' onMouseOver=\"document.getElementById('" + id + "').style.visibility='visible';\" onMouseOut=\"document.getElementById('" + id + "').style.visibility='hidden';\">" + table + label + "</span>"; } public static final String getEquipmentSpeculation( String label, int itemId, int slot ) { if ( slot == -1 ) { slot = EquipmentRequest.chooseEquipmentSlot( itemId ); } Speculation spec = new Speculation(); spec.equip( slot, ItemPool.get( itemId, 1 ) ); Modifiers mods = spec.calculate(); return getSpeculation( label, mods ); } private static final String getPotionSpeculation( String label, int itemId ) { Modifiers mods = Modifiers.getItemModifiers( itemId ); if ( mods == null ) return label; String effect = mods.getString( Modifiers.EFFECT ); if ( effect.equals( "" ) ) return label; int duration = (int)mods.get( Modifiers.EFFECT_DURATION ); int effectId = EffectDatabase.getEffectId( effect ); Speculation spec = new Speculation(); spec.addEffect( EffectPool.get( effectId, Math.max( 1, duration ) ) ); mods = spec.calculate(); mods.set( Modifiers.EFFECT, effect ); if ( duration > 0 ) { mods.set( Modifiers.EFFECT_DURATION, (float)duration ); } return getSpeculation( label, mods ); } private static final UseLink getNavigationLink( int itemId, String location ) { String useType = null; String useLocation = null; boolean combatResults = location.startsWith( "fight.php" ); switch ( itemId ) { // Shops case ItemPool.FRESHWATER_FISHBONE: useType = "assemble"; useLocation = "shop.php?whichshop=fishbones"; break; case ItemPool.TOPIARY_NUGGLET: useType = "sculpt"; useLocation = "shop.php?whichshop=topiary"; break; case ItemPool.TOXIC_GLOBULE: useType = "do science"; useLocation = "shop.php?whichshop=toxic"; break; case ItemPool.ROSE: case ItemPool.WHITE_TULIP: case ItemPool.RED_TULIP: case ItemPool.BLUE_TULIP: useType = "trade in"; useLocation = "shop.php?whichshop=flowertradein"; break; case ItemPool.FAT_LOOT_TOKEN: useType = String.valueOf( InventoryManager.getCount( ItemPool.FAT_LOOT_TOKEN ) ); useLocation = "shop.php?whichshop=damachine"; break; // Subject 37 File goes to Cell #37 case ItemPool.SUBJECT_37_FILE: useType = "cell #37"; useLocation = "cobbsknob.php?level=3&action=cell37"; break; // Guild quest items go to guild chief case ItemPool.BIG_KNOB_SAUSAGE: case ItemPool.EXORCISED_SANDWICH: useType = "guild"; useLocation = "guild.php?place=challenge"; break; case ItemPool.LOATHING_LEGION_JACKHAMMER: useType = "switch"; useLocation = "inv_use.php?which=3&switch=1&whichitem="; break; // Game Grid tokens get a link to the arcade. case ItemPool.GG_TOKEN: useType = "arcade"; useLocation = "place.php?whichplace=arcade"; break; // Game Grid tickets get a link to the arcade redemption counter. case ItemPool.GG_TICKET: useType = "redeem"; useLocation = "shop.php?whichshop=arcade"; break; // Soft green echo eyedrop antidote gets an uneffect link case ItemPool.REMEDY: case ItemPool.ANCIENT_CURE_ALL: useType = "use"; useLocation = "uneffect.php"; break; // Strange leaflet gets a quick 'read' link which sends you // to the leaflet completion page. case ItemPool.STRANGE_LEAFLET: useType = "read"; useLocation = "leaflet.php?action=auto"; break; // You want to give the rusty screwdriver to the Untinker, so // make it easy. case ItemPool.RUSTY_SCREWDRIVER: useType = "visit untinker"; useLocation = "place.php?whichplace=forestvillage&action=fv_untinker"; break; // Hedge maze puzzle and hedge maze key have a link to the maze // for easy access. case ItemPool.HEDGE_KEY: case ItemPool.PUZZLE_PIECE: useType = "maze"; useLocation = "hedgepuzzle.php"; break; // Pixels have handy links indicating how many white pixels are // present in the player's inventory. case ItemPool.WHITE_PIXEL: case ItemPool.RED_PIXEL: case ItemPool.GREEN_PIXEL: case ItemPool.BLUE_PIXEL: int whiteCount = CreateItemRequest.getInstance( ItemPool.WHITE_PIXEL ).getQuantityPossible() + InventoryManager.getCount( ItemPool.WHITE_PIXEL ); useType = whiteCount + " white"; useLocation = "place.php?whichplace=forestvillage&action=fv_mystic"; break; // Special handling for star charts, lines, and stars, where // KoLmafia shows you how many of each you have. case ItemPool.STAR_CHART: case ItemPool.STAR: case ItemPool.LINE: useType = InventoryManager.getCount( ItemPool.STAR_CHART ) + "," + InventoryManager.getCount( ItemPool.STAR ) + "," + InventoryManager.getCount( ItemPool.LINE ); useLocation = "shop.php?whichshop=starchart"; break; // Worthless items and the hermit permit get a link to the hermit. case ItemPool.WORTHLESS_TRINKET: case ItemPool.WORTHLESS_GEWGAW: case ItemPool.WORTHLESS_KNICK_KNACK: case ItemPool.HERMIT_PERMIT: useType = "hermit"; useLocation = "hermit.php"; break; // The different kinds of ores will only have a link if they're // the ones applicable to the trapper quest. case ItemPool.LINOLEUM_ORE: case ItemPool.ASBESTOS_ORE: case ItemPool.CHROME_ORE: case ItemPool.LUMP_OF_COAL: if ( location.startsWith( "dwarffactory.php" ) ) { useType = String.valueOf( InventoryManager.getCount( itemId ) ); useLocation = "dwarfcontraption.php"; break; } if ( itemId != ItemDatabase.getItemId( Preferences.getString( "trapperOre" ) ) ) { return null; } useType = String.valueOf( InventoryManager.getCount( itemId ) ); useLocation = "place.php?whichplace=mclargehuge&action=trappercabin"; break; case ItemPool.GROARS_FUR: case ItemPool.WINGED_YETI_FUR: useType = "trapper"; useLocation = "place.php?whichplace=mclargehuge&action=trappercabin"; break; case ItemPool.FRAUDWORT: case ItemPool.SHYSTERWEED: case ItemPool.SWINDLEBLOSSOM: if ( InventoryManager.getCount( ItemPool.FRAUDWORT ) < 3 || InventoryManager.getCount( ItemPool.SHYSTERWEED ) < 3 || InventoryManager.getCount( ItemPool.SWINDLEBLOSSOM ) < 3 ) { return null; } useType = "galaktik"; useLocation = "shop.php?whichshop=doc"; break; // Disintegrating sheet music gets a link which lets you sing it // to yourself. We'll call it "sing" for now. case ItemPool.SHEET_MUSIC: useType = "sing"; useLocation = "curse.php?action=use&targetplayer=" + KoLCharacter.getPlayerId() + "&whichitem="; break; // Link which uses the plans when you acquire the planks. case ItemPool.DINGY_PLANKS: if ( !InventoryManager.hasItem( ItemPool.DINGHY_PLANS ) ) { return null; } useType = "plans"; useLocation = "inv_use.php?which=3&whichitem="; itemId = ItemPool.DINGHY_PLANS; break; // Link which uses the Knob map when you get the encryption key. case ItemPool.ENCRYPTION_KEY: if ( !InventoryManager.hasItem( ItemPool.COBBS_KNOB_MAP ) ) { return null; } useType = "use map"; useLocation = "inv_use.php?which=3&whichitem="; itemId = ItemPool.COBBS_KNOB_MAP; break; // Link to the guild upon completion of the Citadel quest. case ItemPool.CITADEL_SATCHEL: case ItemPool.THICK_PADDED_ENVELOPE: useType = "guild"; useLocation = "guild.php?place=paco"; break; // Link to the guild when receiving guild quest items. case ItemPool.FERNSWARTHYS_KEY: // ...except that the guild gives you the key again if ( location.startsWith( "guild.php" ) ) { useType = "ruins"; useLocation = "fernruin.php"; break; } /*FALLTHRU*/ case ItemPool.DUSTY_BOOK: useType = "guild"; useLocation = "guild.php?place=ocg"; break; // Link to the untinker if you find an abridged dictionary. case ItemPool.ABRIDGED: useType = "untinker"; useLocation = "place.php?whichplace=forestvillage&action=fv_untinker"; break; // Link to the chasm if you just untinkered a dictionary. case ItemPool.BRIDGE: case ItemPool.DICTIONARY: case ItemPool.MORNINGWOOD_PLANK: case ItemPool.HARDWOOD_PLANK: case ItemPool.WEIRDWOOD_PLANK: case ItemPool.THICK_CAULK: case ItemPool.LONG_SCREW: case ItemPool.BUTT_JOINT: case ItemPool.SNOW_BOARDS: int urlEnd = OrcChasmRequest.getChasmProgress(); if ( urlEnd == 30 ) { break; } useType = "chasm"; useLocation = "place.php?whichplace=orc_chasm&action=bridge" + urlEnd; break; // Link to the frat house if you acquired a Spanish Fly case ItemPool.SPANISH_FLY: useType = String.valueOf( InventoryManager.getCount( itemId ) ); useLocation = "adventure.php?snarfblat=27"; break; // Link to Big Brother if you pick up a sand dollar case ItemPool.SAND_DOLLAR: useType = String.valueOf( InventoryManager.getCount( itemId ) ); useLocation = "monkeycastle.php?who=2"; break; // Link to the Old Man if you buy the damp old boot case ItemPool.DAMP_OLD_BOOT: useType = "old man"; useLocation = "place.php?whichplace=sea_oldman&action=oldman_oldman"; break; case ItemPool.GUNPOWDER: useType = String.valueOf( InventoryManager.getCount( itemId ) ); useLocation = IslandRequest.getPyroURL(); break; case ItemPool.TOWEL: useType = "fold"; useLocation = "inv_use.php?which=3&whichitem="; break; case ItemPool.GOLD_BOWLING_BALL: case ItemPool.REALLY_DENSE_MEAT_STACK: case ItemPool.SCARAB_BEETLE_STATUETTE: if ( !combatResults ) break; /*FALLTHRU*/ case ItemPool.HOLY_MACGUFFIN: case ItemPool.ED_HOLY_MACGUFFIN: if ( KoLCharacter.isEd() ) { useType = "Amun"; } else { useType = "council"; } useLocation = "council.php"; break; // Link to the Pretentious Artist when you find his last tool case ItemPool.PRETENTIOUS_PAINTBRUSH: case ItemPool.PRETENTIOUS_PALETTE: case ItemPool.PRETENTIOUS_PAIL: if ( !InventoryManager.hasItem( ItemPool.PRETENTIOUS_PAINTBRUSH ) || !InventoryManager.hasItem( ItemPool.PRETENTIOUS_PALETTE ) || !InventoryManager.hasItem( ItemPool.PRETENTIOUS_PAIL ) ) { return null; } useType = "artist"; useLocation = "place.php?whichplace=town_wrong&action=townwrong_artist_quest"; break; case ItemPool.MOLYBDENUM_MAGNET: case ItemPool.MOLYBDENUM_HAMMER: case ItemPool.MOLYBDENUM_PLIERS: case ItemPool.MOLYBDENUM_SCREWDRIVER: case ItemPool.MOLYBDENUM_WRENCH: useType = "yossarian"; useLocation = "bigisland.php?action=junkman"; break; case ItemPool.FILTHWORM_QUEEN_HEART: useType = "stand"; useLocation = "bigisland.php?place=orchard&action=stand"; break; case ItemPool.EMPTY_AGUA_DE_VIDA_BOTTLE: useType = "gaze"; useLocation = "place.php?whichplace=memories"; break; case ItemPool.SUGAR_SHEET: useType = "fold"; useLocation = "shop.php?whichshop=sugarsheets"; break; // Link to the kegger if you acquired a phone number. That's // not useful, but having the item count in the link is case ItemPool.ORQUETTES_PHONE_NUMBER: useType = String.valueOf( InventoryManager.getCount( itemId ) ); useLocation = "adventure.php?snarfblat=231"; break; case ItemPool.FORGED_ID_DOCUMENTS: useType = "vacation"; useLocation = "adventure.php?snarfblat=355"; break; case ItemPool.BUS_PASS: case ItemPool.IMP_AIR: useType = String.valueOf( InventoryManager.getCount( itemId ) ); useLocation = "pandamonium.php?action=moan"; break; case ItemPool.HACIENDA_KEY: useType = String.valueOf( InventoryManager.getCount( itemId ) ); useLocation = "volcanoisland.php?action=tniat&pwd=" + GenericRequest.passwordHash; break; case ItemPool.NOSTRIL_OF_THE_SERPENT: if ( !InventoryManager.hasItem( ItemPool.STONE_WOOL ) ) { return null; } itemId = ItemPool.STONE_WOOL; useType = "stone wool"; useLocation = "inv_use.php?which=3&whichitem="; break; case ItemPool.MOSS_COVERED_STONE_SPHERE: useType = "use sphere"; useLocation = "adventure.php?snarfblat=346"; break; case ItemPool.DRIPPING_STONE_SPHERE: useType = "use sphere"; useLocation = "adventure.php?snarfblat=347"; break; case ItemPool.CRACKLING_STONE_SPHERE: useType = "use sphere"; useLocation = "adventure.php?snarfblat=348"; break; case ItemPool.SCORCHED_STONE_SPHERE: useType = "use sphere"; useLocation = "adventure.php?snarfblat=349"; break; case ItemPool.STONE_ROSE: useType = "gnasir"; useLocation = "place.php?whichplace=desertbeach&action=db_gnasir"; break; case ItemPool.WORM_RIDING_MANUAL_PAGE: { int count = InventoryManager.getCount( itemId ); return count < 15 ? new UseLink( itemId, count ) : new UseLink( itemId, count, "gnasir", "place.php?whichplace=desertbeach&action=db_gnasir" ); } case ItemPool.FIRST_PIZZA: case ItemPool.LACROSSE_STICK: case ItemPool.EYE_OF_THE_STARS: case ItemPool.STANKARA_STONE: case ItemPool.MURPHYS_FLAG: case ItemPool.SHIELD_OF_BROOK: useType = "copperhead club"; useLocation = "adventure.php?snarfblat=383"; break; case ItemPool.GOLD_PIECE: return new UseLink( itemId, InventoryManager.getCount( itemId ) ); case ItemPool.SPOOKYRAVEN_NECKLACE: useType = "talk to Lady Spookyraven"; useLocation = "place.php?whichplace=manor1&action=manor1_ladys"; break; case ItemPool.POWDER_PUFF: case ItemPool.FINEST_GOWN: case ItemPool.DANCING_SHOES: if ( !InventoryManager.hasItem( ItemPool.POWDER_PUFF ) || !InventoryManager.hasItem( ItemPool.FINEST_GOWN ) || !InventoryManager.hasItem( ItemPool.DANCING_SHOES ) ) { return null; } useType = "talk to Lady Spookyraven"; useLocation = "place.php?whichplace=manor2&action=manor2_ladys"; break; case ItemPool.BABY_GHOSTS: useType = "talk to Lady Spookyraven"; useLocation = "place.php?whichplace=manor3&action=manor3_ladys"; break; case ItemPool.CRUMBLING_WHEEL: { int count1 = InventoryManager.getCount( itemId ); int count2 = InventoryManager.getCount( ItemPool.TOMB_RATCHET ); useType = String.valueOf( count1 ) + "+" + String.valueOf( count2 ); return !Preferences.getBoolean( "controlRoomUnlock" ) ? new UseLink( itemId, count1, useType, "javascript:return false;" ) : new UseLink( itemId, count1, useType, "place.php?whichplace=pyramid&action=pyramid_control" ); } case ItemPool.TOMB_RATCHET: { int count1 = InventoryManager.getCount( itemId ); int count2 = InventoryManager.getCount( ItemPool.CRUMBLING_WHEEL ); useType = String.valueOf( count2 ) + "+" + String.valueOf( count1 ); return !Preferences.getBoolean( "controlRoomUnlock" ) ? new UseLink( itemId, count1, useType, "javascript:return false;" ) : new UseLink( itemId, count1, useType, "place.php?whichplace=pyramid&action=pyramid_control" ); } case ItemPool.PACK_OF_SMOKES: { int count = InventoryManager.getCount( itemId ); return count < 10 ? new UseLink( itemId, count ) : new UseLink( itemId, count, "radio", "place.php?whichplace=airport_spooky&action=airport2_radio" ); } case ItemPool.EXPERIMENTAL_SERUM_P00: { int count = InventoryManager.getCount( itemId ); return count < 5 && QuestDatabase.isQuestLaterThan( Quest.SERUM, QuestDatabase.UNSTARTED ) ? new UseLink( itemId, count ) : new UseLink( itemId, count, "radio", "place.php?whichplace=airport_spooky&action=airport2_radio" ); } case ItemPool.MEATSMITH_CHECK: return new UseLink( itemId, 1, "visit meatsmith", "shop.php?whichshop=meatsmith" ); case ItemPool.NO_HANDED_PIE: return new UseLink( itemId, 1, "visit armorer", "shop.php?whichshop=armory" ); case ItemPool.BACON: int baconcount = InventoryManager.getCount( itemId ); useType = "spend (" + baconcount + ")"; useLocation = "shop.php?whichshop=bacon"; break; case ItemPool.RAD: int radcount = InventoryManager.getCount( itemId ); useType = "mutate (" + radcount + ")"; useLocation = "shop.php?whichshop=mutate"; break; case ItemPool.CASHEW: useType = "thanksgiving"; useLocation = "shop.php?whichshop=thankshop"; break; case ItemPool.SPANT_CHITIN: case ItemPool.SPANT_TENDON: useType = "assemble"; useLocation = "shop.php?whichshop=spant"; break; default: } if ( useType == null || useLocation == null ) { return null; } return new UseLink( itemId, useType, useLocation ); } public static class UseLink { private int itemId; private int itemCount; private String useType; private String useLocation; private boolean inline; protected UseLink() { } public UseLink( String useType, String useLocation ) { this( -1, 0, useType, useLocation, false ); } public UseLink( int itemId, String useType, String useLocation ) { this( itemId, 1, useType, useLocation ); } public UseLink( int itemId, int itemCount, String useLocation ) { this( itemId, itemCount, String.valueOf( itemCount ), useLocation ); } public UseLink( int itemId, int itemCount, String useType, String useLocation ) { this( itemId, itemCount, useType, useLocation, useLocation.startsWith( "inv" ) ); } public UseLink( int itemId, int itemCount ) { // This is just a counter this( itemId, itemCount, "javascript:return false;" ); } public UseLink( int itemId, int itemCount, String useType, String useLocation, boolean inline ) { this.itemId = itemId; this.itemCount = itemCount; this.useType = useType; this.useLocation = useLocation; this.inline = inline; if ( this.useLocation.endsWith( "=" ) ) { this.useLocation += this.itemId; } if ( this.useLocation.contains( "?" ) && !this.useLocation.contains( "phash=" ) && // It's not harmful to include the password hash // when it is unnecessary, but it is not pretty !this.useLocation.startsWith( "adventure.php" ) && !this.useLocation.startsWith( "place.php" ) && !this.useLocation.startsWith( "council.php" ) && !this.useLocation.startsWith( "guild.php" ) && !this.useLocation.startsWith( "wand.php" ) && !this.useLocation.startsWith( "diary.php" ) && !this.useLocation.startsWith( "volcanoisland.php" ) && !this.useLocation.startsWith( "cobbsknob.php" ) ) { this.useLocation += "&pwd=" + GenericRequest.passwordHash; } } public int getItemId() { return this.itemId; } public int getItemCount() { return this.itemCount; } public String getUseType() { return this.useType; } public String getUseLocation() { return this.useLocation; } public boolean showInline() { return this.inline && Preferences.getBoolean( "relayUsesInlineLinks" ); } public String getItemHTML() { if ( this.useLocation.equals( "#" ) ) { return "<font size=1>[<a href=\"javascript:" + "multiUse('multiuse.php'," + this.itemId + "," + this.itemCount + ");void(0);\">" + this.useType + "</a>]</font>"; } if ( !this.showInline() ) { return "<font size=1>[<a href=\"" + this.useLocation + "\">" + this.useType + "</a>]</font>"; } String[] pieces = this.useLocation.split( "\\?" ); return "<font size=1>[<a href=\"javascript:" + "singleUse('" + pieces[ 0 ].trim() + "','" + pieces[ 1 ].trim() + "&ajax=1');void(0);\">" + this.useType + "</a>]</font>"; } } public static class UsesLink extends UseLink { private final UseLink[] links; public UsesLink( UseLink[] links ) { this.links = links; } @Override public String getItemHTML() { StringBuilder buf = new StringBuilder(); for ( int i = 0; i < this.links.length; ++i ) { if ( i > 0 ) buf.append( " " ); buf.append( this.links[ i ].getItemHTML() ); } return buf.toString(); } } }