/** * 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.HashMap; import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.chat.ChatManager; import net.sourceforge.kolmafia.objectpool.IntegerPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.persistence.QuestDatabase; import net.sourceforge.kolmafia.persistence.QuestDatabase.Quest; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.session.ConsequenceManager; import net.sourceforge.kolmafia.session.InventoryManager; public class QuestLogRequest extends GenericRequest { private static final Pattern HEADER_PATTERN = Pattern.compile( "<b>([^<]*?[^>]*?)</b>(?:<p>|)<blockquote>", Pattern.DOTALL ); private static final Pattern BODY_PATTERN = Pattern.compile( "(?<=<b>)(.*?[^<>]*?)</b><br>(.*?)(?=<p>$|<p><b>|<p></blockquote>)", Pattern.DOTALL ); public QuestLogRequest() { super( "questlog.php" ); } private static final boolean finishedQuest( final String pref ) { return Preferences.getString( pref ).equals( QuestDatabase.FINISHED ); } public static final boolean isDungeonOfDoomAvailable() { return Preferences.getInteger( "lastPlusSignUnlock" ) == KoLCharacter.getAscensions() && !InventoryManager.hasItem( ItemPool.PLUS_SIGN ); } public static final boolean isWhiteCitadelAvailable() { String pref = Preferences.getString( Quest.CITADEL.getPref() ); return pref.equals( QuestDatabase.FINISHED ) || pref.equals( "step5" ) || pref.equals( "step6" ); } public static final boolean areFriarsAvailable() { return Preferences.getString( Quest.FRIAR.getPref() ).equals( QuestDatabase.FINISHED ); } public static final boolean isBlackMarketAvailable() { if ( Preferences.getInteger( "lastWuTangDefeated" ) == KoLCharacter.getAscensions() ) { return false; } if ( KoLCharacter.inNuclearAutumn() ) { return false; } String pref = Preferences.getString( Quest.MACGUFFIN.getPref() ); return pref.equals( QuestDatabase.FINISHED ) || pref.contains( "step" ); } public static final boolean isHippyStoreAvailable() { return !Preferences.getString( Quest.ISLAND_WAR.getPref() ).equals( "step1" ); } @Override public void run() { KoLmafia.updateDisplay( "Retrieving quest data..." ); // When KoL provides a link to the Quest log, it goes to the // section you visited last. Therefore, visit all sections but // end with page 1. this.addFormField( "which", "3" ); super.run(); this.addFormField( "which", "2" ); super.run(); this.addFormField( "which", "1" ); super.run(); } @Override protected boolean retryOnTimeout() { return true; } public static final void registerQuests( final boolean isExternal, final String urlString, final String responseText ) { if ( urlString.contains( "which=1" ) || urlString.contains( "which=7" ) || !urlString.contains( "which" ) ) { parseResponse( responseText, 1 ); } else if ( urlString.contains( "which=2" ) ) { parseResponse( responseText, 2 ); } else if ( urlString.contains( "which=3" ) ) { ConsequenceManager.parseAccomplishments( responseText ); ChatManager.setChatLiteracy( Preferences.getBoolean( "chatLiterate" ) ); } } private static void parseResponse( final String responseText, final int source ) { Matcher headers = QuestLogRequest.HEADER_PATTERN.matcher( responseText ); HashMap<Integer, String> map = new HashMap<Integer, String>(); while ( headers.find() ) { map.put( IntegerPool.get( headers.end() ), headers.group( 1 ) ); } Iterator<Integer> it = map.keySet().iterator(); while ( it.hasNext() ) { Integer key = it.next(); String header = map.get( key ); String cut = responseText.substring( key.intValue() ).split( "</blockquote>" )[ 0 ]; if ( header.equals( "Council Quests:" ) ) { handleQuestText( cut, source ); } else if ( header.equals( "Guild Quests:" ) ) { handleQuestText( cut, source ); } // First time I opened this today it said Miscellaneous quests, now says Other quests, so check for both else if ( header.equals( "Other Quests:" ) || header.equals( "Miscellaneous Quests:" ) ) { handleQuestText( cut, source ); } else { // encountered a section in questlog we don't know how to handle. } } // Some quests vanish when completed but can be inferred by the presence of a new one if ( QuestDatabase.isQuestLaterThan( Quest.SPOOKYRAVEN_BABIES, QuestDatabase.UNSTARTED ) ) { QuestDatabase.setQuestProgress( Quest.SPOOKYRAVEN_DANCE, QuestDatabase.FINISHED ); } if ( QuestDatabase.isQuestLaterThan( Quest.SPOOKYRAVEN_DANCE, QuestDatabase.UNSTARTED ) ) { QuestDatabase.setQuestProgress( Quest.SPOOKYRAVEN_NECKLACE, QuestDatabase.FINISHED ); } if ( QuestDatabase.isQuestLaterThan( Quest.MACGUFFIN, "step1" ) ) { QuestDatabase.setQuestProgress( Quest.BLACK, QuestDatabase.FINISHED ); } if ( QuestDatabase.isQuestLaterThan( Quest.WORSHIP, "step3" ) ) { QuestDatabase.setQuestProgress( Quest.CURSES, QuestDatabase.FINISHED ); QuestDatabase.setQuestProgress( Quest.DOCTOR, QuestDatabase.FINISHED ); QuestDatabase.setQuestProgress( Quest.BUSINESS, QuestDatabase.FINISHED ); QuestDatabase.setQuestProgress( Quest.SPARE, QuestDatabase.FINISHED ); } if ( QuestDatabase.isQuestLaterThan( Quest.PYRAMID, QuestDatabase.UNSTARTED ) ) { QuestDatabase.setQuestProgress( Quest.DESERT, QuestDatabase.FINISHED ); } // Set (mostly historical) preferences we can set based on quest status if ( QuestDatabase.isQuestLaterThan( Quest.MACGUFFIN, "step1" ) || QuestDatabase.isQuestFinished( Quest.MEATCAR ) ) { KoLCharacter.setDesertBeachAvailable(); } if ( QuestDatabase.isQuestLaterThan( Quest.ISLAND_WAR, QuestDatabase.STARTED ) || QuestDatabase.isQuestLaterThan( Quest.PIRATE, QuestDatabase.UNSTARTED ) || QuestDatabase.isQuestFinished( Quest.HIPPY ) ) { Preferences.setInteger( "lastIslandUnlock", KoLCharacter.getAscensions() ); } if ( QuestDatabase.isQuestFinished( Quest.SPOOKYRAVEN_NECKLACE ) ) { Preferences.setInteger( "lastSecondFloorUnlock", KoLCharacter.getAscensions() ); } if ( QuestDatabase.isQuestLaterThan( Quest.GARBAGE, "step7" ) ) { Preferences.setInteger( "lastCastleGroundUnlock", KoLCharacter.getAscensions() ); } if ( QuestDatabase.isQuestLaterThan( Quest.GARBAGE, "step8" ) ) { Preferences.setInteger( "lastCastleTopUnlock", KoLCharacter.getAscensions() ); } if ( QuestDatabase.isQuestLaterThan( Quest.WORSHIP, "step1" ) ) { Preferences.setInteger( "lastTempleButtonsUnlock", KoLCharacter.getAscensions() ); Preferences.setInteger( "lastTempleUnlock", KoLCharacter.getAscensions() ); } Preferences.setBoolean( "middleChamberUnlock", QuestDatabase.isQuestLaterThan( Quest.PYRAMID, QuestDatabase.STARTED ) ); Preferences.setBoolean( "lowerChamberUnlock", QuestDatabase.isQuestLaterThan( Quest.PYRAMID, "step1" ) ); Preferences.setBoolean( "controlRoomUnlock", QuestDatabase.isQuestLaterThan( Quest.PYRAMID, "step2" ) ); if ( !Preferences.getBoolean( "pyramidBombUsed" ) ) { Preferences.setBoolean( "pyramidBombUsed", QuestDatabase.isQuestFinished( Quest.PYRAMID ) ); } Preferences.setBoolean( "bigBrotherRescued", QuestDatabase.isQuestLaterThan( Quest.SEA_MONKEES, "step1" ) ); if ( QuestDatabase.isQuestFinished ( Quest.ISLAND_WAR ) ) { Preferences.setString( "warProgress", "finished" ); } else if ( QuestDatabase.isQuestLaterThan( Quest.ISLAND_WAR, QuestDatabase.STARTED ) ) { Preferences.setString( "warProgress", "started" ); } else { Preferences.setString( "warProgress", "unstarted" ); } } private static void handleQuestText( String response, int source ) { if ( source == 1 ) { Preferences.setString( "ghostLocation", "" ); } Matcher body = QuestLogRequest.BODY_PATTERN.matcher( response ); // Form of.. a regex! group(1) now contains the quest title and group(2) has the details. while ( body.find() ) { String title = body.group( 1 ); String details = body.group( 2 ); String pref = QuestDatabase.titleToPref( title ); String status = ""; status = QuestDatabase.findQuestProgress( pref, details ); // Debugging /*if ( !pref.equals( "" ) ) { RequestLogger.printLine( pref + " (" + status + ")" ); } else { RequestLogger.printLine( "unhandled: " + title ); }*/ // Once we've implemented everything, we can do some error checking to make sure we handled everything // successfully. if ( pref.equals( "" ) ) { /*KoLmafia.updateDisplay( KoLConstants.CONTINUE_STATE, "Unknown quest, or something went wrong while parsing questlog.php" );*/ continue; } if ( status.equals( "" ) ) { /*KoLmafia.updateDisplay( KoLConstants.CONTINUE_STATE, "Unknown quest status found while parsing questlog.php" );*/ continue; } /* * if ( source == 2 && !status.equals( "finished" ) ) * { * // Probably shouldn't happen. We were parsing the completed quests page but somehow didn't set a quest * to finished. Possible exception happens during nemesis quest. * KoLmafia.updateDisplay( KoLConstants.ERROR_STATE, * "Something went wrong while parsing completed quests" ); * return; * } */ QuestDatabase.setQuestProgress( pref, status ); } } public static boolean isTavernAvailable() { return QuestDatabase.isQuestLaterThan( Quest.RAT, QuestDatabase.STARTED ); } }