/** * 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.Arrays; 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.RequestLogger; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.utilities.StringUtilities; import net.sourceforge.kolmafia.webui.RelayLoader; public abstract class VioletFogManager { private static final Pattern CHOICE_PATTERN = Pattern.compile( "whichchoice value=(\\d+)" ); // Range of choice numbers within the fog private static final int FIRST_CHOICE = 48; private static final int LAST_CHOICE = 70; // The various locations within the violet fog private static final String FogLocationNames [] = { "Violet Fog (Start)", // 48 "Man on Bicycle", // 49 "Pleasant-faced Man", // 50 "Man on Cornflake", // 51 "Giant Chessboard", // 52 "Mustache", // 53 "Birds", // 54 "Machine Elves", // 55 "Boat on a River", // 56 "Man in Sunglasses", // 57 "Caterpiller", // 58 "This is not a Pipe", // 59 "Chorus Girls", // 60 "Huge Mountain", // 61 "Eye with Hat", // 62 "Eye with Weapon", // 63 "Eye with Garment", // 64 "Muscle Training", // 65 "Myst Training", // 66 "Moxie Training", // 67 "Alcohol Fish", // 68 "Food Fish", // 69 "Medicine Fish", // 70 }; private static final int FogLocationExits [][] = { { 49, 50, 51 }, // 48 { 52, 53, 56 }, // 49 { 53, 54, 57 }, // 50 { 52, 54, 55 }, // 51 { 61, 65, 68 }, // 52 { 61, 66, 69 }, // 53 { 61, 67, 70 }, // 54 { 58, 65, 70 }, // 55 { 59, 66, 68 }, // 56 { 60, 67, 69 }, // 57 { 51, 52, 63 }, // 58 { 49, 53, 62 }, // 59 { 50, 54, 64 }, // 60 { 49, 50, 51 }, // 61 { 50, 52, 61 }, // 62 { 51, 53, 61 }, // 63 { 49, 54, 61 }, // 64 { 50, 51, 54 }, // 65 { 49, 51, 52 }, // 66 { 49, 50, 53 }, // 67 { 49, 50, 53 }, // 68 { 50, 51, 54 }, // 69 { 49, 51, 52 }, // 70 }; // The routing table. // // One row for each fog location (48 - 70) // Each row contains one tuple for each possible fog destination (49 - 70) // Each tuple contains the Next Hop and the Hop Count to get there private static int FogRoutingTable[][][]; private static final int[] routingTuple( final int source, final int destination ) { if ( source < VioletFogManager.FIRST_CHOICE || source > VioletFogManager.LAST_CHOICE || destination < VioletFogManager.FIRST_CHOICE + 1 || destination > VioletFogManager.LAST_CHOICE ) { return null; } return VioletFogManager.FogRoutingTable[ source - VioletFogManager.FIRST_CHOICE ][ destination - VioletFogManager.FIRST_CHOICE - 1 ]; } private static final int nextHop( final int source, final int destination ) { int[] tuple = VioletFogManager.routingTuple( source, destination ); return tuple == null ? -1 : tuple[ 0 ]; } static { VioletFogManager.buildRoutingTable(); // printRoutingTable(); } private static final void buildRoutingTable() { // Get a zeroed array to start things off. VioletFogManager.FogRoutingTable = new int[ VioletFogManager.LAST_CHOICE - VioletFogManager.FIRST_CHOICE + 1 ][ VioletFogManager.LAST_CHOICE - VioletFogManager.FIRST_CHOICE ][ 2 ]; int unfilled = ( VioletFogManager.LAST_CHOICE - VioletFogManager.FIRST_CHOICE + 1 ) * ( VioletFogManager.LAST_CHOICE - VioletFogManager.FIRST_CHOICE ); // Seed it with final destinations: next hop = -1 and hopcount = 0 for ( int source = VioletFogManager.FIRST_CHOICE + 1; source <= VioletFogManager.LAST_CHOICE; ++source ) { int tuple[] = VioletFogManager.routingTuple( source, source ); tuple[ 0 ] = -1; tuple[ 1 ] = 0; --unfilled; } // Seed it with exit destinations: next hop = destination and hopcount = 1 for ( int source = VioletFogManager.FIRST_CHOICE; source <= VioletFogManager.LAST_CHOICE; ++source ) { int exits[] = VioletFogManager.FogLocationExits[ source - VioletFogManager.FIRST_CHOICE ]; for ( int i = 0; i < exits.length; ++i ) { int destination = exits[ i ]; int tuple[] = VioletFogManager.routingTuple( source, destination ); tuple[ 0 ] = destination; tuple[ 1 ] = 1; --unfilled; } } // Now iterate over entire table calculating next hops and hopcounts while ( unfilled > 0 ) { int filled = 0; for ( int source = VioletFogManager.FIRST_CHOICE; source <= VioletFogManager.LAST_CHOICE; ++source ) { for ( int destination = VioletFogManager.FIRST_CHOICE + 1; destination <= VioletFogManager.LAST_CHOICE; ++destination ) { int tuple[] = VioletFogManager.routingTuple( source, destination ); // If we've calculated this tuple, skip it if ( tuple[ 0 ] != 0 ) { continue; } // See which of our direct exits can get there fastest int nextHop = 0; int hopCount = Integer.MAX_VALUE; int exits[] = VioletFogManager.FogLocationExits[ source - VioletFogManager.FIRST_CHOICE ]; for ( int i = 0; i < exits.length; ++i ) { int destTuple[] = VioletFogManager.routingTuple( exits[ i ], destination ); if ( destTuple[ 0 ] != 0 && destTuple[ 1 ] < hopCount ) { nextHop = exits[ i ]; hopCount = destTuple[ 1 ]; } } // If we found a route, enter it into table if ( nextHop != 0 ) { tuple[ 0 ] = nextHop; tuple[ 1 ] = hopCount + 1; ++filled; } } } if ( filled == 0 ) { RequestLogger.printLine( "Internal error: " + unfilled + " unreachable nodes in violet fog routing table" ); break; } unfilled -= filled; } } // Range of choice numbers with a goal private static final int FIRST_GOAL_LOCATION = 62; public static final String FogGoals [] = { "escape from the fog", // 48-61 "Cerebral Cloche", // 62 "Cerebral Crossbow", // 63 "Cerebral Culottes", // 64 "Muscle Training", // 65 "Mysticality Training", // 66 "Moxie Training", // 67 "ice stein", // 68 "munchies pill", // 69 "homeopathic healing powder", // 70 "Boost Prime Stat", "Boost Lowest Stat" }; private static final AdventureResult FogGoalItems[] = { null, ItemPool.get( ItemPool.C_CLOCHE, 1 ), ItemPool.get( ItemPool.C_CROSSBOW, 1 ), ItemPool.get( ItemPool.C_CULOTTES, 1 ), null, null, null, ItemPool.get( ItemPool.ICE_STEIN, 1 ), ItemPool.get( ItemPool.MUNCHIES_PILL, 1 ), ItemPool.get( ItemPool.HOMEOPATHIC, 1 ), }; // The choice table. // // One row for each fog location (48 - 70) // Each row contains four values, corresponding to choices 1 - 4 // // -1 The "goal" // 0 Unknown // xx A destination private static final int FogChoiceTable[][] = new int[ VioletFogManager.LAST_CHOICE - VioletFogManager.FIRST_CHOICE + 1 ][ 4 ]; public static final void reset() { // Reset what we've "learned" about the fog choices for ( int i = VioletFogManager.FIRST_CHOICE; i <= VioletFogManager.LAST_CHOICE; ++i ) { int choice[] = VioletFogManager.FogChoiceTable[ i - VioletFogManager.FIRST_CHOICE ]; choice[ 0 ] = i < VioletFogManager.FIRST_GOAL_LOCATION ? 0 : -1; choice[ 1 ] = 0; choice[ 2 ] = 0; choice[ 3 ] = i < VioletFogManager.FIRST_GOAL_LOCATION ? -1 : 0; } int lastVioletFogAscension = Preferences.getInteger( "lastVioletFogMap" ); if ( lastVioletFogAscension != KoLCharacter.getAscensions() ) { Preferences.setInteger( "lastVioletFogMap", KoLCharacter.getAscensions() ); Preferences.setString( "violetFogLayout", "" ); } String layout = Preferences.getString( "violetFogLayout" ); if ( layout.equals( "" ) ) { return; } int currentIndex = 0; String[] layoutSplit = layout.split( "," ); for ( int i = 0; i < VioletFogManager.FogChoiceTable.length; ++i ) { for ( int j = 0; j < VioletFogManager.FogChoiceTable[ i ].length; ++j ) { VioletFogManager.FogChoiceTable[ i ][ j ] = StringUtilities.parseInt( layoutSplit[ currentIndex++ ] ); } } } public static final void saveMap() { StringBuilder map = new StringBuilder(); for ( int i = 0; i < VioletFogManager.FogChoiceTable.length; ++i ) { for ( int j = 0; j < VioletFogManager.FogChoiceTable[ i ].length; ++j ) { if ( i != 0 || j != 0 ) { map.append( ',' ); } map.append( VioletFogManager.FogChoiceTable[ i ][ j ] ); } } Preferences.setInteger( "lastVioletFogMap", KoLCharacter.getAscensions() ); Preferences.setString( "violetFogLayout", map.toString() ); } private static final String currentGoalString() { int goal = Preferences.getInteger( "violetFogGoal" ); if ( goal < 0 || goal > 11 ) { return "unknown"; } if ( goal == 10 ) // Boost Prime Stat { goal = KoLCharacter.getPrimeIndex() + 4; } else if ( goal == 11 ) // Boost Lowest Stat { long mus = KoLCharacter.getTotalMuscle(); long mys = KoLCharacter.getTotalMysticality(); long mox = KoLCharacter.getTotalMoxie(); if ( mus <= mys && mus <= mox ) { goal = 4; } else if ( mys <= mus && mys <= mox ) { goal = 5; } else { goal = 6; } } return VioletFogManager.FogGoals[ goal ]; } public static final boolean fogChoice( final int choice ) { return choice >= VioletFogManager.FIRST_CHOICE && choice <= VioletFogManager.LAST_CHOICE; } public static final String handleChoice( final int source ) { // We only handle Violet Fog choices if ( !VioletFogManager.fogChoice( source ) ) { return ""; } // Get the user specified goal int goal = Preferences.getInteger( "violetFogGoal" ); for ( int i = 0; i < VioletFogManager.FogGoalItems.length; ++i ) { if ( VioletFogManager.FogGoalItems[ i ] != null && GoalManager.hasGoal( VioletFogManager.FogGoalItems[ i ] ) ) { goal = i; break; } } // If no goal, return "4". // - If we are not at a "goal" location, this will exit the fog // - If we are at a "goal" location, this will send us to a non-"goal" location if ( goal == 0 ) { return "4"; } if ( goal == 10 ) // Boost Prime Stat { goal = KoLCharacter.getPrimeIndex() + 4; } else if ( goal == 11 ) // Boost Lowest Stat { long mus = KoLCharacter.getTotalMuscle(); long mys = KoLCharacter.getTotalMysticality(); long mox = KoLCharacter.getTotalMoxie(); if ( mus <= mys && mus <= mox ) { goal = 4; } else if ( mys <= mus && mys <= mox ) { goal = 5; } else { goal = 6; } } // Find the location we must get to to achieve the goal int destination = VioletFogManager.FIRST_GOAL_LOCATION + goal - 1; if ( !VioletFogManager.fogChoice( destination ) ) { return ""; } // Are we there yet? if ( source == destination ) { // The first decision will get us the goal we seek return "1"; } // We haven't reached the goal yet. Find the next hop. int nextHop = VioletFogManager.nextHop( source, destination ); // Choose the path that will take us there int path[] = VioletFogManager.FogChoiceTable[ source - VioletFogManager.FIRST_CHOICE ]; for ( int i = 0; i < path.length; ++i ) { if ( path[ i ] == nextHop ) { return String.valueOf( i + 1 ); } } // We don't know how to get there. Pick an unexplored path. for ( int i = 0; i < path.length; ++i ) { if ( path[ i ] == 0 ) { // We don't know how to get to the Next Hop return String.valueOf( i + 1 ); } } // This shouldn't happen return ""; } public static final boolean mapChoice( final int lastChoice, final int lastDecision, final String text ) { if ( !VioletFogManager.fogChoice( lastChoice ) ) { return false; } // Punt if bogus decision if ( lastDecision < 1 || lastDecision > 4 ) { return true; } // Return if we've already mapped this decision if ( VioletFogManager.FogChoiceTable[ lastChoice - VioletFogManager.FIRST_CHOICE ][ lastDecision - 1 ] != 0 ) { return true; } Matcher choiceMatcher = VioletFogManager.CHOICE_PATTERN.matcher( text ); if ( !choiceMatcher.find() ) { return false; } int source = StringUtilities.parseInt( choiceMatcher.group( 1 ) ); // We only handle Violet Fog choices if ( !VioletFogManager.fogChoice( source ) ) { return false; } // Update the path table int choices[] = VioletFogManager.FogChoiceTable[ lastChoice - VioletFogManager.FIRST_CHOICE ]; choices[ lastDecision - 1 ] = source; VioletFogManager.saveMap(); // See if exactly one exit is unknown int unknownIndex = -1; for ( int i = 0; i < choices.length; ++i ) { if ( choices[ i ] != 0 ) { continue; } if ( unknownIndex != -1 ) { return true; } unknownIndex = i; } // Done if all three destinations are known. if ( unknownIndex == -1 ) { return true; } // Yes. Figure out which one it is int exits[] = VioletFogManager.FogLocationExits[ lastChoice - VioletFogManager.FIRST_CHOICE ]; for ( int i = 0; i < exits.length; ++i ) { int exit = exits[ i ]; boolean found = false; for ( int j = 0; j < choices.length; ++j ) { if ( exit == choices[ j ] ) { found = true; break; } } if ( !found ) { choices[ unknownIndex ] = exit; VioletFogManager.saveMap(); return true; } } return true; } public static final String[][] choiceSpoilers( final int choice ) { // We only handle Violet Fog choices if ( !VioletFogManager.fogChoice( choice ) ) { return null; } // Return an array with the same structure as used by built-in // choice adventures. String[][] result = new String[ 3 ][]; // The choice option is the first element result[ 0 ] = new String[ 1 ]; result[ 0 ][ 0 ] = "choiceAdventure" + String.valueOf( choice ); // The name of the choice is second element result[ 1 ] = new String[ 1 ]; result[ 1 ][ 0 ] = VioletFogManager.FogLocationNames[ choice - VioletFogManager.FIRST_CHOICE ]; // An array of choice spoilers is the third element int choices[] = VioletFogManager.FogChoiceTable[ choice - VioletFogManager.FIRST_CHOICE ]; result[ 2 ] = new String[ 4 ]; result[ 2 ][ 0 ] = VioletFogManager.choiceName( choice, choices[ 0 ] ); result[ 2 ][ 1 ] = VioletFogManager.choiceName( choice, choices[ 1 ] ); result[ 2 ][ 2 ] = VioletFogManager.choiceName( choice, choices[ 2 ] ); result[ 2 ][ 3 ] = VioletFogManager.choiceName( choice, choices[ 3 ] ); return result; } private static final String choiceName( final int choice, final int destination ) { // If it's unknown, no name if ( destination == 0 ) { return ""; } // If it's the Goal, pick the goal if ( destination == -1 ) { return choice < VioletFogManager.FIRST_GOAL_LOCATION ? VioletFogManager.FogGoals[ 0 ] : VioletFogManager.FogGoals[ choice - VioletFogManager.FIRST_GOAL_LOCATION + 1 ]; } // Otherwise, return the name of the destination return VioletFogManager.FogLocationNames[ destination - VioletFogManager.FIRST_CHOICE ]; } public static final boolean freeAdventure( final String choice, final String decision ) { // "choiceAdventureX" int source = StringUtilities.parseInt( choice.substring( 15 ) ); // Journey to the Center of your Mind if ( source == 71 ) { // We got diverted from where we thought we were going // Switch location to the trip of choice. String name = ""; if ( decision.equals( "1" ) ) { name = "An Incredibly Strange Place (Bad Trip)"; } else if ( decision.equals( "2" ) ) { name = "An Incredibly Strange Place (Mediocre Trip)"; } else if ( decision.equals( "3" ) ) { name = "An Incredibly Strange Place (Great Trip)"; } Preferences.setString( "chosenTrip", name ); KoLAdventure.setNextAdventure( name ); return true; } // Make sure it's a fog adventure if ( !VioletFogManager.fogChoice( source ) ) { return false; } // It is. If it's a "goal" location, decision "1" takes an adventure. return source < VioletFogManager.FIRST_GOAL_LOCATION ? true : !decision.equals( "1" ); } // The Wiki has a Violet Fog Map: // // http://kol.coldfront.net/thekolwiki/index.php/Violet_Fog_Map // // The Wiki's numbering scheme mapped to Choice Adventure number: // // 1 = 61 (Huge Mountain) // 2 = 49 (Man on Bicycle) // 3 = 52 (Giant Chessboard) // 4 = 68 (Alcohol Fish) // 5 = 56 (Boat on a River) // 6 = 50 (Pleasant-faced Man) // 7 = 53 (Mustache) // 8 = 51 (Man on Cornflake) // 9 = 55 (Machine Elves) // 10 = 70 (Medicine Fish) // 11 = 54 (Birds) // 12 = 67 (Moxie Training) // 13 = 57 (Man in Sunglasses) // 14 = 60 (Chorus Girls) // 15 = 64 (Eye with Garment) // 16 = 66 (Mysticality Training) // 17 = 65 (Muscle Training) // 18 = 69 (Food Fish) // 19 = 58 (Caterpillar) // 20 = 59 (This is not a Pipe) // 21 = 62 (Eye with Hat) // 22 = 63 (Eye with Weapon) private static final int WikiToMafia [] = { 61, // 1 49, // 2 52, // 3 68, // 4 56, // 5 50, // 6 53, // 7 51, // 8 55, // 9 70, // 10 54, // 11 67, // 12 57, // 13 60, // 14 64, // 15 66, // 16 65, // 17 69, // 18 58, // 19 59, // 20 62, // 21 63, // 22 }; private static final int mafiaCode( final int wikiCode ) { return VioletFogManager.WikiToMafia[ wikiCode - 1 ]; } private static final int MafiaToWiki [] = { 2, // 49 6, // 50 8, // 51 3, // 52 7, // 53 11, // 54 9, // 55 5, // 56 13, // 57 19, // 58 20, // 59 14, // 60 1, // 61 21, // 62 22, // 63 15, // 64 17, // 65 16, // 66 12, // 67 4, // 68 18, // 69 10, // 70 }; private static final int wikiCode( final int mafiaCode ) { return VioletFogManager.MafiaToWiki[ mafiaCode - VioletFogManager.FIRST_CHOICE - 1 ]; } private static int WikiFogLocationExits[][]; static { VioletFogManager.buildWikiExits(); } private static final void buildWikiExits() { // Get a zeroed array to start things off. VioletFogManager.WikiFogLocationExits = new int[ VioletFogManager.LAST_CHOICE - VioletFogManager.FIRST_CHOICE ][ 3 ]; // Examine each node in Mafia order for ( int source = VioletFogManager.FIRST_CHOICE + 1; source <= VioletFogManager.LAST_CHOICE; ++source ) { // Get the array of exit paths int mafiaExits[] = VioletFogManager.FogLocationExits[ source - VioletFogManager.FIRST_CHOICE ]; int wikiExits[] = VioletFogManager.WikiFogLocationExits[ VioletFogManager.wikiCode( source ) - 1 ]; // Copy translated exit from Mafia exit table to Wiki exit table for ( int i = 0; i < mafiaExits.length; ++i ) { wikiExits[ i ] = VioletFogManager.wikiCode( mafiaExits[ i ] ); } // Sort the exits in Wiki order Arrays.sort( wikiExits ); } } // Gemelli has a tool that accepts a code and displays the map // corresponding to it: // // originally: http://www.feesher.com/fog_mapper.php // now: http://fog.bewarethefgc.com // // To get the code, examine the 22 nodes in Wiki order // Examine each of the three destinations from each node, again in Wiki order. // Generate a digit from 0-3: // 0 = unmapped // 1 = this way // 2 = that way // 3 = the other way // // Take the resulting 66 digit string and convert two digits at a time // from base 4 to base 16, resulting in a 33 digit hex string public static final String gemelliCode() { int code[] = new int[ 66 ]; int codeIndex = 0; // Examine each node in Wiki order for ( int i = 1; i < VioletFogManager.FogChoiceTable.length; ++i ) { // Get the choice adventure # corresponding to the Wiki code int source = VioletFogManager.mafiaCode( i ); // Get the array of exit paths int paths[] = VioletFogManager.FogChoiceTable[ source - VioletFogManager.FIRST_CHOICE ]; // For each choice in Wiki order int exits[] = VioletFogManager.WikiFogLocationExits[ i - 1 ]; for ( int j = 0; j < exits.length; ++j ) { // Find the exit in the paths for ( int index = 0; index < paths.length; ++index ) { if ( paths[ index ] == VioletFogManager.mafiaCode( exits[ j ] ) ) { int choice = source < VioletFogManager.FIRST_GOAL_LOCATION ? index + 1 : index; code[ codeIndex ] = choice; break; } } ++codeIndex; } } // Convert the 66 element int array into a 33 character character array char data[] = new char[ 33 ]; for ( int i = 0; i < code.length; i += 2 ) { int hexDigit = code[ i ] * 4 + code[ i + 1 ]; data[ i / 2 ] = Character.forDigit( hexDigit, 16 ); } return String.valueOf( data ); } public static final void showGemelliMap() { RelayLoader.openSystemBrowser( "http://fog.bewarethefgc.com/index.php?mapstring=" + VioletFogManager.gemelliCode() ); } public static final void addGoalButton( final StringBuffer buffer ) { String goal = VioletFogManager.currentGoalString(); ChoiceManager.addGoalButton( buffer, goal ); } }