/**
* 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.Calendar;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.kolmafia.AdventureResult;
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.RequestLogger;
import net.sourceforge.kolmafia.RequestThread;
import net.sourceforge.kolmafia.objectpool.EffectPool;
import net.sourceforge.kolmafia.objectpool.ItemPool;
import net.sourceforge.kolmafia.objectpool.SkillPool;
import net.sourceforge.kolmafia.persistence.ConcoctionDatabase;
import net.sourceforge.kolmafia.persistence.ConsumablesDatabase;
import net.sourceforge.kolmafia.persistence.ItemDatabase;
import net.sourceforge.kolmafia.persistence.MonsterDatabase.Element;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.session.InventoryManager;
import net.sourceforge.kolmafia.session.ResponseTextParser;
import net.sourceforge.kolmafia.session.ResultProcessor;
import net.sourceforge.kolmafia.session.TurnCounter;
import net.sourceforge.kolmafia.swingui.GenericFrame;
import net.sourceforge.kolmafia.utilities.InputFieldUtilities;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public class EatItemRequest
extends UseItemRequest
{
private static final Pattern FORTUNE_PATTERN =
Pattern.compile( "<font size=1>(Lucky numbers: (\\d+), (\\d+), (\\d+))</td>" );
private static final Pattern INSUFFICIENT_QUANTITY_PATTERN =
Pattern.compile( "You only have (\\d+) of those, not (\\d+)" );
private static final Pattern MAYONEX_PATTERN =
Pattern.compile( "Force of Mayo Be With You</b><br>\\(duration: (\\d+) Adventure" );
private static int ignorePrompt = 0;
private static int askedAboutMilk = 0;
private static int askedAboutLunch = 0;
private static int askedAboutGarish = 0;
private static int askedAboutMayodiol = 0;
private static int askedAboutRecordHunger = 0;
private static AdventureResult queuedFoodHelper = null;
private static int queuedFoodHelperCount = 0;
public static int foodConsumed = 0;
public static boolean timeSpinnerUsed = false;
public EatItemRequest( final AdventureResult item )
{
super( ItemDatabase.getConsumptionType( item.getItemId() ), item );
}
@Override
public int getAdventuresUsed()
{
if ( this.itemUsed.getItemId() == ItemPool.BLACK_PUDDING )
{
// Items that can redirect to a fight
return this.itemUsed.getCount();
}
return 0;
}
public static final void ignorePrompt()
{
EatItemRequest.ignorePrompt = KoLCharacter.getUserId();
}
public static final void clearFoodHelper()
{
EatItemRequest.queuedFoodHelper = null;
EatItemRequest.queuedFoodHelperCount = 0;
}
public static final AdventureResult currentFoodHelper()
{
return ( EatItemRequest.queuedFoodHelper != null && EatItemRequest.queuedFoodHelperCount > 0 ) ?
EatItemRequest.queuedFoodHelper.getInstance( EatItemRequest.queuedFoodHelperCount ) :
null;
}
public static final int maximumUses( final int itemId, final String itemName, final int fullness )
{
if ( KoLCharacter.isJarlsberg() && ConcoctionDatabase.getMixingMethod( itemId ) != CraftingType.JARLS )
{
UseItemRequest.limiter = "its non-Jarlsbergian nature";
return 0;
}
if ( KoLCharacter.inZombiecore() && !itemName.equals( "steel lasagna" ) &&
( ConsumablesDatabase.getNotes( itemName ) == null || !ConsumablesDatabase.getNotes( itemName ).startsWith( "Zombie Slayer" ) ) )
{
UseItemRequest.limiter = "it not being a brain";
return 0;
}
if ( KoLCharacter.inNuclearAutumn() && ConsumablesDatabase.getFullness( itemName ) > 1 )
{
return 0;
}
switch ( itemId )
{
case ItemPool.SPAGHETTI_BREAKFAST:
// This is your breakfast, you need to eat it first thing
return ( KoLCharacter.getFullness() == 0 && KoLCharacter.getFullnessLimit() > 0 &&
!Preferences.getBoolean( "_spaghettiBreakfastEaten" ) ) ? 1 : 0;
}
int limit = KoLCharacter.getFullnessLimit();
int fullnessLeft = limit - KoLCharacter.getFullness();
UseItemRequest.limiter = "fullness";
return fullnessLeft / fullness;
}
@Override
public void run()
{
if ( GenericRequest.abortIfInFightOrChoice() )
{
return;
}
if ( this.consumptionType == KoLConstants.CONSUME_FOOD_HELPER )
{
int count = this.itemUsed.getCount();
if ( !InventoryManager.retrieveItem( this.itemUsed ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "Helper not available." );
return;
}
if ( this.itemUsed.equals( EatItemRequest.queuedFoodHelper ) )
{
EatItemRequest.queuedFoodHelperCount += count;
}
else
{
EatItemRequest.queuedFoodHelper = this.itemUsed;
EatItemRequest.queuedFoodHelperCount = count;
}
KoLmafia.updateDisplay( this.itemUsed.getName() + " queued for next " + count + " food" +
(count == 1 ? "" : "s") + " eaten." );
return;
}
if ( !ConsumablesDatabase.meetsLevelRequirement( this.itemUsed.getName() ) )
{
UseItemRequest.lastUpdate = "Insufficient level to consume " + this.itemUsed;
KoLmafia.updateDisplay( MafiaState.ERROR, UseItemRequest.lastUpdate );
return;
}
int itemId = this.itemUsed.getItemId();
UseItemRequest.lastUpdate = "";
int maximumUses = UseItemRequest.maximumUses( itemId );
if ( maximumUses < this.itemUsed.getCount() )
{
KoLmafia.updateDisplay( "(usable quantity of " + this.itemUsed +
" is limited to " + maximumUses + " by " +
UseItemRequest.limiter + ")" );
this.itemUsed = this.itemUsed.getInstance( maximumUses );
}
if ( this.itemUsed.getCount() < 1 )
{
return;
}
if ( !EatItemRequest.sequentialConsume( itemId ) &&
!InventoryManager.retrieveItem( this.itemUsed ) )
{
return;
}
int iterations = 1;
int origCount = this.itemUsed.getCount();
// The miracle of "consume some" does not apply to black puddings
if ( origCount > 1 &&
( EatItemRequest.singleConsume( itemId ) ||
( EatItemRequest.sequentialConsume( itemId ) && InventoryManager.getCount( itemId ) < origCount) ) )
{
iterations = origCount;
this.itemUsed = this.itemUsed.getInstance( 1 );
}
String originalURLString = this.getURLString();
for ( int i = 1; i <= iterations && KoLmafia.permitsContinue(); ++i )
{
EatItemRequest.foodConsumed = i - 1;
if ( !this.allowFoodConsumption() )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "Aborted eating " + this.itemUsed.getCount() + " " + this.itemUsed.getName() + "." );
return;
}
this.constructURLString( originalURLString );
this.useOnce( i, iterations, "Eating" );
}
if ( KoLmafia.permitsContinue() )
{
EatItemRequest.foodConsumed = origCount;
KoLmafia.updateDisplay( "Finished eating " + origCount + " " + this.itemUsed.getName() + "." );
}
}
@Override
public void useOnce( final int currentIteration, final int totalIterations, String useTypeAsString )
{
UseItemRequest.lastUpdate = "";
// Check to make sure the character has the item in their
// inventory first - if not, report the error message and
// return from the method.
if ( !InventoryManager.retrieveItem( this.itemUsed ) )
{
UseItemRequest.lastUpdate = "Insufficient items to use.";
return;
}
if ( this.consumptionType == KoLConstants.CONSUME_MULTIPLE && this.itemUsed.getCount() > 1 )
{
this.addFormField( "action", "useitem" );
}
this.addFormField( "ajax", "1" );
this.addFormField( "quantity", String.valueOf( this.itemUsed.getCount() ) );
if ( EatItemRequest.queuedFoodHelper != null && EatItemRequest.queuedFoodHelperCount > 0 )
{
int helperItemId = EatItemRequest.queuedFoodHelper.getItemId();
switch ( helperItemId )
{
case ItemPool.SCRATCHS_FORK:
// Check it can be safely used
UseItemRequest.lastUpdate = UseItemRequest.elementalHelper( "Hotform", Element.HOT, 1000 );
if ( !UseItemRequest.lastUpdate.equals( "" ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, UseItemRequest.lastUpdate );
EatItemRequest.queuedFoodHelper = null;
return;
}
// deliberate fallthrough
case ItemPool.FUDGE_SPORK:
// Items submitted with utensil
this.addFormField( "utensil", String.valueOf( helperItemId ) );
break;
default:
// Autoused helpers are ignored
this.removeFormField( "utensil" );
}
EatItemRequest.queuedFoodHelperCount -= 1;
}
else
{
this.removeFormField( "utensil" );
}
super.runOneIteration( currentIteration, totalIterations, useTypeAsString );
}
private static final boolean singleConsume( final int itemId )
{
// Consume one at a time when a helper is involved.
// Multi-consume with a helper actually DOES work, even though
// there is no interface for doing so in game, but that's
// probably not something that should be relied on.
if ( EatItemRequest.queuedFoodHelper != null && EatItemRequest.queuedFoodHelperCount > 0 )
{
return true;
}
switch ( itemId )
{
case ItemPool.BLACK_PUDDING:
// Eating a black pudding can lead to a combat with no
// feedback about how many were successfully eaten
// before the combat.
return true;
}
return false;
}
private static final boolean sequentialConsume( final int itemId )
{
switch (itemId )
{
case ItemPool.BORIS_PIE:
case ItemPool.JARLSBERG_PIE:
case ItemPool.SNEAKY_PETE_PIE:
// Allow multiple pies to be made and eaten with only one key.
return true;
}
return false;
}
private final boolean allowFoodConsumption()
{
if ( !GenericFrame.instanceExists() )
{
return true;
}
if ( EatItemRequest.ignorePrompt == KoLCharacter.getUserId() )
{
return true;
}
String itemName = this.itemUsed.getName();
if ( !askAboutGarish( itemName ) )
{
return false;
}
if ( !askAboutMayodiol( this.itemUsed.getItemId() ) )
{
return false;
}
if ( !EatItemRequest.askAboutMilk( itemName, this.itemUsed.getCount() ) )
{
return false;
}
if ( !UseItemRequest.askAboutPvP( itemName ) )
{
return false;
}
// If we are not a Pastamancer, that's good enough. If we are,
// make sure the player isn't going to accidentally scuttle the
// stupid Spaghettihose trophy.
if ( KoLCharacter.getClassType() != KoLCharacter.PASTAMANCER )
{
return true;
}
// If carboLoading is 0, it doesn't matter what you eat.
// If it's 1, this might be normal aftercore eating.
// If it's 10, the character will qualify for the trophy
int carboLoading = Preferences.getInteger( "carboLoading" );
if ( carboLoading <= 1 || carboLoading >= 10 )
{
return true;
}
// If the food is not made with noodles, no fear
if ( ConcoctionDatabase.noodleCreation( this.itemUsed.getItemId() ) == null )
{
return true;
}
// Nag
if ( !InputFieldUtilities.confirm( "Eating pasta with only " + carboLoading + " levels of Carboloading will ruin your chance to get the Spaghettihose trophy. Are you sure?" ) )
{
return false;
}
return true;
}
public static boolean askAboutMilk( final String name, final int count )
{
// If user specifically said not to worry about milk, don't nag
int myUserId = KoLCharacter.getUserId();
if ( EatItemRequest.ignorePrompt == myUserId )
{
return true;
}
// If the item doesn't give any adventures, it won't benefit from using milk
String note = ConsumablesDatabase.getNotes( name );
String advGain = ConsumablesDatabase.getAdvRangeByName( name );
if ( advGain.equals( "0" ) )
{
if ( note == null || !note.contains( "Unspaded" ) )
{
return true;
}
}
// If we are not in Axecore, don't even consider Lunch
if ( !KoLCharacter.inAxecore() )
{
EatItemRequest.askedAboutLunch = myUserId;
}
// If we are not in Nuclear Autumn or don't have 7th Floor, don't even consider Record Hunger
if ( !KoLCharacter.inNuclearAutumn() || Preferences.getInteger( "falloutShelterLevel" ) < 7 )
{
EatItemRequest.askedAboutRecordHunger = myUserId;
}
boolean skipMilkNag = ( EatItemRequest.askedAboutMilk == myUserId );
boolean skipLunchNag = ( EatItemRequest.askedAboutLunch == myUserId );
boolean skipRecordHungerNag = ( EatItemRequest.askedAboutRecordHunger == myUserId );
// If we've already asked about milk and/or lunch or record hunger, don't nag
if ( skipMilkNag && skipLunchNag && skipRecordHungerNag )
{
return true;
}
// See if the character can cast Song of the Glorious Lunch
UseSkillRequest lunch = UseSkillRequest.getInstance( "Song of the Glorious Lunch" );
boolean canLunch = KoLCharacter.inAxecore() && KoLConstants.availableSkills.contains( lunch );
// See if the character has (or can buy) a milk of magnesium.
boolean canMilk = InventoryManager.itemAvailable( ItemPool.MILK_OF_MAGNESIUM );
// If you either can't get or don't care about both effects, don't nag
if ( ( !canLunch || skipLunchNag ) && ( !canMilk || skipMilkNag ) && skipRecordHungerNag )
{
return true;
}
// Calculate how much fullness we are about to add
int fullness = ConsumablesDatabase.getFullness( name );
int consumptionTurns = count * fullness;
// Check for Glorious Lunch
if ( !skipLunchNag && canLunch )
{
// See if already have enough of the Glorious Lunch effect
int lunchTurns = ConsumablesDatabase.GLORIOUS_LUNCH.getCount( KoLConstants.activeEffects );
if ( lunchTurns < consumptionTurns )
{
String message = lunchTurns > 0 ?
"Song of the Glorious Lunch will run out before you finish eating that. Are you sure?" :
"Are you sure you want to eat without Song of the Glorious Lunch?";
if ( !InputFieldUtilities.confirm( message ) )
{
return false;
}
EatItemRequest.askedAboutLunch = myUserId;
}
}
// Check for Got Milk
if ( !skipMilkNag && canMilk )
{
// See if already have enough of the Got Milk effect
int milkyTurns = ConsumablesDatabase.MILK.getCount( KoLConstants.activeEffects );
if ( milkyTurns < consumptionTurns )
{
String message = milkyTurns > 0 ?
"Got Milk will run out before you finish eating that. Are you sure?" :
"Are you sure you want to eat without milk?";
if ( !InputFieldUtilities.confirm( message ) )
{
return false;
}
EatItemRequest.askedAboutMilk = myUserId;
}
}
// Check for Record Hunger
if ( !skipRecordHungerNag )
{
// See if already have Record Hunger effect
int recordHungerTurns = ConsumablesDatabase.RECORD_HUNGER.getCount( KoLConstants.activeEffects );
if ( recordHungerTurns < 1 )
{
String message = "Are you sure you want to eat without Record Hunger?";
if ( !InputFieldUtilities.confirm( message ) )
{
return false;
}
EatItemRequest.askedAboutRecordHunger = myUserId;
}
}
return true;
}
private static final boolean askAboutGarish( String itemName )
{
// Only affects lasagna
if ( !ConsumablesDatabase.isLasagna( ItemDatabase.getItemId( itemName ) ) )
{
return true;
}
// If you've got Garish, or it's Monday, no need to ask
Calendar date = Calendar.getInstance( TimeZone.getTimeZone( "GMT-0700" ) );
if( KoLConstants.activeEffects.contains( EffectPool.get( EffectPool.GARISH ) ) || date.get( Calendar.DAY_OF_WEEK ) == Calendar.MONDAY )
{
return true;
}
// If we've already asked about Garish, don't nag
if ( EatItemRequest.askedAboutGarish == KoLCharacter.getUserId() )
{
return true;
}
// If we don't have skill, all summons are used, and we can't get one, no need to ask
if ( ( !KoLCharacter.hasSkill( SkillPool.CLIP_ART ) || UseSkillRequest.getUnmodifiedInstance( SkillPool.CLIP_ART ).getMaximumCast() == 0 )
&& !InventoryManager.itemAvailable( ItemPool.FIELD_GAR_POTION ) )
{
return true;
}
// If autoGarish is true, get Gar-ish
if ( Preferences.getBoolean( "autoGarish" ) )
{
RequestThread.postRequest( UseItemRequest.getInstance( ItemPool.FIELD_GAR_POTION ) );
if ( !KoLConstants.activeEffects.contains( EffectPool.get( EffectPool.GARISH ) ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "Failed to use Potion of the Field Gar." );
return false;
}
else
{
return true;
}
}
if ( !InputFieldUtilities.confirm( "Are you sure you want to eat Lasagna without Potion of the Field Gar ?" ) )
{
return false;
}
EatItemRequest.askedAboutGarish = KoLCharacter.getUserId();
return true;
}
private static final boolean askAboutMayodiol( final int id )
{
// If we've already asked about Mayodiol, don't nag
if ( EatItemRequest.askedAboutMayodiol == KoLCharacter.getUserId() )
{
return true;
}
// If we're not at drunk limit, it's ok
if ( KoLCharacter.getInebrietyLimit() != KoLCharacter.getInebriety() )
{
return true;
}
// If it's Mayo, warning is only needed if Mayodiol
if ( ConcoctionDatabase.isMayo( id ) )
{
if ( id == ItemPool.MAYODIOL )
{
if ( !InputFieldUtilities.confirm( "Putting this in your mouth will cause you to overdrink with the next food, are you sure ?" ) )
{
return false;
}
}
else
{
return true;
}
}
else
{
// It's food
// If we will use MayoMinder to automatically use Mayodiol, warn
if ( Preferences.getString( "mayoInMouth" ).equals( "" ) &&
Preferences.getString( "mayoMinderSetting" ).equals( "Mayodiol" ) &&
InventoryManager.hasItem( ItemPool.MAYODIOL ) )
{
if ( !InputFieldUtilities.confirm( "Eating this will cause you to overdrink due to Mayodiol in inventory with Mayo Minder™ set to use it, are you sure ?" ) )
{
return false;
}
}
// If we already have Mayodiol in our mouth, warn
else if ( Preferences.getString( "mayoInMouth" ).equals( "Mayodiol" ) )
{
if ( !InputFieldUtilities.confirm( "Eating this will cause you to overdrink due to Mayodiol in your mouth, are you sure ?" ) )
{
return false;
}
}
// Otherwise no warning needed
else
{
return true;
}
}
EatItemRequest.askedAboutMayodiol = KoLCharacter.getUserId();
return true;
}
public static final void parseConsumption( final AdventureResult item, final AdventureResult helper, final String responseText )
{
// Make sure the global value is reset before returning
boolean timeSpinnerUsed = EatItemRequest.timeSpinnerUsed;
EatItemRequest.timeSpinnerUsed = false;
// Special handling for fortune cookies, since you can smash
// them, as well as eat them
if ( item.getItemId() == ItemPool.FORTUNE_COOKIE &&
responseText.contains( "You brutally smash the fortune cookie" ) )
{
ResultProcessor.processResult( item.getNegation() );
return;
}
if ( item.getItemId() == ItemPool.CARTON_OF_SNAKE_MILK &&
responseText.contains( "cream cheese" ) )
{
ResultProcessor.processResult( item.getNegation() );
return;
}
// You're really, really hungry, but that isn't what you're hungry for.
if ( KoLCharacter.inZombiecore() && responseText.contains( "that isn't what you're hungry for" ) )
{
UseItemRequest.lastUpdate = "You can only eat tasty, tasty brains.";
KoLmafia.updateDisplay( MafiaState.ERROR, UseItemRequest.lastUpdate );
return;
}
// Breakfast has to be the first thing you eat in a day. That's what breakfast means.
if ( responseText.contains( "That's what breakfast means" ) )
{
UseItemRequest.lastUpdate = "A spaghetti breakfast must be your the first food of the day.";
KoLmafia.updateDisplay( MafiaState.ERROR, UseItemRequest.lastUpdate );
return;
}
// Food is restricted by Standard.
if ( responseText.contains( "That item is too old to be used on this path" ) )
{
UseItemRequest.lastUpdate = item.getName() + " is too old to be used on this path.";
KoLmafia.updateDisplay( MafiaState.ERROR, UseItemRequest.lastUpdate );
return;
}
// You only have 1 of those, not 2
Matcher quantityMatcher = EatItemRequest.INSUFFICIENT_QUANTITY_PATTERN.matcher( responseText );
if ( quantityMatcher.find() )
{
int count = StringUtilities.parseInt( quantityMatcher.group( 1 ) );
int requested = StringUtilities.parseInt( quantityMatcher.group( 2 ) );
UseItemRequest.lastUpdate = "You only have " + count + " of those, not " + requested;
KoLmafia.updateDisplay( MafiaState.ERROR, UseItemRequest.lastUpdate );
return;
}
boolean shouldUpdateFullness = !responseText.contains( " Fullness" );
if ( responseText.contains( "too full" ) )
{
int fullness = ConsumablesDatabase.getFullness( item.getName() );
int count = item.getCount();
UseItemRequest.lastUpdate = "Consumption limit reached.";
KoLmafia.updateDisplay( MafiaState.ERROR, UseItemRequest.lastUpdate );
// If we have no fullness data for this item, we can't tell what,
// if anything, consumption did to our fullness.
if ( fullness == 0 )
{
return;
}
int maxFullness = KoLCharacter.getFullnessLimit();
// Based on what we think our current fullness is,
// calculate how many of this item we have room for.
int maxEat = ( maxFullness - KoLCharacter.getFullness()) / fullness;
// We know that KoL did not let us eat as many as we
// requested, so adjust for how many we could eat.
int couldEat = Math.max( 0, Math.min( count - 1, maxEat ) );
if ( couldEat > 0 )
{
if ( shouldUpdateFullness )
{
KoLCharacter.setFullness( KoLCharacter.getFullness() + couldEat * fullness );
}
Preferences.decrement( "munchiesPillsUsed", couldEat );
ResultProcessor.processResult( item.getInstance( -couldEat ) );
}
int estimatedFullness = maxFullness - fullness + 1;
if ( estimatedFullness > KoLCharacter.getFullness() )
{
KoLCharacter.setFullness( estimatedFullness );
}
KoLCharacter.updateStatus();
return;
}
// Check for consumption helpers, which will need to be removed
// from inventory if they were successfully used.
if ( helper != null )
{
// Check for success message, since there are multiple
// ways these could fail:
boolean success = true;
switch ( helper.getItemId() )
{
case ItemPool.SCRATCHS_FORK:
// "You eat the now piping-hot <food> -- it's
// sizzlicious! The salad fork cools, and you
// discard it."
if ( !responseText.contains( "The salad fork cools" ) )
{
success = false;
}
break;
case ItemPool.FUDGE_SPORK:
// "You eat the <food> with your fudge spork,
// and then you eat your fudge spork. How sweet it is!"
if ( responseText.contains( "you eat your fudge spork" ) )
{
Preferences.setBoolean( "_fudgeSporkUsed", true );
}
else
{
success = false;
}
break;
}
if ( !success )
{
UseItemRequest.lastUpdate = "Consumption helper failed.";
KoLmafia.updateDisplay( MafiaState.ERROR, UseItemRequest.lastUpdate );
return;
}
// Remove the consumption helper from inventory.
ResultProcessor.processResult( helper.getNegation() );
}
int consumptionType = UseItemRequest.getConsumptionType( item );
if ( consumptionType == KoLConstants.CONSUME_FOOD_HELPER )
{
// Consumption helpers are removed above when you
// successfully eat or drink.
return;
}
// The food was consumed successfully
EatItemRequest.handleFoodHelper( item.getName(), item.getCount(), responseText );
EatItemRequest.updateTimeSpinner( item.getItemId(), timeSpinnerUsed );
if ( !timeSpinnerUsed )
{
ResultProcessor.processResult( item.getNegation() );
}
KoLCharacter.updateStatus();
// Re-sort consumables list if needed
if ( Preferences.getBoolean( "sortByRoom" ) )
{
ConcoctionDatabase.getUsables().sort();
}
// Perform item-specific processing
switch ( item.getItemId() )
{
case ItemPool.FORTUNE_COOKIE:
case ItemPool.QUANTUM_TACO:
// If it's a fortune cookie, get the fortune
Matcher matcher = EatItemRequest.FORTUNE_PATTERN.matcher( responseText );
while ( matcher.find() )
{
EatItemRequest.handleFortuneCookie( matcher );
}
return;
case ItemPool.LUCIFER:
// Jumbo Dr. Lucifer reduces your hit points to 1.
ResultProcessor.processResult( new AdventureResult( AdventureResult.HP, 1 - KoLCharacter.getCurrentHP() ) );
return;
case ItemPool.BLACK_PUDDING:
// "You screw up your courage and eat the black pudding.
// It turns out to be the blood sausage sort of
// pudding. You're not positive that that's a good
// thing. Bleah."
if ( responseText.contains( "blood sausage" ) )
{
return;
}
// If we are actually redirected to a fight, the item
// is consumed elsewhere. Eating a black pudding via
// the in-line ajax support no longer redirects to a
// fight. Instead, the fight is forced by a script:
//
// <script type="text/javascript">top.mainpane.document.location = "fight.php";</script>
//
// If we got here, we removed it above and incremented
// our fullness, but it wasn't actually consumed.
ResultProcessor.processResult( item );
if ( shouldUpdateFullness )
{
KoLCharacter.setFullness( KoLCharacter.getFullness() -3 );
}
// "You don't have time to properly enjoy a black
// pudding right now."
if ( responseText.contains( "don't have time" ) )
{
UseItemRequest.lastUpdate = "Insufficient adventures left.";
}
// "You're way too beaten up to enjoy a black pudding
// right now. Because they're tough to chew. Yeah."
else if ( responseText.contains( "too beaten up" ) )
{
UseItemRequest.lastUpdate = "Too beaten up.";
}
if ( !UseItemRequest.lastUpdate.equals( "" ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, UseItemRequest.lastUpdate );
}
return;
case ItemPool.STEEL_STOMACH:
if ( responseText.contains( "You acquire a skill" ) )
{
ResponseTextParser.learnSkill( "Stomach of Steel" );
}
return;
case ItemPool.EXTRA_GREASY_SLIDER:
KoLCharacter.setSpleenUse( KoLCharacter.getSpleenUse() - 5 * item.getCount() );
KoLCharacter.updateStatus();
return;
case ItemPool.SPAGHETTI_BREAKFAST:
Preferences.setBoolean( "_spaghettiBreakfastEaten", true );
return;
case ItemPool.SMORE:
Preferences.increment( "smoresEaten", 1 );
ConsumablesDatabase.setSmoresData();
ConsumablesDatabase.calculateAdventureRanges();
return;
}
}
public static final void handleFoodHelper( final String itemName, final int count, final String responseText )
{
// You chase it with that salt you made in the chemistry
// lab. Man. Teenagers will eat anything.
if ( responseText.contains( "You chase it with that salt you made" ) )
{
int itemsUsed = Math.min( Math.min( count, InventoryManager.getCount( ItemPool.GRAINS_OF_SALT ) ),
3 - Preferences.getInteger( "_saltGrainsConsumed" ) );
RequestLogger.printLine( "You ate " + itemsUsed + " grains of salt with your food" );
ResultProcessor.processItem( ItemPool.GRAINS_OF_SALT, -itemsUsed );
Preferences.increment( "_saltGrainsConsumed", itemsUsed );
}
// You dip the spaghetti breakfast in swamp honey before you
// eat it. Mmmmm!
if ( responseText.contains( "in swamp honey before you eat it." ) )
{
int itemsUsed = Math.min( count, InventoryManager.getCount( ItemPool.JAR_OF_SWAMP_HONEY ) );
if ( itemsUsed > 1 )
{
RequestLogger.printLine( "You ate " + itemsUsed + " jars of swamp honey with your food" );
}
else
{
RequestLogger.printLine( "You ate a jar of swamp honey with your food" );
}
ResultProcessor.processItem( ItemPool.JAR_OF_SWAMP_HONEY, -itemsUsed );
}
// You give the barrel cracker a nice dry rubbing before going to work on it...</
if ( responseText.contains( "a nice dry rubbing before going to work on it" ) )
{
int itemsUsed = Math.min( count, InventoryManager.getCount( ItemPool.DRY_RUB ) );
if ( itemsUsed > 1 )
{
RequestLogger.printLine( "You ate " + itemsUsed + " shakers of dry rub with your food" );
}
else
{
RequestLogger.printLine( "You ate a shaker of dry rub with your food" );
}
ResultProcessor.processItem( ItemPool.DRY_RUB, -itemsUsed );
}
// You feel the canticle take hold, and feel suddenly bloated
// as the pasta expands in your belly.
if ( KoLCharacter.getClassType() == KoLCharacter.PASTAMANCER &&
responseText.contains( "feel suddenly bloated" ) )
{
Preferences.setInteger( "carboLoading", 0 );
}
// If you have Mayo Minder running, you don't need to use the mayo helpers, but they are still used up
// Your Mayo Minderâ„¢ beeps, reminding you to squirt some mayonnaise in your mouth before eating.
// You feel the Mayonex gurgling in your stomach.
// The Mayodiol kicks in and converts some of what you just ate into pure ethanol.
// The Mayostat kicks in and you belch up a mayonnaise-coated bolus of food.
// The Mayozapine kicks in and makes the food extra-delicious!
// The Mayoflex kicks in and makes the food more nutritious.
if ( responseText.contains( "reminding you to squirt some mayonnaise" ) )
{
if ( responseText.contains( "feel the Mayonex gurgling" ) )
{
int itemsUsed = Math.min( count, InventoryManager.getCount( ItemPool.MAYONEX ) );
RequestLogger.printLine( "Mayo Minder™ reminded you to use Mayonex (" + itemsUsed + " times)" );
ResultProcessor.processItem( ItemPool.MAYONEX, -itemsUsed );
}
else if ( responseText.contains( "Mayodiol kicks in" ) )
{
int itemsUsed = Math.min( count, InventoryManager.getCount( ItemPool.MAYODIOL ) );
RequestLogger.printLine( "Mayo Minder™ reminded you to use Mayodiol (" + itemsUsed + " times)" );
ResultProcessor.processItem( ItemPool.MAYODIOL, -itemsUsed );
}
else if ( responseText.contains( "Mayostat kicks in" ) )
{
int itemsUsed = Math.min( count, InventoryManager.getCount( ItemPool.MAYOSTAT ) );
RequestLogger.printLine( "Mayo Minder™ reminded you to use Mayostat (" + itemsUsed + " times)" );
ResultProcessor.processItem( ItemPool.MAYOSTAT, -itemsUsed );
}
else if ( responseText.contains( "Mayozapine kicks in" ) )
{
int itemsUsed = Math.min( count, InventoryManager.getCount( ItemPool.MAYOZAPINE ) );
RequestLogger.printLine( "Mayo Minder™ reminded you to use Mayozapine (" + itemsUsed + " times)" );
ResultProcessor.processItem( ItemPool.MAYOZAPINE, -itemsUsed );
}
else if ( responseText.contains( "Mayoflex kicks in" ) )
{
int itemsUsed = Math.min( count, InventoryManager.getCount( ItemPool.MAYOFLEX ) );
RequestLogger.printLine( "Mayo Minder™ reminded you to use Mayoflex (" + itemsUsed + " times)" );
ResultProcessor.processItem( ItemPool.MAYOFLEX, -itemsUsed );
}
}
// With Mayonex, you gain mayoLevel rather than adventures
if ( responseText.contains( "feel the Mayonex gurgling" ) )
{
Matcher mayonexMatcher = EatItemRequest.MAYONEX_PATTERN.matcher( responseText );
while ( mayonexMatcher.find() )
{
Preferences.increment( "mayoLevel", StringUtilities.parseInt( mayonexMatcher.group( 1 ) ) );
}
}
// If you had mayo in your mouth, you do no longer
Preferences.setString( "mayoInMouth", "" );
// If the user has fullness display turned on ( "You gain x
// Fullness" ) DON'T touch fullness here. It is handled in
// ResultProcessor.
if ( !responseText.contains( " Fullness" ) )
{
int fullness = ConsumablesDatabase.getFullness( itemName );
int fullnessUsed = fullness * count;
// The Mayodiol kicks in and converts some of what you just ate into pure ethanol.
if ( responseText.contains( "Mayodiol kicks in" ) )
{
fullnessUsed -= 1;
}
KoLCharacter.setFullness( KoLCharacter.getFullness() + fullnessUsed );
}
Preferences.decrement( "munchiesPillsUsed", count );
}
private static final void handleFortuneCookie( final Matcher matcher )
{
String message = matcher.group( 1 );
RequestLogger.updateSessionLog( message );
RequestLogger.printLine( message );
if ( TurnCounter.isCounting( "Fortune Cookie" ) )
{
for ( int i = 2; i <= 4; ++i )
{
int number = StringUtilities.parseInt( matcher.group( i ) );
if ( TurnCounter.isCounting( "Fortune Cookie", number ) )
{
TurnCounter.stopCounting( "Fortune Cookie" );
TurnCounter.startCounting( number, "Fortune Cookie", "fortune.gif" );
TurnCounter.stopCounting( "Semirare window begin" );
TurnCounter.stopCounting( "Semirare window end" );
return;
}
}
}
int minCounter;
// First semirare comes between 70 and 80 regardless of path
// If we haven't played 70 turns, we definitely have not passed
// the semirare counter yet.
if ( KoLCharacter.getCurrentRun() < 70 )
{
minCounter = 70;
}
// If we haven't seen a semirare yet and are still within the
// window for the first, again, expect the first one.
else if ( KoLCharacter.getCurrentRun() < 80 &&
KoLCharacter.lastSemirareTurn() == 0 )
{
minCounter = 70;
}
// Otherwise, we are definitely past the first semirare,
// whether or not we saw it. If you are not an Oxygenarian,
// semirares come less frequently
else if ( KoLCharacter.canEat() || KoLCharacter.canDrink() )
{
minCounter = 150; // conservative, wiki claims 160 minimum
}
// ... than if you are on the Oxygenarian path
else
{
minCounter = 100; // conservative, wiki claims 102 minimum
}
minCounter -= KoLCharacter.turnsSinceLastSemirare();
for ( int i = 2; i <= 4; ++i )
{
int number = StringUtilities.parseInt( matcher.group( i ) );
int minEnd = 0;
if ( TurnCounter.getCounters( "Semirare window begin", 0, 500 ).equals( "" ) )
{
// We are possibly within the window currently.
// If the actual semirare turn has already been
// missed, a number past the window end could
// be valid - but it would have to be at least
// 80 turns past the end.
minEnd = number - 79;
}
if ( number < minCounter ||
!TurnCounter.getCounters( "Semirare window begin", number + 1, 500 ).equals( "" ) )
{
KoLmafia.updateDisplay( "Lucky number " + number +
" ignored - too soon to be a semirare." );
continue;
}
if ( number > 205 ||
!TurnCounter.getCounters( "Semirare window end", minEnd, number - 1 ).equals( "" ) )
{ // conservative, wiki claims 200 maximum
KoLmafia.updateDisplay( "Lucky number " + number +
" ignored - too large to be a semirare." );
continue;
}
// Add the new lucky number
TurnCounter.startCounting( number, "Fortune Cookie", "fortune.gif" );
}
TurnCounter.stopCounting( "Semirare window begin" );
TurnCounter.stopCounting( "Semirare window end" );
}
public static final void updateTimeSpinner( final int itemId, final boolean timeSpinnerUsed )
{
// This will also track Thanksgetting foods, since all foods that count for it
// show up in the Time-Spinner list
if ( timeSpinnerUsed )
{
Preferences.increment( "_timeSpinnerMinutesUsed", 3 );
return;
}
if ( !ItemDatabase.isDiscardable( itemId ) || !ItemDatabase.isTradeable( itemId ) ||
ItemDatabase.isGiftItem( itemId ) )
{
return;
}
String itemString = String.valueOf( itemId );
String foodAvailable = Preferences.getString( "_timeSpinnerFoodAvailable" );
String[] foods = foodAvailable.split( "," );
for ( String food : foods )
{
if ( food.equals( itemString ) )
{
return;
}
}
if ( !foodAvailable.isEmpty() )
{
foodAvailable += ",";
}
foodAvailable += itemString;
Preferences.setString( "_timeSpinnerFoodAvailable", foodAvailable );
if ( itemId >= ItemPool.CANDIED_SWEET_POTATOES && itemId <= ItemPool.BREAD_ROLL )
{
Preferences.increment( "_thanksgettingFoodsEaten" );
}
}
public static final String lastSemirareMessage()
{
KoLCharacter.ensureUpdatedAscensionCounters();
int turns = Preferences.getInteger( "semirareCounter" );
if ( turns == 0 )
{
return "No semirare found yet this run.";
}
int current = KoLCharacter.getCurrentRun();
String location = Preferences.getString( "semirareLocation" );
String loc = location.equals( "" ) ? "" : ( " in " + location );
return "Last semirare found " + ( current - turns ) + " turns ago (on turn " + turns + ")" + loc;
}
public static final boolean registerRequest()
{
AdventureResult item = UseItemRequest.lastItemUsed;
int count = item.getCount();
String name = item.getName();
String useString = "eat " + count + " " + name ;
RequestLogger.updateSessionLog();
RequestLogger.updateSessionLog( useString );
return true;
}
}