/** * 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.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.KoLAdventure; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLConstants.MafiaState; import net.sourceforge.kolmafia.KoLConstants.Stat; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.RequestLogger; import net.sourceforge.kolmafia.RequestThread; import net.sourceforge.kolmafia.moods.RecoveryManager; import net.sourceforge.kolmafia.objectpool.EffectPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.persistence.AdventureDatabase; import net.sourceforge.kolmafia.persistence.ItemDatabase; import net.sourceforge.kolmafia.persistence.MonsterDatabase.Element; import net.sourceforge.kolmafia.persistence.QuestDatabase; import net.sourceforge.kolmafia.persistence.QuestDatabase.Quest; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.FightRequest; import net.sourceforge.kolmafia.request.GenericRequest; import net.sourceforge.kolmafia.request.PlaceRequest; import net.sourceforge.kolmafia.request.UseItemRequest; import net.sourceforge.kolmafia.swingui.CouncilFrame; import net.sourceforge.kolmafia.utilities.StringUtilities; public abstract class SorceressLairManager { private static final GenericRequest QUEST_HANDLER = new GenericRequest( "" ); // Patterns for repeated usage. private static final Pattern MAP_PATTERN = Pattern.compile( "usemap=\"#(\\w+)\"" ); // Items for the tower doorway private static final AdventureResult DIGITAL_KEY = ItemPool.get( ItemPool.DIGITAL_KEY, 1 ); private static final AdventureResult STAR_KEY = ItemPool.get( ItemPool.STAR_KEY, 1 ); private static final AdventureResult SKELETON_KEY = ItemPool.get( ItemPool.SKELETON_KEY, 1 ); private static final AdventureResult BORIS_KEY = ItemPool.get( ItemPool.BORIS_KEY, 1 ); private static final AdventureResult JARLSBERG_KEY = ItemPool.get( ItemPool.JARLSBERG_KEY, 1 ); private static final AdventureResult SNEAKY_PETE_KEY = ItemPool.get( ItemPool.SNEAKY_PETE_KEY, 1 ); private static final AdventureResult UNIVERSAL_KEY = ItemPool.get( ItemPool.UNIVERSAL_KEY, 1 ); private static final AdventureResult KEY_RING = ItemPool.get( ItemPool.SKELETON_KEY_RING, 1 ); private static final AdventureResult BALLOON = ItemPool.get( ItemPool.BALLOON_MONKEY, 1 ); // Items for the shadow battle private static final AdventureResult [] HEALING_ITEMS = new AdventureResult[] { ItemPool.get( ItemPool.RED_PIXEL_POTION, 1 ), ItemPool.get( ItemPool.FILTHY_POULTICE, 1 ), ItemPool.get( ItemPool.GAUZE_GARTER, 1 ), }; // Items for the chamber private static final AdventureResult CONFIDENCE = EffectPool.get( EffectPool.CONFIDENCE, Integer.MAX_VALUE ); public static final AdventureResult NAGAMAR = ItemPool.get( ItemPool.WAND_OF_NAGAMAR, 1 ); private static final String[][] CROWD2_DATA = { // The second crowd: Stat tests { "standing around flexing their muscles and using grip exercisers", "Strongest Adventurer", Stat.MUSCLE.toString(), }, { "sitting around playing chess and solving complicated-looking logic puzzles", "Smartest Adventurer", Stat.MYSTICALITY.toString(), }, { "all wearing sunglasses and dancing", "Smoothest Adventurer", Stat.MOXIE.toString(), }, }; private static final String[][] CROWD3_DATA = { // The third crowd: Elemental tests { "people, all of whom appear to be on fire", "Hottest Adventurer", Element.HOT.toString(), }, { "people, clustered around a group of igloos", "Coldest Adventurer", Element.COLD.toString(), }, { "people, surrounded by a cloud of eldritch mist", "Spookiest Adventurer", Element.SPOOKY.toString(), }, { "people, surrounded by garbage and clouds of flies", "Stinkiest Adventurer", Element.STENCH.toString(), }, { "greasy-looking people furtively skulking around", "Sleaziest Adventurer", Element.SLEAZE.toString(), }, }; private static final String[][] MAZE_TRAP1_DATA = { // The first maze trap { "smoldering bushes on the outskirts of a hedge maze", "Hot Damage", Element.HOT.toString(), }, { "frost-rimed bushes on the outskirts of a hedge maze", "Cold Damage", Element.COLD.toString(), }, { "creepy-looking black bushes on the outskirts of a hedge maze", "Spooky Damage", Element.SPOOKY.toString(), }, { "nasty-looking, dripping green bushes on the outskirts of a hedge maze", "Stench Damage", Element.STENCH.toString(), }, { "purplish, greasy-looking hedges", "Sleaze Damage", Element.SLEAZE.toString(), }, }; private static final String[][] MAZE_TRAP2_DATA = { // The second maze trap { "smoke rising from deeper within the maze", "Hot Damage", Element.HOT.toString(), }, { "wintry mists rising from deeper within the maze", "Cold Damage", Element.COLD.toString(), }, { "a miasma of eldritch vapors rising from deeper within the maze", "Spooky Damage", Element.SPOOKY.toString(), }, { "a cloud of green gas hovering over the maze", "Stench Damage", Element.STENCH.toString(), }, { "a greasy purple cloud hanging over the center of the maze", "Sleaze Damage", Element.SLEAZE.toString(), }, }; private static final String[][] MAZE_TRAP3_DATA = { // The third maze trap { "with lava slowly oozing out of it", "Hot Damage", Element.HOT.toString(), }, { "occasionally disgorging a bunch of ice cubes", "Cold Damage", Element.COLD.toString(), }, { "surrounded by creepy black mist", "Spooky Damage", Element.SPOOKY.toString(), }, { "disgorging a really surprising amount of sewage", "Stench Damage", Element.STENCH.toString(), }, { "that occasionally vomits out a greasy ball of hair", "Sleaze Damage", Element.SLEAZE.toString(), }, }; public static final int CROWD1 = 0; public static final int CROWD2 = 1; public static final int CROWD3 = 2; public static final int TRAP1 = 3; public static final int TRAP2 = 4; public static final int TRAP3 = 5; public static String[][] challengeToData( final int challenge ) { switch ( challenge ) { case SorceressLairManager.CROWD2: return SorceressLairManager.CROWD2_DATA; case SorceressLairManager.CROWD3: return SorceressLairManager.CROWD3_DATA; case SorceressLairManager.TRAP1: return SorceressLairManager.MAZE_TRAP1_DATA; case SorceressLairManager.TRAP2: return SorceressLairManager.MAZE_TRAP2_DATA; case SorceressLairManager.TRAP3: return SorceressLairManager.MAZE_TRAP3_DATA; default: return null; } } public static void parseChallenge( final int challenge, final String test, final String setting1, final String setting2 ) { Preferences.setString( setting1, test ); Preferences.setString( setting2, "none" ); String[][] data = SorceressLairManager.challengeToData( challenge ); if ( data == null ) { return; } for ( String [] entry : data ) { if ( test.equals( entry[ 0 ] ) ) { Preferences.setString( setting2, entry[ 2 ] ); return; } } } public static String getChallengeName( final int challenge ) { switch ( challenge ) { case SorceressLairManager.CROWD1: return "Crowd #1"; case SorceressLairManager.CROWD2: return "Crowd #2"; case SorceressLairManager.CROWD3: return "Crowd #3"; case SorceressLairManager.TRAP1: return "Maze Trap #1"; case SorceressLairManager.TRAP2: return "Maze Trap #2"; case SorceressLairManager.TRAP3: return "Maze Trap #3"; default: return "Unknown Challenge"; } } public static String getChallengeDescription( final int challenge, final String test ) { if ( challenge == SorceressLairManager.CROWD1) { return "Fastest Adventurer"; } String[][] data = SorceressLairManager.challengeToData( challenge ); if ( data == null ) { return "(bogus)"; } for ( String [] entry : data ) { if ( test.equals( entry[ 2 ] ) ) { return entry[ 1 ]; } } return "(" + test + ")"; } private static final Object[][] LOCK_DATA = { { SorceressLairManager.BORIS_KEY, "ns_lock1", }, { SorceressLairManager.JARLSBERG_KEY, "ns_lock2", }, { SorceressLairManager.SNEAKY_PETE_KEY, "ns_lock3", }, { SorceressLairManager.STAR_KEY, "ns_lock4", }, { SorceressLairManager.DIGITAL_KEY, "ns_lock5", }, { SorceressLairManager.SKELETON_KEY, "ns_lock6", }, }; public static AdventureResult lockToKey( final String lock ) { for ( Object [] row : SorceressLairManager.LOCK_DATA ) { if ( lock.equals( (String) row[ 1 ] ) ) { return (AdventureResult) row[ 0 ]; } } return null; } public static String keyToLock( final AdventureResult key ) { return SorceressLairManager.keyToLock( key.getName() ); } public static String keyToLock( final String keyName ) { for ( Object [] row : SorceressLairManager.LOCK_DATA ) { AdventureResult key = (AdventureResult) row[ 0 ]; if ( keyName.equals( key.getName() ) ) { return (String) row[ 1 ]; } } return null; } private static final Pattern RANK_PATTERN = Pattern.compile( "<b>#(\\d+)</b>" ); private static final Pattern OPTIMISM_PATTERN = Pattern.compile( "You feel (.*?) about your chances in the (.*?) Adventurer contest" ); private static final Pattern ENTERED_PATTERN = Pattern.compile( ""You already entered the (.*?) Adventurer contest.*?"", Pattern.DOTALL ); private static final Pattern QUEUED_PATTERN = Pattern.compile( "there are (\\d+) (Adventurers|other Adventurers|of them)" ); private static String contestToStat( final String contest ) { return contest.equals( "Strongest" ) ? Stat.MUSCLE.toString() : contest.equals( "Smartest" ) ? Stat.MYSTICALITY.toString() : contest.equals( "Smoothest" ) ? Stat.MOXIE.toString() : null; } private static String contestToElement( final String contest ) { return contest.equals( "Hottest" ) ? Element.HOT.toString() : contest.equals( "Coldest" ) ? Element.COLD.toString() : contest.equals( "Spookiest" ) ? Element.SPOOKY.toString() : contest.equals( "Stinkiest" ) ? Element.STENCH.toString() : contest.equals( "Sleaziest" ) ? Element.SLEAZE.toString() : null; } public static void parseContestBooth( final int decision, final String responseText ) { if ( decision == 4 ) { // Claim your prize if ( responseText.contains( "World's Best Adventurer sash" ) ) { QuestDatabase.setQuestProgress( Quest.FINAL, "step3" ); } return; } QuestDatabase.setQuestIfBetter( Quest.FINAL, QuestDatabase.STARTED ); // Are we entering a contest? if ( decision >= 1 && decision <= 3 ) { // According to my evaluation, you qualify to start at rank // <b>#N</b> in the <attribute> Adventurer contest. // // The man wraps a measuring tape around various parts of your // body and declares that you are qualified to begin the // contest at rank <b>#N</b>. // // The man peers at you through a magnifying glass for a little // while, then writes <b>#N</b> on his clipboard. Matcher rankMatcher = SorceressLairManager.RANK_PATTERN.matcher( responseText ); if ( rankMatcher.find() ) { String setting = "nsContestants" + String.valueOf( decision ); int value = StringUtilities.parseInt( rankMatcher.group( 1 ) ); Preferences.setInteger( setting, value - 1 ); } return; } // You feel <feeling> about your chances in the <attribute> Adventurer contest. Matcher optimismMatcher = SorceressLairManager.OPTIMISM_PATTERN.matcher( responseText ); while ( optimismMatcher.find() ) { String mood = optimismMatcher.group( 1 ); String contest = optimismMatcher.group( 2 ); if ( contest.equals( "Fastest" ) ) { continue; } String stat = SorceressLairManager.contestToStat( contest ); if ( stat != null ) { Preferences.setString( "nsChallenge1", stat ); continue; } String element = SorceressLairManager.contestToElement( contest ); if ( element != null ) { Preferences.setString( "nsChallenge2", element ); continue; } } // "You already entered the <attribute> Adventurer contest. You // should go get in line and wait for it to start. My clipboard // here says that there are X Adventurers in the contest // besides you." // // "You already entered the <attribute> Adventurer contest. You // should go wait with the other entrants. According to my // clipboard here, there are X other Adventurers waiting." // // "You already entered the <attribute> Adventurer contest. You // should go wait in line with the other Adventurers. It says // here that there are X of them besides you" // // "You already entered the Fastest Adventurer contest. You // should go get in line and wait for it to start. It says // here that the contest is currently you and one other // Adventurer. Hey, a 50/50 chance is pretty good, eh?" // // "You already entered the <attribute> Adventurer contest. // You should go wait in line with the other Adventurers. It // says on my clipboard that only one other Adventurer besides // you entered this one. So you should go wait in line with // the other Adventurer, is what I meant to say." // // "You already entered the <attribute> Adventurer contest. You // should go wait with the other entrants. It says here that // there's only one other person in that contest, actually. So // go wait with the... entrant." // // "You already entered the <attribute> Adventurer contest. You // should go get in line and wait for it to start. Wait -- my // clipboard says that you're the only Adventurer who // entered. That can't be right, can it? Well, if it's true, // then I guess you're definitely going to win!" // // "You already entered the <attribute> Adventurer contest. You // should go wait in line with the other Adventurers. Actually, // wait -- it says here on my clipboard that you're the only // entrant, so I guess you win that one by default." Matcher enteredMatcher = SorceressLairManager.ENTERED_PATTERN.matcher( responseText ); while ( enteredMatcher.find() ) { String contest = enteredMatcher.group( 1 ); String text = enteredMatcher.group( 0 ); Matcher queuedMatcher = SorceressLairManager.QUEUED_PATTERN.matcher( text ); int queue = queuedMatcher.find() ? StringUtilities.parseInt( queuedMatcher.group( 1 ) ) : ( text.contains( "you and one other" ) || text.contains( "only one other Adventurer besides you" ) || text.contains( "only one other person in that contest" ) ) ? 1 : text.contains( "only Adventurer" ) || text.contains( "only entrant" ) ? 0 : -1; if ( contest.equals( "Fastest" ) ) { Preferences.setInteger( "nsContestants1", queue ); continue; } String stat = SorceressLairManager.contestToStat( contest ); if ( stat != null ) { Preferences.setInteger( "nsContestants2", queue ); Preferences.setString( "nsChallenge1", stat ); continue; } String element = SorceressLairManager.contestToElement( contest ); if ( element != null ) { Preferences.setInteger( "nsContestants3", queue ); Preferences.setString( "nsChallenge2", element ); continue; } } } public static void parseMazeTrap( final int choice, final String responseText ) { // If we experienced the effect of a maze trap, remember the // element. String setting = choice == 1005 ? // 'Allo "nsChallenge3" : choice == 1008 ? // Pooling Your Resources "nsChallenge4" : choice == 1011 ? // Of Mouseholes and Manholes "nsChallenge5" : null; if ( setting == null ) { return; } String element = responseText.contains( "hot damage" ) ? Element.HOT.toString() : responseText.contains( "cold damage" ) ? Element.COLD.toString() : responseText.contains( "spooky damage" ) ? Element.SPOOKY.toString() : responseText.contains( "stench damage" ) ? Element.STENCH.toString() : responseText.contains( "sleaze damage" ) ? Element.SLEAZE.toString() : null; if ( element == null ) { return; } Preferences.setString( setting, element ); } public static void parseTowerResponse( final String action, final String responseText ) { // King Ralph the XI stands before you in all his regal glory. // "I'm sorry, adventurer," he says, "but the king is in // another castle." Then he breaks into a hearty chuckle. "Well // done, adventurer! You laid the smack down on that skank with // admirable derring-do and panache. I am eternally in your debt." if ( action.equals( "ns_11_prism" ) && responseText.contains( "King Ralph the XI stands before you in all his regal glory" ) ) { // Freeing the king finishes the quest. QuestDatabase.setQuestProgress( Quest.FINAL, QuestDatabase.FINISHED ); KoLCharacter.liberateKing(); return; } // Other "actions" all redirect to a choice or fight, if they // are allowed, otherwise simply return the tower image, as // does a request to see the tower with no action. Parse the // image and see what we can glean. SorceressLairManager.parseTower( responseText ); } public static void parseTowerDoorResponse( final String action, final String responseText ) { if ( action == null || action.equals( "" ) ) { SorceressLairManager.parseTowerDoor( responseText ); return; } if ( action.equals( "ns_doorknob" ) ) { // You turn the knob and the door vanishes. I guess it was made out of the same material as those weird lock plates. if ( responseText.contains( "You turn the knob and the door vanishes" ) ) { QuestDatabase.setQuestProgress( Quest.FINAL, "step6" ); } return; } AdventureResult lock = SorceressLairManager.lockToKey( action ); if ( lock == null ) { return; } AdventureResult key = responseText.contains( "universal key" ) ? SorceressLairManager.UNIVERSAL_KEY : lock; // You place Boris's key in the lock and turn it. You hear a // jolly bellowing in the distance as the lock vanishes, along // with the metal plate it was attached to. Huh. // You put Jarlsberg's key in the lock and turn it. You hear a // nasal, sort of annoying laugh in the distance as the lock // vanishes in a puff of rotten-egg-smelling smoke. // You put the key in the lock and hear the roar of a // motorcycle behind you. By the time you turn around to check // out the cool motorcycle guy he's gone, but when you turn // back to the lock it is <i>also</i> gone. // You put the key in and turn it. There is a flash of // brilliant starlight accompanied by a competent but not // exceptional drum solo, and when both have faded, the lock is // gone. // You put the skeleton key in the lock and turn it. The key, // the lock, and the metal plate the lock is attached to all // crumble to dust. And rust, in the case of the metal. // You put the digital key in the lock and turn it. A familiar // sequence of eight tones plays as the lock disappears. if ( responseText.contains( "the lock vanishes" ) || responseText.contains( "turn back to the lock" ) || responseText.contains( "the lock is gone" ) || responseText.contains( "crumble to dust" ) || responseText.contains( "the lock disappears" ) ) { ResultProcessor.processResult( key.getNegation() ); String keys = Preferences.getString( "nsTowerDoorKeysUsed" ); Preferences.setString( "nsTowerDoorKeysUsed", keys + ( keys.equals( "" ) ? "" : "," ) + lock.getDataName() ); } } public static void parseTowerDoor( String responseText ) { // Based on which locks are absent, deduce which keys have been used. StringBuilder buffer = new StringBuilder(); int keys = 0; for ( Object [] row : SorceressLairManager.LOCK_DATA ) { String lock = (String) row[ 1 ]; if ( !responseText.contains( lock ) ) { AdventureResult key = (AdventureResult) row[ 0 ]; buffer.append( keys++ > 0 ? "," : "" ); buffer.append( key.getDataName() ); } } Preferences.setString( "nsTowerDoorKeysUsed", buffer.toString() ); } public static boolean registerRequest( final String urlString ) { if ( !urlString.startsWith( "place.php" ) ) { return false; } String place = GenericRequest.getPlace( urlString ); if ( place == null ) { return false; } String message = null; if ( place.equals( "nstower" ) ) { String action = GenericRequest.getAction( urlString ); if ( action == null ) { // Nothing to log for simply looking at the tower. return true; } if ( action.equals( "ns_10_sorcfight" ) ) { // You are about to confront Her Naughtiness // Clear effects other than Confidence! SorceressLairManager.enterSorceressFight(); // Let KoLAdventure claim this return false; } if ( action.equals( "ns_03_hedgemaze" ) ) { // The hedgemaze is an adventure location. // However, visiting this place/action // will redirect to a choice and we // will log it with room number. // // Therefore, claim this and defer logging. return true; } message = action.equals( "ns_01_contestbooth" ) ? "Tower: Contest Booth" : action.equals( "ns_02_coronation" ) ? "Tower: Closing Ceremony" : action.equals( "ns_11_prism" ) ? ( "[" + KoLAdventure.getAdventureCount() + "] Freeing King Ralph" ) : null; if ( message == null ) { // Everything else is a KoLAdventure return false; } RequestLogger.printLine(); RequestLogger.updateSessionLog(); } else if ( place.equals( "nstower_door" ) ) { String action = GenericRequest.getAction( urlString ); if ( action == null ) { String prefix = "[" + KoLAdventure.getAdventureCount() + "] "; RequestLogger.printLine(); RequestLogger.updateSessionLog(); message = prefix + "Tower Door"; } else { message = action.equals( "ns_lock1" ) ? "Tower Door: Boris's lock" : action.equals( "ns_lock2" ) ? "Tower Door: Jarlsberg's lock" : action.equals( "ns_lock3" ) ? "Tower Door: Sneaky Pete's lock" : action.equals( "ns_lock4" ) ? "Tower Door: star lock" : action.equals( "ns_lock5" ) ? "Tower Door: digital lock" : action.equals( "ns_lock6" ) ? "Tower Door: skeleton lock" : action.equals( "ns_doorknob" ) ? "Tower Door: doorknob" : null; } } else { // Let any other "place" be claimed by other classes. return false; } if ( message == null ) { return false; } RequestLogger.printLine( message ); RequestLogger.updateSessionLog( message ); return true; } public static boolean registerChoice( final int choice, final String urlString ) { Matcher matcher = ChoiceManager.URL_OPTION_PATTERN.matcher( urlString ); int option = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; String message = null; switch ( choice ) { case 1003: // Test Your Might And Also Test Other Things if ( option >= 1 && option <= 3 ) { int challenge = option - 1; String test = challenge == 0 ? "" : Preferences.getString( "nsChallenge" + challenge ); String description = SorceressLairManager.getChallengeDescription( challenge, test ); message = "Registering for the " + description + " Contest"; } else if ( option == 4 ) { message = "Claiming your prize"; } else { return true; } RequestLogger.printLine( message ); RequestLogger.updateSessionLog( message ); return true; case 1015: // The Mirror in the Tower has the View that is True case 1020: // Closing Ceremony case 1021: // Meet Frank case 1022: // Meet Frank // Don't log the URL for these; the encounter suffices return true; case 1005: // 'Allo case 1006: // One Small Step For Adventurer case 1007: // Twisty Little Passages, All Hedge case 1008: // Pooling Your Resources case 1009: // Good Ol' 44% Duck case 1010: // Another Day, Another Fork case 1011: // Of Mouseholes and Manholes case 1012: // The Last Temptation case 1013: // Mazel Tov! // Don't log the URL for these; the encounter suffices return true; } return false; } public static void visitChoice( final int choice, final String responseText ) { switch ( choice ) { case 1005: // 'Allo case 1006: // One Small Step For Adventurer case 1007: // Twisty Little Passages, All Hedge case 1008: // Pooling Your Resources case 1009: // Good Ol' 44% Duck case 1010: // Another Day, Another Fork case 1011: // Of Mouseholes and Manholes case 1012: // The Last Temptation case 1013: // Mazel Tov! KoLAdventure.setLastAdventure( AdventureDatabase.getAdventure( "The Hedge Maze" ) ); QuestDatabase.setQuestProgress( Quest.FINAL, "step4" ); Preferences.setInteger( "currentHedgeMazeRoom", choice - 1004 ); break; } } public static void enterSorceressFight() { // We retain (some) intrinsic effects. In particular, Confidence! boolean isConfident = KoLConstants.activeEffects.contains( SorceressLairManager.CONFIDENCE ); KoLConstants.activeEffects.clear(); if ( isConfident ) { KoLConstants.activeEffects.add( SorceressLairManager.CONFIDENCE ); } } private static final String[][] TOWER_DATA = { { "crowd1.gif", "step1", }, { "crowd2.gif", "step1", }, { "crowd3.gif", "step1", }, { "nstower_regdesk.gif", QuestDatabase.STARTED, }, { "nstower_courtyard.gif", "step3", }, { "nstower_hedgemaze.gif", "step4", }, { "nstower_towerdoor.gif", "step5", }, { "nstower_tower1.gif", "step6", }, { "nstower_tower2.gif", "step7", }, { "nstower_tower3.gif", "step8", }, { "nstower_tower4.gif", "step9", }, { "nstower_tower5.gif", "step10", }, { "chamberlabel.gif", "step11", }, { "kingprism", "step12", }, { "gash.gif", QuestDatabase.FINISHED, }, }; public static void parseTower( String responseText ) { String step = QuestDatabase.UNSTARTED; for ( String[] spot : SorceressLairManager.TOWER_DATA ) { if ( responseText.contains( spot[0] ) ) { step = spot[1]; break; } } QuestDatabase.setQuestIfBetter( Quest.FINAL, step ); // If step is "step1", the following images might or might not // be present // // crowd1.gif // crowd2.gif // crowd3.gif // // However, if absent, you may have not yet entered that // contest or you may have finished it, and there is no // indication of how far you are in the crowd. // If we are past the contests, mark them all finished. if ( QuestDatabase.isQuestLaterThan( Quest.FINAL, "step1" ) ) { Preferences.setInteger( "nsContestants1", 0 ); Preferences.setInteger( "nsContestants2", 0 ); Preferences.setInteger( "nsContestants3", 0 ); } } // Quest scripts public static final int HEDGE_MAZE_TRAPS = 1; public static final int HEDGE_MAZE_GOPHER_DUCK = 2; public static final int HEDGE_MAZE_CHIHUAHUA_KIWI = 3; public static final int HEDGE_MAZE_NUGGLETS = 4; public static final void hedgeMazeTrapsScript() { SorceressLairManager.hedgeMazeScript( SorceressLairManager.HEDGE_MAZE_TRAPS ); } public static final void hedgeMazeGopherDuckScript() { SorceressLairManager.hedgeMazeScript( SorceressLairManager.HEDGE_MAZE_GOPHER_DUCK ); } public static final void hedgeMazeChihuahuaKiwiScript() { SorceressLairManager.hedgeMazeScript( SorceressLairManager.HEDGE_MAZE_CHIHUAHUA_KIWI ); } public static final void hedgeMazeNuggletsScript() { SorceressLairManager.hedgeMazeScript( SorceressLairManager.HEDGE_MAZE_NUGGLETS ); } public static final void hedgeMazeScript( final String tag ) { int mode = tag.equals( "traps" ) ? SorceressLairManager.HEDGE_MAZE_TRAPS : ( tag.equals( "gopher" ) || tag.equals( "duck" ) ) ? SorceressLairManager.HEDGE_MAZE_GOPHER_DUCK : ( tag.equals( "chihuahua" ) || tag.equals( "kiwi" ) ) ? SorceressLairManager.HEDGE_MAZE_CHIHUAHUA_KIWI : tag.equals( "nugglets" ) ? SorceressLairManager.HEDGE_MAZE_NUGGLETS : 0; if ( mode == 0 ) { KoLmafia.updateDisplay( MafiaState.ERROR, "What do you mean by '" + tag + "'?" ); return; } SorceressLairManager.hedgeMazeScript( mode ); } private static final void hedgeMazeScript( final int mode ) { // Is the Hedge maze open? Go look at the tower. RequestThread.postRequest( new PlaceRequest( "nstower" ) ); String status = Quest.FINAL.getStatus(); if ( !status.equals( "step4" ) ) { String message = status.equals( QuestDatabase.UNSTARTED ) ? "You haven't been given the quest to fight the Sorceress!" : QuestDatabase.isQuestLaterThan( status, "step4" ) ? "You have already completed the Hedge Maze." : "You haven't reached the Hedge Maze yet."; KoLmafia.updateDisplay( MafiaState.ERROR, message ); return; } // The Hedge Maze is available switch ( mode ) { case HEDGE_MAZE_TRAPS: // This is the expected path, entering in room 1 Preferences.setInteger( "choiceAdventure1005", 2 ); // 'Allo Preferences.setInteger( "choiceAdventure1008", 2 ); // Pooling Your Resources Preferences.setInteger( "choiceAdventure1011", 2 ); // Of Mouseholes and Manholes Preferences.setInteger( "choiceAdventure1013", 1 ); // Mazel Tov! // If the user is already part way into the maze, the // following will eventually get him back on track. Preferences.setInteger( "choiceAdventure1006", 1 ); // One Small Step For Adventurer Preferences.setInteger( "choiceAdventure1007", 1 ); // Twisty Little Passages, All Hedge Preferences.setInteger( "choiceAdventure1009", 1 ); // Good Ol' 44% Duck Preferences.setInteger( "choiceAdventure1010", 1 ); // Another Day, Another Fork Preferences.setInteger( "choiceAdventure1012", 1 ); // The Last Temptation break; case HEDGE_MAZE_GOPHER_DUCK: // This is the expected path, entering in room 1 Preferences.setInteger( "choiceAdventure1005", 1 ); // 'Allo Preferences.setInteger( "choiceAdventure1006", 2 ); // One Small Step For Adventurer Preferences.setInteger( "choiceAdventure1008", 1 ); // Pooling Your Resources Preferences.setInteger( "choiceAdventure1009", 2 ); // Good Ol' 44% Duck Preferences.setInteger( "choiceAdventure1011", 1 ); // Of Mouseholes and Manholes Preferences.setInteger( "choiceAdventure1012", 1 ); // The Last Temptation Preferences.setInteger( "choiceAdventure1013", 1 ); // Mazel Tov! // If the user is already part way into the maze, the // following will eventually get him back on track. Preferences.setInteger( "choiceAdventure1007", 1 ); // Twisty Little Passages, All Hedge Preferences.setInteger( "choiceAdventure1010", 1 ); // Another Day, Another Fork break; case HEDGE_MAZE_CHIHUAHUA_KIWI: // This is the expected path, entering in room 1 Preferences.setInteger( "choiceAdventure1005", 1 ); // 'Allo Preferences.setInteger( "choiceAdventure1006", 1 ); // One Small Step For Adventurer Preferences.setInteger( "choiceAdventure1007", 2 ); // Twisty Little Passages, All Hedge Preferences.setInteger( "choiceAdventure1009", 1 ); // Good Ol' 44% Duck Preferences.setInteger( "choiceAdventure1010", 2 ); // Another Day, Another Fork Preferences.setInteger( "choiceAdventure1012", 1 ); // The Last Temptation Preferences.setInteger( "choiceAdventure1013", 1 ); // Mazel Tov! // If the user is already part way into the maze, the // following will eventually get him back on track. Preferences.setInteger( "choiceAdventure1008", 1 ); // Pooling Your Resources Preferences.setInteger( "choiceAdventure1011", 1 ); // Of Mouseholes and Manholes break; case HEDGE_MAZE_NUGGLETS: // This is the expected path, entering in room 1 Preferences.setInteger( "choiceAdventure1005", 1 ); // 'Allo Preferences.setInteger( "choiceAdventure1006", 1 ); // One Small Step For Adventurer Preferences.setInteger( "choiceAdventure1007", 1 ); // Twisty Little Passages, All Hedge Preferences.setInteger( "choiceAdventure1008", 1 ); // Pooling Your Resources Preferences.setInteger( "choiceAdventure1009", 1 ); // Good Ol' 44% Duck Preferences.setInteger( "choiceAdventure1010", 1 ); // Another Day, Another Fork Preferences.setInteger( "choiceAdventure1011", 1 ); // Of Mouseholes and Manholes Preferences.setInteger( "choiceAdventure1012", 1 ); // The Last Temptation Preferences.setInteger( "choiceAdventure1013", 1 ); // Mazel Tov! break; default: KoLmafia.updateDisplay( MafiaState.ERROR, "Internal error: unknown mode (" + mode + ")." ); return; } // See if we have enough turns available. int currentRoom = Preferences.getInteger( "currentHedgeMazeRoom" ); int turns = 0; int room = 1005 + ( currentRoom >= 1 && currentRoom <= 9 ? ( currentRoom - 1 ) : 0 ); while ( room <= 1013 ) { // Visiting the current room takes a turn. turns++; // Going left always takes 1 turn if ( Preferences.getInteger( "choiceAdventure" + room ) == 1 ) { room++; continue; } // Going right takes more depending on which room it is room += room == 1005 ? 3 : // Trap 1 room == 1006 ? 2 : // topiary gopher room == 1007 ? 2 : // topiary chihuahua herd room == 1008 ? 3 : // Trap 2 room == 1009 ? 2 : // topiary duck room == 1010 ? 2 : // topiary kiwi room == 1011 ? 2 : // Trap 3 1; } KoLmafia.updateDisplay( "You are currently in room " + currentRoom + " and it will take you " + turns + " turns to clear the maze." ); // *** Check turns remaining // If it is all traps, assess readiness if ( mode == SorceressLairManager.HEDGE_MAZE_TRAPS ) { // First trap takes 90% of maximum HP // Second trap takes 80% of maximum HP // Third trap takes 70% of maximum HP // // With no resistances, you will lose 90% + 70% + 50% = // 240% of your maximum HP. // // Elemental resistances ameliorate that. Resistance // Level 7 reduces elemental damage by ~61%. // 240 * .39 = 93.6% // // That suffices. Level 6 does not. // // Note that if you have a telescope (or have failed a // trap before) we may know the specific element(s) of // the traps. // // For now, assume that the user has already prepared // sufficiently to pass. } // Unless it's all nugglets, all the time, heal up first. if ( mode != SorceressLairManager.HEDGE_MAZE_NUGGLETS ) { RecoveryManager.recoverHP( KoLCharacter.getMaximumHP() ); if ( !KoLmafia.permitsContinue() ) { return; } } KoLmafia.updateDisplay( "Entering the Hedge Maze..." ); while ( status.equals( "step4" ) ) { GenericRequest request = new PlaceRequest( "nstower", "ns_03_hedgemaze" ); RequestThread.postRequest( request ); if ( !KoLmafia.permitsContinue() ) { // Presumably, we got beaten up by a fight or // trap, and an error message was displayed. return; } // *** If we won a fight, will we redirect into the choice? // *** What if we ran out of turns? if ( request.responseText.contains( "You don't have time" ) ) { KoLmafia.updateDisplay( MafiaState.ERROR, "You're out of adventures." ); return; } status = Quest.FINAL.getStatus(); } if ( status.equals( "step5" ) ) { KoLmafia.updateDisplay( "Hedge Maze cleared!" ); } else { KoLmafia.updateDisplay( "Hedge Maze not complete. Unexpected quest status: " + Quest.FINAL.getPref() + " = " + status ); } } public static final void towerDoorScript() { // Is the Tower Door open? Go look at the tower. RequestThread.postRequest( new PlaceRequest( "nstower" ) ); String status = Quest.FINAL.getStatus(); if ( !status.equals( "step5" ) ) { String message = status.equals( QuestDatabase.UNSTARTED ) ? "You haven't been given the quest to fight the Sorceress!" : QuestDatabase.isQuestLaterThan( status, "step5" ) ? "You have already opened the Tower Door." : "You haven't reached the Tower Door yet."; KoLmafia.updateDisplay( MafiaState.ERROR, message ); return; } // Look at the door to decide what remains to be done RequestThread.postRequest( new PlaceRequest( "nstower_door" ) ); String keys = Preferences.getString( "nsTowerDoorKeysUsed" ); ArrayList<Object[]> needed = new ArrayList<Object[]>(); for ( Object[] row : SorceressLairManager.LOCK_DATA ) { AdventureResult key = (AdventureResult) row[ 0 ]; if ( !keys.contains( key.getName() ) ) { needed.add( row ); } } // If we have any locks left to open, acquire the correct key and unlock them if ( needed.size() > 0 ) { // First acquire all needed keys for ( Object[] row : needed ) { AdventureResult key = (AdventureResult) row[ 0 ]; if ( !InventoryManager.retrieveItem( key ) ) { KoLmafia.updateDisplay( MafiaState.ERROR, "Failed to acquire " + key ); return; } } // Then unlock each lock for ( Object[] row : needed ) { AdventureResult key = (AdventureResult) row[ 0 ]; String keyName = key.getName(); String action = (String) row[ 1 ]; RequestThread.postRequest( new PlaceRequest( "nstower_door", action ) ); keys = Preferences.getString( "nsTowerDoorKeysUsed" ); if ( !keys.contains( keyName ) ) { KoLmafia.updateDisplay( MafiaState.ERROR, "Failed to open lock using " + key ); return; } } } // Now turn the doorknob RequestThread.postRequest( new PlaceRequest( "nstower_door", "ns_doorknob", true ) ); status = Quest.FINAL.getStatus(); if ( status.equals( "step6" ) ) { KoLmafia.updateDisplay( "Tower Door open!" ); } } }