/**
* 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.session;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.kolmafia.KoLCharacter;
import net.sourceforge.kolmafia.KoLConstants.MafiaState;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.RequestThread;
import net.sourceforge.kolmafia.objectpool.ItemPool;
import net.sourceforge.kolmafia.request.LeafletRequest;
import net.sourceforge.kolmafia.textui.command.CouncilCommand;
public abstract class LeafletManager
{
private static final LeafletRequest LEAFLET_REQUEST = new LeafletRequest();
private static final Pattern FOREST_PATTERN = Pattern.compile( "Gaps in the dense, forbidding foliage lead (.*?)," );
// This script assumes that the leaflet can be in any state; the player
// can open the leaflet, move around, and manipulate objects in any way
// desired, and then invoke this script, which will detect what has been
// done and carry on from there to complete everything still possible.
//
// Needless to say, the player could have done things that prohibit
// earlier actions: lighting the fireplace precludes recovering the
// parchment, and entering the forest maze precludes doing anything
// from the first half of the map, for example.
// There are ten locations within the Strange Leaflet:
// Cave
// ^
// | N/S
// E/W v
// Clearing <-> Forest Path
// ^
// | N/S
// v E/W
// Open Field <-> Inside House
// ^
// | N/S
// v
// South Bank Table top
// | ^
// | S + S | U/D
// v v
// Forest Maze Middle of tree
// | ^
// N/S/E/W | | U/D
// | v
// +-------------> Bottom of tree
private static final int HOUSE = 0;
private static final int FIELD = 1;
private static final int PATH = 2;
private static final int CLEARING = 3;
private static final int CAVE = 4;
private static final int BANK = 5;
private static final int FOREST = 6;
private static final int BOTTOM = 7;
private static final int TREE = 8;
private static final int TABLE = 9;
// Strings that unambiguously identify current location
private static final String[][] LOCATIONS =
{
{
"<b>In the House</b>",
"in the house",
},
{
"<b>West of House</b>",
"west of the house",
},
{
"<b>North of the Field</b>",
"north of the field",
},
{
"<b>Forest Clearing</b>",
"in the forest clearing",
},
{
"<b>Cave</b>",
"in the cave",
},
{
"<b>South Bank</b>",
"on the south bank",
},
{
"<b>Forest</b>",
"in the forest",
},
{
"<b>On the other side of the forest maze...</b>",
"past maze",
},
{
"<b>Halfway Up The Tree</b>",
"halfway up the tree",
},
{
"<b>Tabletop</b>",
"on the tabletop",
}
};
// Current location
private static int location;
// Items we can pick up: true if in inventory, false otherwise
private static boolean leaflet;
private static boolean sword;
private static boolean stick;
private static boolean boots;
private static boolean parchment;
private static boolean egg;
private static boolean ruby;
private static boolean scroll;
private static boolean ring;
private static boolean trophy;
// Things we can manipulate
private static boolean wornboots; // true if we are wearing the boots
private static boolean door; // true if door is open
private static boolean hedge; // true if hedge is done
private static boolean torch; // true if torch is lit
private static boolean serpent; // true if serpent is done
private static boolean chest; // true if chest is done
private static boolean fireplace; // true if fireplace is lit
private static String magic; // non-null if magic invoked
private static String exit; // non-null if in maze
private static boolean roadrunner; // true if roadrunner is done
private static boolean petunias; // true if petunias are done
private static boolean giant; // true if giant is done
public static final String locationName()
{
String response = LeafletManager.executeCommand( "inv" );
return LeafletManager.locationName( response );
}
public static final String leafletWithMagic()
{
return LeafletManager.robStrangeLeaflet( true );
}
public static final String leafletNoMagic()
{
return LeafletManager.robStrangeLeaflet( false );
}
public static final String robStrangeLeaflet( final boolean invokeMagic )
{
// Make sure the player has the Strange Leaflet.
if ( !InventoryManager.hasItem( ItemPool.STRANGE_LEAFLET ) )
{
if ( KoLCharacter.getLevel() >= 9 )
{
RequestThread.postRequest( CouncilCommand.COUNCIL_VISIT );
}
else
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You are too low level for that quest." );
return CouncilCommand.COUNCIL_VISIT.responseText;
}
}
try
{
// Deduce location and status of items and actions
LeafletManager.initialize();
// Solve the puzzles.
LeafletManager.solveLeaflet( invokeMagic );
}
catch ( LeafletException e )
{
KoLmafia.updateDisplay( MafiaState.ERROR, e.getMessage() );
}
return LeafletManager.LEAFLET_REQUEST.responseText;
}
private static final void initialize()
throws LeafletException
{
// We know nothing about the state of the objects.
LeafletManager.door = false;
LeafletManager.hedge = false;
LeafletManager.torch = false;
LeafletManager.serpent = false;
LeafletManager.chest = false;
LeafletManager.fireplace = false;
LeafletManager.magic = null;
LeafletManager.exit = null;
LeafletManager.roadrunner = false;
LeafletManager.petunias = false;
LeafletManager.giant = false;
// Find out where we are in the leaflet by using the "inv"
// command -- this will return your current virtual inventory
// and also shows your current location in the page title
KoLmafia.updateDisplay( "Determining current leaflet progress..." );
String response = LeafletManager.executeCommand( "inv" );
LeafletManager.leaflet = response.indexOf( "A junk mail leaflet" ) != -1;
LeafletManager.sword =
response.indexOf( "An ornate sword" ) != -1 && response.indexOf( "hangs above the mantel" ) == -1;
LeafletManager.torch = response.indexOf( "A burning torch" ) != -1;
LeafletManager.stick =
LeafletManager.torch || response.indexOf( "A hefty stick" ) != -1 && response.indexOf( "lies on the ground" ) == -1;
LeafletManager.boots = response.indexOf( "A pair of large rubber wading boots" ) != -1;
LeafletManager.wornboots = LeafletManager.boots && response.indexOf( "boots (equipped)" ) != -1;
LeafletManager.parchment = response.indexOf( "A piece of parchment" ) != -1;
LeafletManager.egg = response.indexOf( "A jewel-encrusted egg" ) != -1;
LeafletManager.ruby = response.indexOf( "A fiery ruby" ) != -1;
LeafletManager.scroll = response.indexOf( "A rolled-up scroll" ) != -1;
LeafletManager.ring = response.indexOf( "A giant's pinky ring" ) != -1;
LeafletManager.trophy = response.indexOf( "A shiny bowling trophy" ) != -1;
}
private static final void solveLeaflet( final boolean invokeMagic )
throws LeafletException
{
// For completeness, get the leaflet
LeafletManager.getLeaflet();
// Open the chest to get the house
LeafletManager.openChest();
// Get the grue egg from the hole
LeafletManager.robHole();
// Invoke the magic word, if the player wants to;
// otherwise, retrieve the trophy in all cases.
if ( !LeafletManager.invokeMagic( invokeMagic ) )
{
KoLmafia.updateDisplay( "Serpent-slaying quest complete." );
return;
}
// Get the ring
LeafletManager.getRing();
String extra =
LeafletManager.trophy ? " (trophy available)" : LeafletManager.magic != null ? " (magic invoked)" : "";
KoLmafia.updateDisplay( "Strange leaflet completed" + extra + "." );
if ( LeafletManager.magic != null )
{
KoLCharacter.updateStatus();
}
}
private static final String executeCommand( final String command )
{
LeafletManager.LEAFLET_REQUEST.setCommand( command );
RequestThread.postRequest( LeafletManager.LEAFLET_REQUEST );
// Figure out where we are
LeafletManager.parseLocation( LeafletManager.LEAFLET_REQUEST.responseText );
// Let the caller look at the results, if desired
return LeafletManager.LEAFLET_REQUEST.responseText;
}
private static final int getLocation( final String response )
{
for ( int location = 0; location < LeafletManager.LOCATIONS.length; ++location )
{
String [] names = LeafletManager.LOCATIONS[ location ];
if ( response.indexOf( names[0] ) != -1 )
{
return location;
}
}
return -1;
}
private static final String locationName( final String response )
{
return LeafletManager.locationName( LeafletManager.getLocation( response ) );
}
private static final String locationName( final int location )
{
if ( location < 0 || location >= LeafletManager.LOCATIONS.length )
{
return "Unknown";
}
return LeafletManager.LOCATIONS[ location ][ 1 ];
}
private static final void parseLocation( final String response )
{
// Find out where we are in the leaflet
LeafletManager.location = LeafletManager.getLocation( response );
// Assume no maze exit
LeafletManager.exit = null;
switch ( LeafletManager.location )
{
case HOUSE:
LeafletManager.fireplace = response.indexOf( "fireplace is lit" ) != -1;
// You cannot close the door. "close door" =>
// "You feel a sudden streak of malevolence and
// decide to leave the door wide open. Serves
// 'em right for not locking it."
LeafletManager.door = true;
break;
case FIELD:
LeafletManager.door = response.indexOf( "front door is closed" ) == -1;
break;
case PATH:
LeafletManager.hedge = response.indexOf( "thick hedge" ) == -1;
break;
case CLEARING:
LeafletManager.hedge = true;
break;
case CAVE:
LeafletManager.hedge = true;
LeafletManager.chest = response.indexOf( "empty treasure chest" ) != -1;
LeafletManager.serpent = response.indexOf( "dangerous-looking serpent" ) == -1;
break;
case BANK:
LeafletManager.fireplace = true;
break;
case FOREST:
Matcher matcher = LeafletManager.FOREST_PATTERN.matcher( response );
if ( matcher.find() )
{
LeafletManager.exit = matcher.group( 1 );
}
break;
case BOTTOM:
break;
case TREE:
LeafletManager.roadrunner = response.indexOf( "large ruby in its beak" ) == -1;
LeafletManager.petunias = response.indexOf( "scroll entangled in the flowers" ) == -1;
break;
case TABLE:
LeafletManager.giant = response.indexOf( "The Giant himself" ) == -1;
break;
default:
throw new LeafletException( "Server-side change detected. Script aborted." );
}
}
private static final void parseMantelpiece( final String response )
{
if ( response.indexOf( "brass bowling trophy" ) != -1 )
{
// "A brass bowling trophy sits on the mantelpiece."
LeafletManager.magic = null;
}
else if ( response.indexOf( "carved driftwood bird" ) != -1 )
{
// "A carved driftwood bird sits on the mantelpiece."
LeafletManager.magic = "plover";
}
else if ( response.indexOf( "white house" ) != -1 )
{
// "A ceramic model of a small white house sits on the mantelpiece."
LeafletManager.magic = "xyzzy";
}
else if ( response.indexOf( "brick building" ) != -1 )
{
// "A ceramic model of a brick building sits on the mantelpiece."
LeafletManager.magic = "plugh";
}
else if ( response.indexOf( "model ship" ) != -1 )
{
// "A model ship inside a bottle sits on the mantelpiece."
LeafletManager.magic = "yoho";
}
}
private static final void parseMagic( final String response )
{
// Bail if we didn't invoke a magic word.
if ( LeafletManager.magic == null )
{
return;
}
// Check for failures
if ( response.indexOf( "That only works once." ) != -1 ||
// The player already invoked the correct word
response.indexOf( "send the plover over" ) != -1 ||
// "Red rover, red rover, send the plover over"
response.indexOf( "nothing happens" ) != -1 )
{
// "You chant the magic word, and nothing happens. You
// hear thunder rumbling in the distance..."
LeafletManager.magic = null;
}
}
private static final void getLeaflet()
{
// Can't go back
if ( LeafletManager.location > LeafletManager.BANK )
{
return;
}
if ( LeafletManager.leaflet )
{
return;
}
KoLmafia.updateDisplay( "Retrieving mail..." );
LeafletManager.goTo( LeafletManager.FIELD );
// We can't tell if the mailbox is already open. But, there's
// no harm in opening it twice.
LeafletManager.executeCommand( "open mailbox" );
LeafletManager.executeCommand( "take leaflet" );
LeafletManager.leaflet = true;
}
private static final void openChest()
{
// Can't go back
if ( LeafletManager.location > LeafletManager.BANK )
{
return;
}
if ( LeafletManager.chest )
{
return;
}
KoLmafia.updateDisplay( "Looking for treasure..." );
LeafletManager.goTo( LeafletManager.CAVE );
LeafletManager.killSerpent();
if ( !LeafletManager.chest )
{
LeafletManager.executeCommand( "open chest" );
}
}
private static final void robHole()
{
// Can't go back
if ( LeafletManager.location > LeafletManager.BANK )
{
return;
}
KoLmafia.updateDisplay( "Hunting eggs..." );
// We can't tell if we've already done this. But, there's no
// harm in doing it twice.
LeafletManager.goTo( LeafletManager.CAVE );
LeafletManager.executeCommand( "look behind chest" );
LeafletManager.executeCommand( "look in hole" );
}
// Returns true if should proceed, false if should stop now
private static final boolean invokeMagic( boolean invokeMagic )
{
// Can't go back
if ( LeafletManager.location > LeafletManager.BANK )
{
return true;
}
if ( LeafletManager.trophy )
{
return true;
}
KoLmafia.updateDisplay( "Looking for knick-knacks..." );
LeafletManager.goTo( LeafletManager.HOUSE );
LeafletManager.parseMantelpiece( LeafletManager.executeCommand( "examine fireplace" ) );
if ( LeafletManager.magic == null )
{
LeafletManager.executeCommand( "take trophy" );
LeafletManager.trophy = true;
return true;
}
if ( !invokeMagic )
{
return false;
}
LeafletManager.parseMagic( LeafletManager.executeCommand( LeafletManager.magic ) );
return true;
}
private static final void getRing()
{
if ( LeafletManager.ring )
{
return;
}
if ( LeafletManager.location < LeafletManager.BOTTOM )
{
LeafletManager.goTo( LeafletManager.BOTTOM );
}
KoLmafia.updateDisplay( "Finding the giant..." );
LeafletManager.getScroll();
if ( LeafletManager.parchment && !KoLCharacter.hasSkill( "CLEESH" ) )
{
LeafletManager.executeCommand( "GNUSTO CLEESH" );
ResponseTextParser.learnSkill( "CLEESH" );
LeafletManager.parchment = false;
LeafletManager.scroll = false;
}
LeafletManager.goTo( LeafletManager.TABLE );
if ( !LeafletManager.giant )
{
LeafletManager.executeCommand( "CLEESH giant" );
}
LeafletManager.executeCommand( "take ring" );
LeafletManager.ring = true;
}
private static final void getScroll()
{
if ( LeafletManager.scroll )
{
return;
}
LeafletManager.goTo( LeafletManager.TREE );
// If it's not in the bowl of petunias, we've gotten it and memorized it
if ( LeafletManager.petunias )
{
return;
}
LeafletManager.getRuby();
LeafletManager.goTo( LeafletManager.TREE );
LeafletManager.executeCommand( "throw ruby at petunias" );
LeafletManager.ruby = false;
LeafletManager.executeCommand( "read scroll" );
}
private static final void getRuby()
{
if ( LeafletManager.ruby )
{
return;
}
LeafletManager.goTo( LeafletManager.TREE );
if ( !LeafletManager.roadrunner )
{
if ( !LeafletManager.egg )
{
LeafletManager.executeCommand( "take egg" );
}
LeafletManager.executeCommand( "throw egg at roadrunner" );
LeafletManager.egg = false;
}
LeafletManager.goTo( LeafletManager.BOTTOM );
LeafletManager.executeCommand( "move leaves" );
LeafletManager.ruby = true;
}
private static final void goTo( final int destination )
{
// If you've already reached your destination,
// you do not need to move.
if ( LeafletManager.location == destination )
{
return;
}
// Otherwise, get necessary items and go where
// you need to go.
switch ( destination )
{
case HOUSE:
switch ( LeafletManager.location )
{
case BANK:
case PATH:
case CLEARING:
case CAVE:
LeafletManager.goTo( LeafletManager.FIELD );
// Fall through
case FIELD:
LeafletManager.openDoor();
LeafletManager.executeCommand( "east" );
break;
default:
break;
}
break;
case FIELD:
switch ( LeafletManager.location )
{
case HOUSE:
LeafletManager.executeCommand( "west" );
break;
case CLEARING:
case CAVE:
LeafletManager.goTo( LeafletManager.PATH );
// Fall through
case PATH:
LeafletManager.executeCommand( "south" );
break;
case BANK:
LeafletManager.executeCommand( "north" );
break;
default:
break;
}
break;
case PATH:
switch ( LeafletManager.location )
{
case HOUSE:
case BANK:
LeafletManager.goTo( LeafletManager.FIELD );
// Fall through
case FIELD:
LeafletManager.executeCommand( "north" );
break;
case CLEARING:
LeafletManager.executeCommand( "east" );
break;
case CAVE:
LeafletManager.executeCommand( "south" );
break;
default:
break;
}
break;
case CLEARING:
LeafletManager.cutHedge();
LeafletManager.goTo( LeafletManager.PATH );
LeafletManager.executeCommand( "west" );
break;
case CAVE:
LeafletManager.getTorch();
LeafletManager.goTo( LeafletManager.PATH );
LeafletManager.executeCommand( "north" );
break;
case BANK:
LeafletManager.wearBoots();
LeafletManager.goTo( LeafletManager.FIELD );
LeafletManager.executeCommand( "south" );
break;
// From here on we've entered the maze and can't go back
case FOREST:
// No return
if ( LeafletManager.location > LeafletManager.FOREST )
{
return;
}
LeafletManager.goTo( LeafletManager.BANK );
LeafletManager.executeCommand( "south" );
LeafletManager.executeCommand( "south" );
break;
case BOTTOM:
switch ( LeafletManager.location )
{
default:
LeafletManager.goTo( LeafletManager.FOREST );
// Fall through
case FOREST:
// Stumble around until we get out
KoLmafia.updateDisplay( "Navigating the forest..." );
while ( LeafletManager.exit != null )
{
LeafletManager.executeCommand( LeafletManager.exit );
}
break;
case TABLE:
LeafletManager.goTo( LeafletManager.TREE );
// Fall through
case TREE:
LeafletManager.executeCommand( "down" );
break;
}
break;
case TREE:
switch ( LeafletManager.location )
{
case BOTTOM:
LeafletManager.executeCommand( "up" );
break;
case TABLE:
LeafletManager.executeCommand( "down" );
break;
}
break;
case TABLE:
LeafletManager.goTo( LeafletManager.TREE );
LeafletManager.executeCommand( "up" );
break;
}
}
private static final void getSword()
{
if ( LeafletManager.sword )
{
return;
}
LeafletManager.goTo( LeafletManager.HOUSE );
LeafletManager.executeCommand( "take sword" );
LeafletManager.sword = true;
}
private static final void getStick()
{
if ( LeafletManager.stick || LeafletManager.torch )
{
return;
}
LeafletManager.goTo( LeafletManager.PATH );
LeafletManager.executeCommand( "take stick" );
LeafletManager.stick = true;
}
private static final void cutHedge()
{
if ( LeafletManager.hedge )
{
return;
}
LeafletManager.getSword();
LeafletManager.goTo( LeafletManager.PATH );
if ( !LeafletManager.hedge )
{
LeafletManager.executeCommand( "cut hedge" );
}
}
private static final void openDoor()
{
if ( LeafletManager.door )
{
return;
}
LeafletManager.goTo( LeafletManager.FIELD );
if ( !LeafletManager.door )
{
LeafletManager.executeCommand( "open door" );
}
}
private static final void getTorch()
{
if ( LeafletManager.torch )
{
return;
}
LeafletManager.cutHedge();
if ( !LeafletManager.stick )
{
LeafletManager.getStick();
}
LeafletManager.goTo( LeafletManager.CLEARING );
LeafletManager.executeCommand( "light stick" );
LeafletManager.torch = true;
}
private static final void killSerpent()
{
if ( LeafletManager.serpent )
{
return;
}
LeafletManager.goTo( LeafletManager.CAVE );
if ( !LeafletManager.serpent )
{
LeafletManager.executeCommand( "kill serpent" );
}
}
private static final void wearBoots()
{
if ( LeafletManager.wornboots )
{
return;
}
LeafletManager.getBoots();
LeafletManager.executeCommand( "wear boots" );
LeafletManager.wornboots = true;
}
private static final void getBoots()
{
if ( LeafletManager.boots )
{
return;
}
LeafletManager.lightFire();
LeafletManager.executeCommand( "take boots" );
LeafletManager.boots = true;
}
private static final void lightFire()
{
LeafletManager.getTorch();
LeafletManager.goTo( LeafletManager.HOUSE );
if ( LeafletManager.fireplace )
{
return;
}
if ( !LeafletManager.parchment )
{
LeafletManager.executeCommand( "examine fireplace" );
LeafletManager.executeCommand( "examine tinder" );
LeafletManager.parchment = true;
}
LeafletManager.executeCommand( "light fireplace" );
LeafletManager.fireplace = true;
}
private static class LeafletException
extends RuntimeException
{
public LeafletException( final String s )
{
super( s == null ? "" : s );
}
}
}