/**
* 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.request;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.kolmafia.AdventureResult;
import net.sourceforge.kolmafia.CoinmasterData;
import net.sourceforge.kolmafia.KoLCharacter;
import net.sourceforge.kolmafia.KoLConstants;
import net.sourceforge.kolmafia.KoLConstants.CraftingType;
import net.sourceforge.kolmafia.KoLConstants.MafiaState;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.RequestThread;
import net.sourceforge.kolmafia.objectpool.Concoction;
import net.sourceforge.kolmafia.objectpool.ConcoctionPool;
import net.sourceforge.kolmafia.objectpool.IntegerPool;
import net.sourceforge.kolmafia.objectpool.ItemPool;
import net.sourceforge.kolmafia.persistence.ItemDatabase;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.session.InventoryManager;
import net.sourceforge.kolmafia.session.ResultProcessor;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public class HermitRequest
extends CoinMasterRequest
{
private static final Pattern TOKEN_PATTERN = Pattern.compile( "You have ([\\d,]+) tradable items" );
public static final AdventureResult WORTHLESS_ITEM = ItemPool.get( ItemPool.WORTHLESS_ITEM, 1 );
private static final Map<Integer, Integer> buyPrices = new TreeMap<Integer, Integer>();
public static final CoinmasterData HERMIT =
new CoinmasterData(
"Hermit",
"hermit",
HermitRequest.class,
"worthless item",
null,
false,
HermitRequest.TOKEN_PATTERN,
HermitRequest.WORTHLESS_ITEM,
null,
null,
"hermit.php",
"trade",
KoLConstants.hermitItems,
HermitRequest.buyPrices,
null,
null,
null,
null,
"whichitem",
GenericRequest.WHICHITEM_PATTERN,
"quantity",
GenericRequest.QUANTITY_PATTERN,
null,
null,
true
);
static
{
ConcoctionPool.set( new Concoction( WORTHLESS_ITEM, CraftingType.NOCREATE ) );
};
private static final Pattern CLOVER_PATTERN = Pattern.compile( "(\\d+) left in stock for today" );
public static final AdventureResult CLOVER = ItemPool.get( ItemPool.TEN_LEAF_CLOVER, 1 );
public static final String CLOVER_FIELD = "whichitem=" + String.valueOf( ItemPool.TEN_LEAF_CLOVER );
public static final AdventureResult PERMIT = ItemPool.get( ItemPool.HERMIT_PERMIT, 1 );
public static final AdventureResult TRINKET = ItemPool.get( ItemPool.WORTHLESS_TRINKET, 1 );
public static final AdventureResult GEWGAW = ItemPool.get( ItemPool.WORTHLESS_GEWGAW, 1 );
public static final AdventureResult KNICK_KNACK = ItemPool.get( ItemPool.WORTHLESS_KNICK_KNACK, 1 );
public static final AdventureResult HACK_SCROLL = ItemPool.get( ItemPool.HERMIT_SCRIPT, 1 );
public static final AdventureResult SUMMON_SCROLL = ItemPool.get( ItemPool.ELITE_SCROLL, 1 );
private static boolean checkedForClovers = false;
private static final Integer ONE = IntegerPool.get( 1 );
/**
* Constructs a new <code>HermitRequest</code> that simply checks what items the hermit has available.
*/
public HermitRequest()
{
super( HermitRequest.HERMIT );
}
public HermitRequest( final boolean buying, final AdventureResult [] attachments )
{
super( HermitRequest.HERMIT, buying, attachments );
}
public HermitRequest( final boolean buying, final AdventureResult attachment )
{
super( HermitRequest.HERMIT, buying, attachment );
}
public HermitRequest( final boolean buying, final int itemId, final int quantity )
{
super( HermitRequest.HERMIT, buying, itemId, quantity );
}
public HermitRequest( final int itemId, final int quantity )
{
this( true, itemId, quantity );
}
private static final void registerHermitItem( final int itemId, final int count )
{
AdventureResult item = ItemPool.get( itemId, count );
KoLConstants.hermitItems.add( item );
HermitRequest.buyPrices.put( itemId, HermitRequest.ONE );
}
public static final void initialize()
{
HermitRequest.reset();
if ( KoLCharacter.inZombiecore() || KoLCharacter.inNuclearAutumn() )
{
HermitRequest.resetPurchaseRequests();
return;
}
HermitRequest.registerHermitItem( ItemPool.SEAL_TOOTH, PurchaseRequest.MAX_QUANTITY );
HermitRequest.registerHermitItem( ItemPool.CHISEL, PurchaseRequest.MAX_QUANTITY );
HermitRequest.registerHermitItem( ItemPool.PETRIFIED_NOODLES, PurchaseRequest.MAX_QUANTITY );
HermitRequest.registerHermitItem( ItemPool.JABANERO_PEPPER, PurchaseRequest.MAX_QUANTITY );
HermitRequest.registerHermitItem( ItemPool.BANJO_STRINGS, PurchaseRequest.MAX_QUANTITY );
HermitRequest.registerHermitItem( ItemPool.BUTTERED_ROLL, PurchaseRequest.MAX_QUANTITY );
HermitRequest.registerHermitItem( ItemPool.WOODEN_FIGURINE, PurchaseRequest.MAX_QUANTITY );
HermitRequest.registerHermitItem( ItemPool.KETCHUP, PurchaseRequest.MAX_QUANTITY );
HermitRequest.registerHermitItem( ItemPool.CATSUP, PurchaseRequest.MAX_QUANTITY );
HermitRequest.registerHermitItem( ItemPool.VOLLEYBALL, PurchaseRequest.MAX_QUANTITY );
if ( KoLCharacter.getClassType().equals( KoLCharacter.SEAL_CLUBBER ) )
{
HermitRequest.registerHermitItem( ItemPool.ANCIENT_SEAL, PurchaseRequest.MAX_QUANTITY );
}
HermitRequest.registerHermitItem( ItemPool.TEN_LEAF_CLOVER, -1 );
HermitRequest.resetPurchaseRequests();
}
public static final void reset()
{
HermitRequest.checkedForClovers = false;
KoLConstants.hermitItems.clear();
HermitRequest.buyPrices.clear();
}
public static final void resetPurchaseRequests()
{
HermitRequest.HERMIT.registerPurchaseRequests();
}
private final int worthlessItemsNeeded()
{
if ( this.attachments == null )
{
return 0;
}
int count = 0;
for ( int i = 0; i < this.attachments.length; ++i )
{
AdventureResult attachment = this.attachments[ i ];
count += attachment.getCount();
}
return count;
}
/**
* Executes the <code>HermitRequest</code>. This will trade the item specified in the character's
* <code>KoLSettings</code> for their worthless trinket; if the character has no worthless trinkets, this method
* will report an error to the StaticEntity.getClient().
*/
@Override
public void run()
{
// If we are simply visiting, we need no worthless items
if ( this.attachments == null )
{
super.run();
return;
}
int count = this.worthlessItemsNeeded();
// If we have a hermit script, read it now
if ( InventoryManager.hasItem( HermitRequest.HACK_SCROLL ) )
{
RequestThread.postRequest( UseItemRequest.getInstance( HermitRequest.HACK_SCROLL ) );
}
int worthless = HermitRequest.getWorthlessItemCount( false );
// If we want to make a trade, fetch enough worthless items
if ( worthless < count )
{
InventoryManager.retrieveItem( HermitRequest.WORTHLESS_ITEM.getInstance( count ) );
worthless = HermitRequest.getWorthlessItemCount( false );
}
if ( worthless < count )
{
return;
}
super.run();
}
@Override
public void processResults()
{
// The Hermit has left in the Nuclear Autumn path
if ( KoLCharacter.inNuclearAutumn() )
{
return;
}
// In Zombiecore we can get one clover per day on first visit
if ( KoLCharacter.inZombiecore() )
{
Preferences.setBoolean( "_zombieClover", true );
HermitRequest.checkedForClovers = true;
return;
}
if ( !HermitRequest.parseHermitTrade( this.getURLString(), this.responseText ) )
{
// If we got here, the hermit wouldn't talk to us.
if ( InventoryManager.retrieveItem( HermitRequest.PERMIT ) )
{
this.run();
return;
}
KoLmafia.updateDisplay( MafiaState.ERROR, "You're not allowed to visit the Hermit." );
return;
}
if ( this.attachments == null )
{
return;
}
// If you don't have any hermit permits, get one
// The Hermit looks at you expectantly, and when you don't respond, he points to a crudely-chalked
// sign on the wall reading "Hermit Permit required, pursuant to Seaside Town Ordinance #3769"
if ( this.responseText.indexOf( "Hermit Permit required" ) != -1 )
{
if ( InventoryManager.retrieveItem( HermitRequest.PERMIT ) )
{
this.run();
}
return;
}
// If the item is unavailable, assume he was asking for clover
if ( this.responseText.indexOf( "doesn't have that item." ) != -1 )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "Today is not a clover day." );
return;
}
// If you still didn't acquire items, what went wrong?
if ( this.responseText.indexOf( "You acquire" ) == -1 )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "The hermit kept his stuff." );
return;
}
}
public static final boolean parseHermitTrade( final String urlString, final String responseText )
{
// Nothing special to do if the Hermit has departed
if ( KoLCharacter.inZombiecore() || KoLCharacter.inNuclearAutumn() )
{
return true;
}
// There should be a form, or an indication of item receipt,
// for all valid hermit requests.
if ( responseText.indexOf( "hermit.php" ) == -1 && responseText.indexOf( "You acquire" ) == -1 )
{
return false;
}
// If you don't have enough Hermit Permits, failure
if ( responseText.indexOf( "You don't have enough Hermit Permits" ) != -1 )
{
HermitRequest.checkedForClovers = false;
return true;
}
// If the item is unavailable, assume he was asking for clover
// If asked for too many, you get no items
if ( responseText.indexOf( "doesn't have that item." ) != -1 ||
responseText.indexOf( "You acquire" ) == -1 )
{
HermitRequest.parseHermitStock( responseText );
return true;
}
Matcher quantityMatcher = GenericRequest.QUANTITY_PATTERN.matcher( urlString );
if ( !quantityMatcher.find() )
{
// We simply visited the hermit
HermitRequest.parseHermitStock( responseText );
return true;
}
int quantity = StringUtilities.parseInt( quantityMatcher.group( 1 ) );
// Subtract the worthless items in order of their priority;
// as far as we know, the priority is the item Id.
int used = HermitRequest.subtractWorthlessItems( HermitRequest.TRINKET, quantity );
if ( used > 0 )
{
quantity -= used;
}
used = HermitRequest.subtractWorthlessItems( HermitRequest.GEWGAW, quantity );
if ( used > 0 )
{
quantity -= used;
}
used = HermitRequest.subtractWorthlessItems( HermitRequest.KNICK_KNACK, quantity );
if ( used > 0 )
{
quantity -= used;
}
if ( responseText.indexOf( "he sends you packing" ) != -1 )
{
// No worthless items in inventory, so we can't tell if
// clovers remain in stock
HermitRequest.checkedForClovers = false;
return true;
}
HermitRequest.parseHermitStock( responseText );
return true;
}
private static final int subtractWorthlessItems( final AdventureResult item, final int total )
{
int count = 0 - Math.min( total, item.getCount( KoLConstants.inventory ) );
if ( count != 0 )
{
ResultProcessor.processResult( item.getInstance( count ) );
}
return 0 - count;
}
// <td valign=center><img src="http://images.kingdomofloathing.com/itemimages/tooth.gif" class=hand onClick='javascript:item(617818041)'></td><td valign=center><b>seal tooth</b></td></tr>
private static final Pattern ITEM_PATTERN = Pattern.compile( "javascript:item\\(([\\d]+)\\).*?<b>([^<]*)</b>", Pattern.DOTALL );
private static final void parseHermitStock( final String responseText )
{
// Refresh the Coin Master inventory every time we visit.
Matcher matcher = ITEM_PATTERN.matcher( responseText );
if ( !matcher.find() )
{
return;
}
KoLConstants.hermitItems.clear();
HermitRequest.buyPrices.clear();
do
{
String descId = matcher.group(1);
String itemName = matcher.group(2);
int itemId = ItemDatabase.getItemId( itemName );
// Add it to the Hermit's inventory
HermitRequest.registerHermitItem( itemId, PurchaseRequest.MAX_QUANTITY );
}
while ( matcher.find() );
Matcher cloverMatcher = CLOVER_PATTERN.matcher( responseText );
int count = cloverMatcher.find() ? Integer.parseInt( cloverMatcher.group( 1 ) ) : 0;
int index = KoLConstants.hermitItems.indexOf( CLOVER );
if ( index < 0 )
{
HermitRequest.registerHermitItem( ItemPool.TEN_LEAF_CLOVER, count );
}
else
{
AdventureResult old = (AdventureResult) KoLConstants.hermitItems.get( index );
int oldCount = old.getCount();
if ( oldCount != count )
{
KoLConstants.hermitItems.set( index, CLOVER.getInstance( count ) );
}
}
HermitRequest.checkedForClovers = true;
// Register the purchase requests, now that we know what is available
HermitRequest.HERMIT.registerPurchaseRequests();
}
public static final boolean isWorthlessItem( final int itemId )
{
return itemId == ItemPool.WORTHLESS_TRINKET || itemId == ItemPool.WORTHLESS_GEWGAW || itemId == ItemPool.WORTHLESS_KNICK_KNACK;
}
public static final int getWorthlessItemCount()
{
return HermitRequest.getWorthlessItemCount( false );
}
public static final int getWorthlessItemCount( boolean all )
{
int count =
HermitRequest.TRINKET.getCount( KoLConstants.inventory ) +
HermitRequest.GEWGAW.getCount( KoLConstants.inventory ) +
HermitRequest.KNICK_KNACK.getCount( KoLConstants.inventory );
if ( all )
{
if ( InventoryManager.canUseCloset() )
{
count +=
HermitRequest.TRINKET.getCount( KoLConstants.closet ) +
HermitRequest.GEWGAW.getCount( KoLConstants.closet ) +
HermitRequest.KNICK_KNACK.getCount( KoLConstants.closet );
}
if ( InventoryManager.canUseStorage() )
{
count +=
HermitRequest.TRINKET.getCount( KoLConstants.storage ) +
HermitRequest.GEWGAW.getCount( KoLConstants.storage ) +
HermitRequest.KNICK_KNACK.getCount( KoLConstants.storage );
}
}
return count;
}
public static final int getAcquirableWorthlessItemCount()
{
int count = HermitRequest.getWorthlessItemCount( true );
if ( InventoryManager.canUseNPCStores() )
{
int cost = InventoryManager.currentWorthlessItemCost();
count += KoLCharacter.getAvailableMeat() / cost;
}
return count;
}
public static final int cloverCount()
{
// One clover a day available in Zombie path
if ( KoLCharacter.inZombiecore() )
{
return Preferences.getBoolean( "_zombieClover0" ) ? 0 : 1;
}
if ( !HermitRequest.checkedForClovers )
{
RequestThread.postRequest( new HermitRequest() );
}
int index = KoLConstants.hermitItems.indexOf( CLOVER );
return index < 0 ? 0 : ( (AdventureResult) KoLConstants.hermitItems.get( index ) ).getCount();
}
public static final boolean isCloverDay()
{
return HermitRequest.cloverCount() > 0;
}
public static final void hackHermit()
{
int index = KoLConstants.hermitItems.indexOf( HermitRequest.CLOVER );
if ( index != -1 )
{
AdventureResult clover = ( AdventureResult)KoLConstants.hermitItems.get( index );
KoLConstants.hermitItems.set( index, HermitRequest.CLOVER.getInstance( clover.getCount() + 1 ) );
}
else
{
HermitRequest.registerHermitItem( ItemPool.TEN_LEAF_CLOVER, 1 );
}
}
public static String accessible()
{
return null;
}
public static final boolean registerRequest( final String urlString )
{
if ( !urlString.startsWith( "hermit.php" ) )
{
return false;
}
CoinmasterData data = HermitRequest.HERMIT;
return CoinMasterRequest.registerRequest( data, urlString, true );
}
}