/** * 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; import java.awt.Color; import java.awt.Frame; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.io.RandomAccessFile; import java.math.BigInteger; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.List; import java.util.TimeZone; import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.SwingUtilities; import javax.swing.UIManager; import apple.dts.samplecode.osxadapter.OSXAdapter; import net.java.dev.spellcast.utilities.ActionPanel; import net.java.dev.spellcast.utilities.DataUtilities; import net.java.dev.spellcast.utilities.JComponentUtilities; import net.sourceforge.kolmafia.KoLConstants.CraftingType; import net.sourceforge.kolmafia.KoLConstants.MafiaState; import net.sourceforge.kolmafia.listener.NamedListenerRegistry; import net.sourceforge.kolmafia.listener.PreferenceListenerRegistry; import net.sourceforge.kolmafia.moods.RecoveryManager; import net.sourceforge.kolmafia.objectpool.EffectPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.persistence.BountyDatabase; import net.sourceforge.kolmafia.persistence.ConcoctionDatabase; import net.sourceforge.kolmafia.persistence.EffectDatabase; import net.sourceforge.kolmafia.persistence.EquipmentDatabase; import net.sourceforge.kolmafia.persistence.FamiliarDatabase; import net.sourceforge.kolmafia.persistence.FlaggedItems; import net.sourceforge.kolmafia.persistence.HolidayDatabase; import net.sourceforge.kolmafia.persistence.ItemDatabase; import net.sourceforge.kolmafia.persistence.QuestDatabase; import net.sourceforge.kolmafia.persistence.QuestDatabase.Quest; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.ApiRequest; import net.sourceforge.kolmafia.request.BountyHunterHunterRequest; import net.sourceforge.kolmafia.request.CafeRequest; import net.sourceforge.kolmafia.request.CampgroundRequest; import net.sourceforge.kolmafia.request.CharPaneRequest; import net.sourceforge.kolmafia.request.CharSheetRequest; import net.sourceforge.kolmafia.request.ChateauRequest; import net.sourceforge.kolmafia.request.ClanRumpusRequest; import net.sourceforge.kolmafia.request.ClosetRequest; import net.sourceforge.kolmafia.request.CreateItemRequest; import net.sourceforge.kolmafia.request.CustomOutfitRequest; import net.sourceforge.kolmafia.request.EdBaseRequest; import net.sourceforge.kolmafia.request.EquipmentRequest; import net.sourceforge.kolmafia.request.FalloutShelterRequest; import net.sourceforge.kolmafia.request.FamiliarRequest; import net.sourceforge.kolmafia.request.FloristRequest; import net.sourceforge.kolmafia.request.GenericRequest; import net.sourceforge.kolmafia.request.HermitRequest; import net.sourceforge.kolmafia.request.InternalChatRequest; import net.sourceforge.kolmafia.request.MoonPhaseRequest; import net.sourceforge.kolmafia.request.PasswordHashRequest; import net.sourceforge.kolmafia.request.PeeVPeeRequest; import net.sourceforge.kolmafia.request.PurchaseRequest; import net.sourceforge.kolmafia.request.QuestLogRequest; import net.sourceforge.kolmafia.request.RelayRequest; import net.sourceforge.kolmafia.request.RichardRequest; import net.sourceforge.kolmafia.request.SpelunkyRequest; import net.sourceforge.kolmafia.request.StandardRequest; import net.sourceforge.kolmafia.request.StorageRequest; import net.sourceforge.kolmafia.request.TrendyRequest; import net.sourceforge.kolmafia.request.UseItemRequest; import net.sourceforge.kolmafia.session.ActionBarManager; import net.sourceforge.kolmafia.session.BanishManager; import net.sourceforge.kolmafia.session.BatManager; import net.sourceforge.kolmafia.session.ClanManager; import net.sourceforge.kolmafia.session.ConsequenceManager; import net.sourceforge.kolmafia.session.ContactManager; import net.sourceforge.kolmafia.session.GoalManager; import net.sourceforge.kolmafia.session.InventoryManager; import net.sourceforge.kolmafia.session.IslandManager; import net.sourceforge.kolmafia.session.Limitmode; import net.sourceforge.kolmafia.session.LogoutManager; import net.sourceforge.kolmafia.session.StoreManager; import net.sourceforge.kolmafia.session.TurnCounter; import net.sourceforge.kolmafia.session.ValhallaManager; import net.sourceforge.kolmafia.swingui.AdventureFrame; import net.sourceforge.kolmafia.swingui.DescriptionFrame; import net.sourceforge.kolmafia.swingui.GearChangeFrame; import net.sourceforge.kolmafia.swingui.GenericFrame; import net.sourceforge.kolmafia.swingui.SystemTrayFrame; import net.sourceforge.kolmafia.swingui.listener.LicenseDisplayListener; import net.sourceforge.kolmafia.swingui.panel.GenericPanel; import net.sourceforge.kolmafia.textui.Interpreter; import net.sourceforge.kolmafia.utilities.FileUtilities; import net.sourceforge.kolmafia.utilities.InputFieldUtilities; import net.sourceforge.kolmafia.utilities.LockableListFactory; import net.sourceforge.kolmafia.utilities.LogStream; import net.sourceforge.kolmafia.utilities.StringUtilities; import net.sourceforge.kolmafia.utilities.SwinglessUIUtils; import net.sourceforge.kolmafia.webui.RelayLoader; import net.sourceforge.kolmafia.webui.RelayServer; public abstract class KoLmafia { private static boolean isRefreshing = false; private static boolean isAdventuring = false; private static volatile String abortAfter = null; public static String lastMessage = " "; static { System.setProperty( "sun.java2d.noddraw", "true" ); System.setProperty( "com.apple.mrj.application.apple.menu.about.name", "KoLmafia" ); System.setProperty( "com.apple.mrj.application.live-resize", "true" ); System.setProperty( "com.apple.mrj.application.growbox.intrudes", "false" ); System.setProperty( "java.net.preferIPv4Stack", "true" ); if ( SwinglessUIUtils.isSwingAvailable() ) { JEditorPane.registerEditorKitForContentType( "text/html", RequestEditorKit.class.getName() ); } System.setProperty( "apple.laf.useScreenMenuBar", "true" ); } public static String currentIterationString = ""; public static boolean tookChoice = false; public static boolean redoSkippedAdventures = true; public static boolean isMakingRequest = false; public static MafiaState displayState = MafiaState.ENABLE; private static boolean allowDisplayUpdate = true; public static final int[] initialStats = new int[ 3 ]; private static FileLock SESSION_HOLDER = null; private static FileChannel SESSION_CHANNEL = null; private static File SESSION_FILE = null; private static boolean SESSION_ENDING = false; public static KoLAdventure currentAdventure; public static String statDay = "None"; public static boolean useAmazonImages = true; public static final String AMAZON_IMAGE_SERVER = "https://s3.amazonaws.com/images.kingdomofloathing.com"; public static final String AMAZON_IMAGE_SERVER_PATH = AMAZON_IMAGE_SERVER + "/"; public static final String KOL_IMAGE_SERVER = "http://images.kingdomofloathing.com"; public static final String KOL_IMAGE_SERVER_PATH = KOL_IMAGE_SERVER + "/"; public static String imageServerPrefix() { return KoLmafia.useAmazonImages ? AMAZON_IMAGE_SERVER : KOL_IMAGE_SERVER; } public static String imageServerPath() { return KoLmafia.useAmazonImages ? AMAZON_IMAGE_SERVER_PATH : KOL_IMAGE_SERVER_PATH; } private static final boolean acquireFileLock( final String suffix ) { try { KoLmafia.SESSION_FILE = new File( KoLConstants.SESSIONS_LOCATION, "active_session." + suffix ); if ( KoLmafia.SESSION_FILE.exists() ) { KoLmafia.SESSION_CHANNEL = new RandomAccessFile( KoLmafia.SESSION_FILE, "rw" ).getChannel(); KoLmafia.SESSION_HOLDER = KoLmafia.SESSION_CHANNEL.tryLock(); return KoLmafia.SESSION_HOLDER != null; } PrintStream ostream = LogStream.openStream( KoLmafia.SESSION_FILE, true ); ostream.println( KoLConstants.VERSION_NAME ); ostream.close(); KoLmafia.SESSION_CHANNEL = new RandomAccessFile( KoLmafia.SESSION_FILE, "rw" ).getChannel(); KoLmafia.SESSION_HOLDER = KoLmafia.SESSION_CHANNEL.lock(); return true; } catch ( Exception e ) { return false; } } /** * The main method. Currently, it instantiates a single instance of the <code>KoLmafiaGUI</code>. */ public static final void main( final String[] args ) { System.out.println(); System.out.println( StaticEntity.getVersion() ); System.out.println( KoLConstants.VERSION_DATE ); System.out.println(); System.out.println( "Currently Running on " + System.getProperty( "os.name" ) ); try { System.out.println( "Local Directory is " + KoLConstants.ROOT_LOCATION.getCanonicalPath() ); } catch ( IOException e ) { System.out.println(e.getMessage()+" while trying to determine local directory."); } System.out.println( "Using Java " + System.getProperty( "java.version" ) ); System.out.println(); StaticEntity.setGUIRequired( true ); for ( int i = 0; i < args.length; ++i ) { if ( args[ i ].equalsIgnoreCase( "--HELP" ) || args[ i ].equalsIgnoreCase( "/?" ) ) { System.out.println( "An interface for the online adventure game, The Kingdom of Loathing." ); System.out.println( "Please visit kolmafia.sourceforge.net for more information." ); System.out.println(); System.out.println( "KoLmafia [--Help] [--Version] [--CLI] [--GUI] script" ); System.out.println(); System.out.println( " --Help Display this message and exits." ); System.out.println( " --Version Display the current version and exits." ); System.out.println( " --CLI Run KoLmafia as a command line application." ); System.out.println( " --GUI Run KoLmafia with a graphical user interface (Default)." ); System.out.println( " script Specifies a script to call when starting KoLmafia." ); System.exit( 0 ); } else if ( args[ i ].equalsIgnoreCase( "--VERSION" ) ) { System.exit( 0 ); } else if ( args[ i ].equalsIgnoreCase( "--CLI" ) ) { StaticEntity.setGUIRequired( false ); } else if ( args[ i ].equalsIgnoreCase( "--GUI" ) ) { StaticEntity.setGUIRequired( true ); } } // All dates are presented as if the day began at rollover. TimeZone koltime = (TimeZone) TimeZone.getTimeZone( "GMT-0330" ); KoLConstants.DAILY_FORMAT.setTimeZone( koltime ); // Reload your settings and determine all the different users which // are present in your save state list. Preferences.setBoolean( "useDevProxyServer", false ); Preferences.setBoolean( "relayBrowserOnly", false ); String actualName; String[] pastUsers = StaticEntity.getPastUserList(); for ( int i = 0; i < pastUsers.length; ++i ) { if ( pastUsers[ i ].startsWith( "devster" ) ) { continue; } actualName = Preferences.getString( pastUsers[ i ], "displayName" ); if ( actualName.equals( "" ) ) { actualName = StringUtilities.globalStringReplace( pastUsers[ i ], "_", " " ); } KoLConstants.saveStateNames.add( actualName ); } // Clear out any outdated data files. KoLmafia.checkDataOverrides(); // Create an images directory if necessary KoLConstants.IMAGE_LOCATION.mkdirs(); // Create a script directory if necessary KoLConstants.SCRIPT_LOCATION.mkdirs(); // Clear the image cache for the first time so subsequent image // files loaded into it have the right timestamps if ( Preferences.getLong( "lastImageCacheClear" ) == 0L ) { RelayRequest.clearImageCache(); } if ( SwinglessUIUtils.isSwingAvailable() ) { KoLmafia.initLookAndFeel(); } if ( !KoLmafia.acquireFileLock( "1" ) && !KoLmafia.acquireFileLock( "2" ) ) { System.exit( -1 ); } FlaggedItems.initializeLists(); // Now run the main routines for each, so that // you have an interface. if ( StaticEntity.isGUIRequired() ) { KoLmafiaGUI.initialize(); } else { RequestLogger.openStandard(); } // Now, maybe the person wishes to run something // on startup, and they associated KoLmafia with // some non-ASH file extension. This will run it. StringBuilder initialScript = new StringBuilder(); for ( int i = 0; i < args.length; ++i ) { if ( args[ i ].equalsIgnoreCase( "--CLI" ) || args[ i ].equalsIgnoreCase( "--GUI" ) ) { continue; } initialScript.append( args[ i ] ); initialScript.append( " " ); } if ( initialScript.length() != 0 ) { String actualScript = initialScript.toString().trim(); if ( actualScript.startsWith( "script=" ) ) { actualScript = actualScript.substring( 7 ); } KoLmafiaCLI.DEFAULT_SHELL.executeLine( "call " + actualScript ); } else if ( !StaticEntity.isGUIRequired() ) { KoLmafiaCLI.DEFAULT_SHELL.attemptLogin( "" ); } // Check for KoLmafia updates in a separate thread // so as to allow for continued execution. RequestThread.runInParallel( new UpdateCheckRunnable(), false ); // Always read input from the command line when you're not // in GUI mode. if ( !StaticEntity.isGUIRequired() ) { KoLmafiaCLI.DEFAULT_SHELL.listenForCommands(); } } private static void initLookAndFeel() { // Change the default look and feel to match the player's // preferences. Always do this. String defaultLookAndFeel; if ( System.getProperty( "os.name" ).startsWith( "Mac" ) || System.getProperty( "os.name" ).startsWith( "Win" ) ) { defaultLookAndFeel = UIManager.getSystemLookAndFeelClassName(); } else { defaultLookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); } String lookAndFeel = Preferences.getString( "swingLookAndFeel" ); if ( lookAndFeel.equals( "" ) ) { lookAndFeel = defaultLookAndFeel; } UIManager.LookAndFeelInfo[] installed = UIManager.getInstalledLookAndFeels(); String[] installedLooks = new String[ installed.length ]; for ( int i = 0; i < installedLooks.length; ++i ) { installedLooks[ i ] = installed[ i ].getClassName(); } boolean foundLookAndFeel = false; for ( int i = 0; i < installedLooks.length && !foundLookAndFeel; ++i ) { foundLookAndFeel = installedLooks[ i ].equals( lookAndFeel ); } if ( !foundLookAndFeel ) { lookAndFeel = defaultLookAndFeel; } try { UIManager.setLookAndFeel( lookAndFeel ); JFrame.setDefaultLookAndFeelDecorated( System.getProperty( "os.name" ).startsWith( "Mac" ) ); } catch ( Exception e ) { // Should not happen, as we checked to see if // the look and feel was installed first. JFrame.setDefaultLookAndFeelDecorated( true ); } if ( StaticEntity.usesSystemTray() ) { SystemTrayFrame.addTrayIcon(); } OSXAdapter.setDockIconImage( JComponentUtilities.getImage( "limeglass.gif" ).getImage() ); if ( System.getProperty( "os.name" ).startsWith( "Win" ) || lookAndFeel.equals( UIManager.getCrossPlatformLookAndFeelClassName() ) ) { UIManager.put( "ProgressBar.foreground", Color.black ); UIManager.put( "ProgressBar.selectionForeground", Color.lightGray ); UIManager.put( "ProgressBar.background", Color.lightGray ); UIManager.put( "ProgressBar.selectionBackground", Color.black ); } tab.CloseTabPaneEnhancedUI.selectedA = DataUtilities.toColor( Preferences.getString( "innerTabColor" ) ); tab.CloseTabPaneEnhancedUI.selectedB = DataUtilities.toColor( Preferences.getString( "outerTabColor" ) ); tab.CloseTabPaneEnhancedUI.notifiedA = DataUtilities.toColor( Preferences.getString( "innerChatColor" ) ); tab.CloseTabPaneEnhancedUI.notifiedB = DataUtilities.toColor( Preferences.getString( "outerChatColor" ) ); } private static final void checkDataOverrides() { String lastVersion = Preferences.getString( "previousUpdateVersion" ); String currentVersion = StaticEntity.getVersion(); int currentRevision = StaticEntity.getRevision(); String message = null; if ( lastVersion == null || lastVersion.equals( "" ) ) { message = "Clearing data overrides: initializing from " + currentVersion; } else if ( !lastVersion.equals( KoLConstants.VERSION_NAME ) ) { message = "Clearing data overrides: upgrade from " + lastVersion + " to " + currentVersion; } // Save revision, just for fun, but do not clear override files // for minor version upgrades. Preferences.setString( "previousUpdateVersion", KoLConstants.VERSION_NAME ); Preferences.setInteger( "previousUpdateRevision", currentRevision ); if ( message == null ) { return; } RequestLogger.printLine( message ); for ( int i = 0; i < KoLConstants.OVERRIDE_DATA.length; ++i ) { File outdated = new File( KoLConstants.DATA_LOCATION, KoLConstants.OVERRIDE_DATA[ i ] ); if ( outdated.exists() ) { outdated.delete(); } } } public static final boolean isSessionEnding() { return KoLmafia.SESSION_ENDING; } public static final String getLastMessage() { return KoLmafia.lastMessage; } /** * Updates the currently active display in the <code>KoLmafia</code> session. */ public static final void updateDisplay( final String message ) { KoLmafia.updateDisplay( MafiaState.CONTINUE, message ); } /** * Updates the currently active display in the <code>KoLmafia</code> session. */ public static final void updateDisplay( final MafiaState state, final String message ) { if ( StaticEntity.getContinuationState() == MafiaState.ABORT && state != MafiaState.ABORT ) { return; } if ( StaticEntity.getContinuationState() != MafiaState.PENDING || state == MafiaState.ABORT ) { StaticEntity.setContinuationState( state ); } RequestLogger.printLine( state, message ); if ( KoLmafia.allowDisplayUpdate ) { SystemTrayFrame.updateToolTip( message ); } KoLmafia.lastMessage = message; if ( !message.contains( KoLConstants.LINE_BREAK ) ) { KoLmafia.updateDisplayState( state, message ); } } private static final void updateDisplayState( final MafiaState state, final String message ) { // Relay threads don't get to change the display state if ( StaticEntity.isRelayThread() ) return; // Update all panels and frames with the message. if ( KoLmafia.allowDisplayUpdate ) { String unicodeMessage = StringUtilities.getEntityDecode( message, false ); ActionPanel[] panels = StaticEntity.getExistingPanels(); for ( int i = 0; i < panels.length; ++i ) { if ( panels[ i ] instanceof GenericPanel ) { ( (GenericPanel) panels[ i ] ).setStatusMessage( unicodeMessage ); } panels[ i ].setEnabled( state != MafiaState.CONTINUE ); } Frame [] frames = Frame.getFrames(); for ( int i = 0; i < frames.length; ++i ) { if ( frames[ i ] instanceof GenericFrame ) { GenericFrame frame = (GenericFrame) frames[ i ]; frame.setStatusMessage( unicodeMessage ); frame.updateDisplayState( state ); } } if ( KoLDesktop.instanceExists() ) { KoLDesktop.getInstance().updateDisplayState( state ); } } KoLmafia.displayState = state; } public static final void enableDisplay() { if ( StaticEntity.getContinuationState() == MafiaState.ABORT || StaticEntity.getContinuationState() == MafiaState.ERROR ) { KoLmafia.updateDisplayState( MafiaState.ERROR, "" ); } else { KoLmafia.updateDisplayState( MafiaState.ENABLE, "" ); } StaticEntity.setContinuationState( MafiaState.CONTINUE ); } public static final void timein( final String name ) { // Save the current user settings to disk Preferences.reset( null ); // Load the JSON string first, so we can use it, if necessary. ActionBarManager.loadJSONString(); // Reload the current user's preferences Preferences.reset( name ); // The password hash changes for each session GenericRequest.passwordHash = ""; PasswordHashRequest request = new PasswordHashRequest( "lchat.php" ); RequestThread.postRequest( request ); // Just in case it's a new day... // Close existing session log and reopen it RequestLogger.closeSessionLog(); RequestLogger.openSessionLog(); // Some things aren't properly set by KoL until main.php is loaded RequestThread.postRequest( new GenericRequest( "main.php" ) ); // Get current moon phases RequestThread.postRequest( new MoonPhaseRequest() ); KoLCharacter.setHoliday( HolidayDatabase.getHoliday() ); } public static final void resetCounters() { Preferences.setInteger( "lastCounterDay", HolidayDatabase.getPhaseStep() ); Preferences.setString( "barrelLayout", "?????????" ); Preferences.setBoolean( "bootsCharged", false ); Preferences.setBoolean( "breakfastCompleted", false ); Preferences.setBoolean( "burrowgrubHiveUsed", false ); Preferences.setInteger( "burrowgrubSummonsRemaining", 0 ); Preferences.setInteger( "cocktailSummons", 0 ); Preferences.setBoolean( "concertVisited", false ); Preferences.setInteger( "currentMojoFilters", 0 ); Preferences.setString( "currentPvpVictories", "" ); Preferences.setBoolean( "dailyDungeonDone", false ); Preferences.setBoolean( "demonSummoned", false ); Preferences.setBoolean( "expressCardUsed", false ); Preferences.setInteger( "extraRolloverAdventures", 0 ); Preferences.setBoolean( "friarsBlessingReceived", false ); Preferences.setInteger( "grimoire1Summons", 0 ); Preferences.setInteger( "grimoire2Summons", 0 ); Preferences.setInteger( "grimoire3Summons", 0 ); Preferences.setInteger( "lastBarrelSmashed", 0 ); Preferences.setInteger( "libramSummons", 0 ); Preferences.setBoolean( "libraryCardUsed", false ); Preferences.setInteger( "noodleSummons", 0 ); Preferences.setInteger( "nunsVisits", 0 ); Preferences.setBoolean( "oscusSodaUsed", false ); Preferences.setBoolean( "outrageousSombreroUsed", false ); Preferences.setInteger( "prismaticSummons", 0 ); Preferences.setBoolean( "rageGlandVented", false ); Preferences.setInteger( "reagentSummons", 0 ); Preferences.setString( "romanticTarget", "" ); Preferences.setInteger( "seaodesFound", 0 ); Preferences.setBoolean( "spiceMelangeUsed", false ); Preferences.setInteger( "spookyPuttyCopiesMade", 0 ); Preferences.setBoolean( "styxPixieVisited", false ); Preferences.setBoolean( "telescopeLookedHigh", false ); Preferences.setInteger( "tempuraSummons", 0 ); Preferences.setInteger( "timesRested", 0 ); Preferences.setInteger( "tomeSummons", 0 ); // Reset kolhsTotalSchoolSpirited to 0 if _kolhsSchoolSpirited wasn't set yesterday if ( !Preferences.getBoolean( "_kolhsSchoolSpirited" ) ) { Preferences.setInteger( "kolhsTotalSchoolSpirited", 0 ); } Preferences.resetDailies(); VYKEACompanionData.initialize( false ); ConsequenceManager.updateOneDesc(); BanishManager.resetRollover(); // Libram summoning skills now costs 1 MP again LockableListFactory.sort( KoLConstants.summoningSkills ); LockableListFactory.sort( KoLConstants.usableSkills ); // Remove Wandering Monster counters TurnCounter.stopCounting( "Romantic Monster window begin" ); TurnCounter.stopCounting( "Romantic Monster window end" ); TurnCounter.stopCounting( "Digitize Monster" ); TurnCounter.stopCounting( "Holiday Monster window begin" ); TurnCounter.stopCounting( "Holiday Monster window end" ); TurnCounter.stopCounting( "Event Monster window begin" ); TurnCounter.stopCounting( "Event Monster window end" ); TurnCounter.stopCounting( "Taco Elf window begin" ); TurnCounter.stopCounting( "Taco Elf window end" ); } public static void refreshSession() { KoLmafia.setIsRefreshing( true ); KoLmafia.refreshSessionData(); AdventureFrame.updateFromPreferences(); // It would be nice to not have to do this IslandManager.ensureUpdatedBigIsland(); boolean shouldResetCounters = Preferences.getInteger( "lastCounterDay" ) != HolidayDatabase.getPhaseStep(); boolean shouldResetGlobalCounters = Preferences.getInteger( "lastGlobalCounterDay" ) != HolidayDatabase.getPhaseStep(); int ascensions = KoLCharacter.getAscensions(); int knownAscensions = Preferences.getInteger( "knownAscensions" ); if ( ascensions != 0 && knownAscensions != -1 && knownAscensions != ascensions ) { Preferences.setInteger( "knownAscensions", ascensions ); ValhallaManager.resetPerAscensionCounters(); shouldResetCounters = true; } else if ( knownAscensions == -1 ) { Preferences.setInteger( "knownAscensions", ascensions ); } if ( shouldResetCounters ) { KoLmafia.resetCounters(); } if ( shouldResetGlobalCounters ) { Preferences.resetGlobalDailies(); } // No spurious adventure logging KoLAdventure.locationLogged = true; KoLmafia.setIsRefreshing( false ); } private static void refreshSessionData() { KoLmafia.updateDisplay( "Refreshing session data..." ); // Load saved counters before any requests are made, since both // charpane and charsheet requests can set them. CharPaneRequest.reset(); KoLCharacter.setCurrentRun( 0 ); TurnCounter.loadCounters(); // Some things aren't properly set by KoL until main.php is loaded RequestThread.postRequest( new GenericRequest( "main.php" ) ); // Get current moon phases RequestThread.postRequest( new MoonPhaseRequest() ); KoLCharacter.setHoliday( HolidayDatabase.getHoliday() ); // Forget what is trendy TrendyRequest.reset(); // Initialize pasta thralls & Ed servants, regardless of // character class PastaThrallData.initialize(); EdServantData.initialize(); // Start out fetching the status using the KoL API. This // provides data from a lot of different standard pages // We are in Valhalla if this redirects to afterlife.php String redirection = ApiRequest.updateStatus(); if ( redirection != null && redirection.startsWith( "afterlife.php" ) ) { // In Valhalla, parse the CharPane and abort further processing KoLmafia.updateDisplay( "Welcome to Valhalla!" ); RequestThread.postRequest( new CharPaneRequest() ); KoLCharacter.setGuildStoreOpen( false ); return; } // Retrieve the character sheet. It's necessary to do this // before concoctions have a chance to get refreshed. GenericRequest request = new CharSheetRequest(); RequestThread.postRequest( request ); // If you get redirected on the request for the character sheet, // don't make any more requests. if ( request.redirectLocation != null ) { return; } // Now that we know the character's ascension count, reset // anything that depends on that. KoLCharacter.resetPerAscensionData(); // Hermit items depend on character class HermitRequest.initialize(); // Retrieve the contents of inventory. InventoryManager.refresh(); // Retrieve the contents of the closet. ClosetRequest.refresh(); // Load Banished monsters BanishManager.loadBanishedMonsters(); // Retrieve Custom Outfit list if ( !Limitmode.limitOutfits() ) { RequestThread.postRequest( new CustomOutfitRequest() ); } // Look at the Quest Log RequestThread.postRequest( new QuestLogRequest() ); // if the Cyrpt quest is active, force evilometer refresh // (if we don't know evil levels already) if ( QuestDatabase.isQuestStep( Quest.CYRPT, QuestDatabase.STARTED ) ) { if ( Preferences.getInteger( "cyrptTotalEvilness" ) == 0 ) { RequestThread.postRequest( UseItemRequest.getInstance( ItemPool.EVILOMETER ) ); } } if ( KoLCharacter.isEd() ) { // Inspect your servants RequestThread.postRequest( new EdBaseRequest( "edbase_door", true ) ); } else if ( !KoLCharacter.inAxecore() && !KoLCharacter.isJarlsberg() && !KoLCharacter.isSneakyPete() ) { // Retrieve the Terrarium RequestThread.postRequest( new FamiliarRequest() ); } ChateauRequest.refresh(); // Retrieve campground data to see if the user has box servants // or a bookshelf if ( !Limitmode.limitCampground() && !KoLCharacter.isEd() && !KoLCharacter.inNuclearAutumn() ) { KoLmafia.updateDisplay( "Retrieving campground data..." ); CampgroundRequest.reset(); RequestThread.postRequest( new CampgroundRequest( "inspectdwelling" ) ); RequestThread.postRequest( new CampgroundRequest( "inspectkitchen" ) ); RequestThread.postRequest( new CampgroundRequest( "workshed" ) ); KoLCharacter.checkTelescope(); } if ( !Limitmode.limitCampground() && KoLCharacter.inNuclearAutumn() ) { KoLmafia.updateDisplay( "Retrieving fallout shelter data..." ); FalloutShelterRequest.reset(); RequestThread.postRequest( new FalloutShelterRequest() ); } RequestThread.postRequest( new PeeVPeeRequest( "fight" ) ); if ( Preferences.getInteger( "lastEmptiedStorage" ) != KoLCharacter.getAscensions() ) { StorageRequest.refresh(); CafeRequest.pullLARPCard(); } if ( KoLConstants.inventory.contains( ItemPool.get( ItemPool.KEYOTRON, 1 ) ) && Preferences.getInteger( "lastKeyotronUse" ) != KoLCharacter.getAscensions() ) { RequestThread.postRequest( UseItemRequest.getInstance( ItemPool.KEYOTRON ) ); } // If we have a Crown of Thrones and/or Buddy Bjorn available and it's not // equipped, see which familiar is sitting in it, if any. InventoryManager.checkCrownOfThrones(); InventoryManager.checkBuddyBjorn(); // Check items that vary per person // These won't actually generate a server hit if the item // has been seen at its current modifiers InventoryManager.checkNoHat(); InventoryManager.checkJickSword(); // Refresh familiar stuff FamiliarData.reset(); // Make sure that we know about the easy to see Golden Mr. A's, at least InventoryManager.countGoldenMrAccesories(); // Look up the current clan ClanManager.resetClanId(); ClanManager.getClanName( true ); // Update your mail contacts ContactManager.clearMailContacts(); ContactManager.updateMailContacts(); // Get current list of restricted items StandardRequest.initialize(); KoLmafia.updateDisplay( "Session data refreshed." ); // Inventory may have changed NamedListenerRegistry.fireChange( "(coinmaster)" ); ConcoctionDatabase.refreshConcoctions(); // Check the Florist to see what is planted FloristRequest.reset(); RequestThread.postRequest( new FloristRequest() ); // Check some things that are not (yet) in api.php EquipmentRequest.checkCowboyBoots(); EquipmentRequest.checkHolster(); } public static final boolean isRefreshing() { return KoLmafia.isRefreshing; } private static final void setIsRefreshing( final boolean isRefreshing ) { if ( KoLmafia.isRefreshing != isRefreshing ) { KoLmafia.isRefreshing = isRefreshing; PreferenceListenerRegistry.deferPreferenceListeners( isRefreshing ); } } public static final void resetAfterAvatar() { KoLmafia.setIsRefreshing( true ); // Set this first to prevent duplicate skill refreshing KoLCharacter.setRestricted( false ); // Start out fetching the status using the KoL API. This // provides data from a lot of different standard pages ApiRequest.updateStatus(); // Retrieve the character sheet. We must do this before // concoctions have a chance to get refreshed. // Clear skills first, since we no longer know Avatar skills KoLCharacter.resetSkills(); RequestThread.postRequest( new CharSheetRequest() ); // Clear preferences Preferences.setString( "banishingShoutMonsters", "" ); Preferences.setString( "peteMotorbikeTires", "" ); Preferences.setString( "peteMotorbikeGasTank", "" ); Preferences.setString( "peteMotorbikeHeadlight", "" ); Preferences.setString( "peteMotorbikeCowling", "" ); Preferences.setString( "peteMotorbikeMuffler", "" ); Preferences.setString( "peteMotorbikeSeat", "" ); BanishManager.resetAvatar(); // Hermit items depend on character class HermitRequest.initialize(); // Retrieve inventory contents, since quest items may disappear. InventoryManager.refresh(); // Retrieve the Terrarium RequestThread.postRequest( new FamiliarRequest() ); GearChangeFrame.updateFamiliars(); // Check the campground CampgroundRequest.reset(); RequestThread.postRequest( new CampgroundRequest( "inspectdwelling" ) ); RequestThread.postRequest( new CampgroundRequest( "inspectkitchen" ) ); RequestThread.postRequest( new CampgroundRequest( "workshed" ) ); RequestThread.postRequest( new CampgroundRequest( "bookshelf" ) ); KoLCharacter.checkTelescope(); // Finally, update available concoctions ConcoctionDatabase.resetQueue(); ConcoctionDatabase.refreshConcoctions(); KoLmafia.setIsRefreshing( false ); // Check the Florist FloristRequest.reset(); RequestThread.postRequest( new FloristRequest() ); // Run a user-supplied script KoLmafiaCLI.DEFAULT_SHELL.executeLine( Preferences.getString( "kingLiberatedScript" ) ); } public static final void resetAfterLimitmode() { KoLmafia.setIsRefreshing( true ); // Clear Spelunky preferences & items SpelunkyRequest.reset(); // Clear Batfellow preferences & items BatManager.end(); // Set this first to prevent duplicate skill refreshing KoLCharacter.setRestricted( false ); // Start out fetching the status using the KoL API. This // provides data from a lot of different standard pages ApiRequest.updateStatus(); // Retrieve the character sheet. It's necessary to do this // before concoctions have a chance to get refreshed. // Clear skills first, since we no longer know Limitmode skills KoLCharacter.resetSkills(); RequestThread.postRequest( new CharSheetRequest() ); // Retrieve inventory contents, since quest items may disappear. InventoryManager.refresh(); // Retrieve Custom Outfit list, since outfits contain limited items RequestThread.postRequest( new CustomOutfitRequest() ); // Retrieve the Terrarium RequestThread.postRequest( new FamiliarRequest() ); // If we logged in during limitmode, we may not have seen the Campground if ( !KoLCharacter.isEd() ) { CampgroundRequest.reset(); RequestThread.postRequest( new CampgroundRequest( "inspectdwelling" ) ); RequestThread.postRequest( new CampgroundRequest( "inspectkitchen" ) ); RequestThread.postRequest( new CampgroundRequest( "workshed" ) ); RequestThread.postRequest( new CampgroundRequest( "bookshelf" ) ); KoLCharacter.checkTelescope(); } // Finally, update available concoctions ConcoctionDatabase.resetQueue(); ConcoctionDatabase.refreshConcoctions(); KoLmafia.setIsRefreshing( false ); // Ensure Gear Changer accurate GearChangeFrame.validateSelections(); } /** * Used to reset the session tally to its original values. */ public static void resetSession() { KoLConstants.encounterList.clear(); KoLConstants.adventureList.clear(); KoLmafia.initialStats[ 0 ] = KoLCharacter.calculateBasePoints( KoLCharacter.getTotalMuscle() ); KoLmafia.initialStats[ 1 ] = KoLCharacter.calculateBasePoints( KoLCharacter.getTotalMysticality() ); KoLmafia.initialStats[ 2 ] = KoLCharacter.calculateBasePoints( KoLCharacter.getTotalMoxie() ); AdventureResult.SESSION_FULLSTATS[ 0 ] = 0; AdventureResult.SESSION_FULLSTATS[ 1 ] = 0; AdventureResult.SESSION_FULLSTATS[ 2 ] = 0; AdventureResult.SESSION_SUBSTATS[ 0 ] = 0; AdventureResult.SESSION_SUBSTATS[ 1 ] = 0; AdventureResult.SESSION_SUBSTATS[ 2 ] = 0; KoLConstants.tally.clear(); KoLConstants.tally.add( new AdventureResult( AdventureResult.ADV ) ); KoLConstants.tally.add( new AdventureResult( AdventureResult.MEAT ) ); KoLConstants.tally.add( AdventureResult.SESSION_SUBSTATS_RESULT ); KoLConstants.tally.add( AdventureResult.SESSION_FULLSTATS_RESULT ); // We could clear this here. However, it's useful for ASH // scripts to know this value regardless of whether the user // cleared the tally via the menu. // // KoLCharacter.clearSessionMeat(); } public static final void saveDataOverride() { if ( ItemDatabase.newItems ) { ItemDatabase.writeItems( new File( KoLConstants.DATA_LOCATION, "items.txt" ) ); } if ( EquipmentDatabase.newEquipment ) { EquipmentDatabase.writeEquipment( new File( KoLConstants.DATA_LOCATION, "equipment.txt" ) ); } if ( EffectDatabase.newEffects ) { EffectDatabase.writeEffects( new File( KoLConstants.DATA_LOCATION, "statuseffects.txt" ) ); } if ( ItemDatabase.newItems || EquipmentDatabase.newEquipment || EffectDatabase.newEffects ) { Modifiers.writeModifiers( new File( KoLConstants.DATA_LOCATION, "modifiers.txt" ) ); } if ( FamiliarDatabase.newFamiliars ) { FamiliarDatabase.writeFamiliars( new File( KoLConstants.DATA_LOCATION, "familiars.txt" ) ); } } /** * Adds the recent effects accumulated so far to the actual effects. This should be called after the previous * effects were decremented, if adventuring took place. */ public static final void applyEffects() { boolean concoctionRefreshNeeded = false; int oldCount = KoLConstants.activeEffects.size(); for ( int j = 0; j < KoLConstants.recentEffects.size(); ++j ) { AdventureResult effect = KoLConstants.recentEffects.get( j ); AdventureResult.addResultToList( KoLConstants.activeEffects, effect ); int effectId = effect.getEffectId(); if ( effectId == EffectPool.INIGOS || effectId == EffectPool.CRAFT_TEA ) { concoctionRefreshNeeded = true; } else if ( effectId == EffectPool.COWRRUPTION ) { if ( KoLConstants.activeEffects.contains( effect ) && KoLCharacter.getClassType() == KoLCharacter.COWPUNCHER ) { KoLCharacter.addAvailableSkill( "Absorb Cowrruption" ); } else { KoLCharacter.removeAvailableSkill( "Absorb Cowrruption" ); } } } KoLConstants.recentEffects.clear(); LockableListFactory.sort( KoLConstants.activeEffects ); if ( oldCount != KoLConstants.activeEffects.size() ) { KoLCharacter.updateStatus(); } if ( concoctionRefreshNeeded ) { ConcoctionDatabase.setRefreshNeeded( true ); } } /** * Makes the given request for the given number of iterations, or until * continues are no longer possible, either through user cancellation * or something occuring which prevents the requests from resuming. * * @param request The request made by the user * @param iterations The number of times the request should be repeated */ public static void makeRequest( final Runnable request, final int iterations ) { // This will only be true if this method is recursively // called via a script: an afterAdventureScript calling // "adventure", for example boolean wasAdventuring = KoLmafia.isAdventuring; try { if ( request instanceof KoLAdventure ) { KoLmafia.currentAdventure = (KoLAdventure) request; if ( KoLmafia.currentAdventure.getRequest() instanceof ClanRumpusRequest ) { RequestThread.postRequest( ( (ClanRumpusRequest) KoLmafia.currentAdventure.getRequest() ).setTurnCount( iterations ) ); return; } if ( KoLmafia.currentAdventure.getRequest() instanceof RichardRequest ) { RequestThread.postRequest( ( (RichardRequest) KoLmafia.currentAdventure.getRequest() ).setTurnCount( iterations ) ); return; } if ( KoLCharacter.getCurrentHP() == 0 && !KoLmafia.currentAdventure.isNonCombatsOnly() ) { RecoveryManager.recoverHP(); } if ( !KoLmafia.permitsContinue() ) { return; } if ( !wasAdventuring ) { KoLmafia.isAdventuring = true; NamedListenerRegistry.fireChange( "(adventuring)" ); SpecialOutfit.createImplicitCheckpoint(); } } KoLmafia.executeRequest( request, iterations, wasAdventuring ); } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); } finally { if ( request instanceof KoLAdventure && !wasAdventuring ) { SpecialOutfit.restoreImplicitCheckpoint(); KoLmafia.isAdventuring = false; NamedListenerRegistry.fireChange( "(adventuring)" ); if ( RecoveryManager.isRecoveryPossible() ) { RecoveryManager.runBetweenBattleChecks( false ); } } } } private static void executeRequest( final Runnable request, final int totalIterations, final boolean wasAdventuring ) { Interpreter.forgetPendingState(); // Begin the adventuring process, or the request execution // process (whichever is applicable). boolean isAdventure = request instanceof KoLAdventure; List<AdventureResult> goals = GoalManager.getGoals(); boolean deferConcoctionRefresh = true; AdventureResult[] items = new AdventureResult[ goals.size() ]; CreateItemRequest[] creatables = new CreateItemRequest[ goals.size() ]; for ( int i = 0; i < goals.size(); ++i ) { AdventureResult goal = goals.get( i ); items[ i ] = goal; creatables[ i ] = CreateItemRequest.getInstance( goal ); if ( deferConcoctionRefresh && ConcoctionDatabase.getMixingMethod( goal ) != CraftingType.NOCREATE ) { deferConcoctionRefresh = false; } } KoLmafia.forceContinue(); KoLmafia.abortAfter = null; if ( deferConcoctionRefresh ) { ConcoctionDatabase.deferRefresh( true ); } int currentIteration = 0; while ( KoLmafia.permitsContinue() && ++currentIteration <= totalIterations ) { int runBeforeRequest = KoLCharacter.getCurrentRun(); KoLmafia.tookChoice = false; KoLmafia.executeRequestOnce( request, currentIteration, totalIterations, items, creatables, wasAdventuring ); if ( isAdventure && KoLmafia.redoSkippedAdventures && runBeforeRequest == KoLCharacter.getCurrentRun() ) { --currentIteration; } // Check if bounties completed, and hand in if so boolean completeBounty = false; completeBounty |= BountyDatabase.checkBounty( "currentEasyBountyItem" ); completeBounty |= BountyDatabase.checkBounty( "currentHardBountyItem" ); completeBounty |= BountyDatabase.checkBounty( "currentSpecialBountyItem" ); if ( completeBounty ) { RequestThread.postRequest( new BountyHunterHunterRequest() ); } } if ( deferConcoctionRefresh ) { ConcoctionDatabase.deferRefresh( false ); } if ( isAdventure ) { AdventureFrame.updateRequestMeter( 1, 1 ); } // If you've completed the requests, make sure to update // the display. if ( KoLmafia.permitsContinue() && RecoveryManager.isRecoveryPossible() ) { if ( isAdventure && GoalManager.hasGoals() ) { KoLmafia.updateDisplay( MafiaState.ERROR, "Conditions not satisfied after " + ( currentIteration - 1 ) + ( currentIteration == 2 ? " adventure." : " adventures." ) ); } } else if ( StaticEntity.getContinuationState() == MafiaState.PENDING ) { Interpreter.rememberPendingState(); KoLmafia.forceContinue(); } } private static void executeRequestOnce( final Runnable request, final int currentIteration, final int totalIterations, final AdventureResult[] items, final CreateItemRequest[] creatables, final boolean wasAdventuring ) { if ( request instanceof KoLAdventure ) { KoLmafia.executeAdventureOnce( (KoLAdventure) request, currentIteration, totalIterations, items, creatables, wasAdventuring ); return; } if ( request instanceof CampgroundRequest ) { KoLmafia.updateDisplay( "Campground request " + currentIteration + " of " + totalIterations + " in progress..." ); } RequestLogger.printLine(); RequestThread.postRequest( (GenericRequest) request ); RequestLogger.printLine(); } private static void executeAdventureOnce( final KoLAdventure adventure, final int currentIteration, final int totalIterations, final AdventureResult[] items, final CreateItemRequest[] creatables, final boolean wasAdventuring ) { if ( KoLCharacter.getAdventuresLeft() == 0 ) { KoLmafia.updateDisplay( MafiaState.PENDING, "Ran out of adventures." ); return; } if ( KoLmafia.handleConditions( items, creatables ) ) { KoLmafia.updateDisplay( MafiaState.PENDING, "Conditions satisfied after " + currentIteration + " adventures." ); return; } if ( KoLCharacter.isFallingDown() ) { String holiday = HolidayDatabase.getHoliday(); String adventureName = adventure.getAdventureName(); if ( KoLCharacter.hasEquipped( ItemPool.get( ItemPool.DRUNKULA_WINEGLASS, 1 ) ) ) { // The wine glass allows you to adventure while falling down drunk } else if ( KoLCharacter.getLimitmode() == Limitmode.SPELUNKY ) { // You're allowed to Spelunk even while falling down drunk } else if ( adventureName.equals( "An Eldritch Fissure" ) || adventureName.equals( "An Eldritch Horror" ) || adventureName.equals( "Trick-or-Treating" ) ) { // You're allowed to explore eldritch fissures // or go trick-or-treating even while falling // down drunk } else if ( !holiday.contains( "St. Sneaky Pete's Day" ) && !holiday.contains( "Drunksgiving" ) ) { KoLmafia.updateDisplay( MafiaState.ERROR, "You are too drunk to continue." ); return; } else if ( KoLCharacter.getInebriety() <= 25 ) { KoLmafia.updateDisplay( MafiaState.ERROR, "You are not drunk enough to continue." ); return; } } if ( KoLmafia.abortAfter != null ) { KoLmafia.updateDisplay( MafiaState.PENDING, KoLmafia.abortAfter ); return; } // Otherwise, disable the display and update the user // and the current request number. Different requests // have different displays. They are handled here. if ( totalIterations > 1 ) { KoLmafia.currentIterationString = "Request " + currentIteration + " of " + totalIterations + " (" + adventure.toString() + ") in progress..."; } else { KoLmafia.currentIterationString = "Visit to " + adventure.toString() + " in progress..."; } if ( !wasAdventuring ) { AdventureFrame.updateRequestMeter( currentIteration - 1, totalIterations ); } RequestLogger.printLine(); RequestThread.postRequest( adventure ); RequestLogger.printLine(); KoLmafia.currentIterationString = ""; KoLmafia.executeAfterAdventureScript(); if ( KoLmafia.handleConditions( items, creatables ) ) { KoLmafia.updateDisplay( MafiaState.PENDING, "Conditions satisfied after " + currentIteration + " adventures." ); return; } } public static boolean executeAfterAdventureScript() { return KoLmafia.executeScript( Preferences.getString( "afterAdventureScript" ) ); } public static boolean executeBeforePVPScript() { return KoLmafia.executeScript( Preferences.getString( "beforePVPScript" ) ); } public static boolean executeScript( final String scriptPath ) { if ( !scriptPath.equals( "" ) ) { KoLmafiaCLI.DEFAULT_SHELL.executeLine( scriptPath ); return true; } return false; } private static boolean handleConditions( final AdventureResult[] items, final CreateItemRequest[] creatables ) { if ( items.length == 0 ) { return false; } if ( !GoalManager.hasGoals() ) { return true; } boolean shouldCreate = false; for ( int i = 0; i < creatables.length && !shouldCreate; ++i ) { shouldCreate = creatables[ i ] != null && creatables[ i ].getQuantityPossible() >= items[ i ].getCount(); } // In theory, you could do a real validation by doing a full // dependency search. While that's technically better, it's // also not very useful. for ( int i = 0; i < creatables.length && shouldCreate; ++i ) { shouldCreate = creatables[ i ] == null || creatables[ i ].getQuantityPossible() >= items[ i ].getCount(); } // Create any items which are creatable. if ( shouldCreate ) { for ( int i = creatables.length - 1; i >= 0; --i ) { if ( creatables[ i ] != null && creatables[ i ].getQuantityPossible() >= items[ i ].getCount() ) { creatables[ i ].setQuantityNeeded( items[ i ].getCount() ); RequestThread.postRequest( creatables[ i ] ); creatables[ i ] = null; } } } // If the conditions existed and have been satisfied, // then you should stop. return !GoalManager.hasGoals(); } public static void abortAfter( String msg ) { KoLmafia.abortAfter = msg; } public static void protectClovers() { if ( KoLCharacter.inBeecore() ) { KoLmafiaCLI.DEFAULT_SHELL.executeCommand( "closet", "put * ten-leaf clover" ); } else { KoLmafiaCLI.DEFAULT_SHELL.executeCommand( "use", "* ten-leaf clover" ); } } /** * Show an HTML string to the user */ public static void showHTML( String location, String text ) { if ( !GenericFrame.instanceExists() ) { KoLmafiaCLI.showHTML( text ); return; } GenericRequest request = new GenericRequest( location ); request.responseText = text; DescriptionFrame.showRequest( request ); } /** * Retrieves whether or not continuation of an adventure or request is permitted by the or by current circumstances * in-game. * * @return <code>true</code> if requests are allowed to continue */ public static final boolean permitsContinue() { return StaticEntity.getContinuationState() == MafiaState.CONTINUE; } /** * Retrieves whether or not continuation of an adventure or request will be denied by the regardless of continue * state reset, until the display is enable (ie: in an abort state). * * @return <code>true</code> if requests are allowed to continue */ public static final boolean refusesContinue() { return StaticEntity.userAborted || StaticEntity.getContinuationState() == MafiaState.ABORT; } /** * Forces a continue state. This should only be called when there is no doubt that a continue should occur. * * @return <code>true</code> if requests are allowed to continue */ public static final void forceContinue() { StaticEntity.setContinuationState( MafiaState.CONTINUE ); StaticEntity.userAborted = false; } /** * Utility. This method used to decode a saved password. This should be called whenever a new password intends to be * stored in the global file. */ public static final void addSaveState( final String username, final String password ) { String utfString = StringUtilities.getURLEncode( password ); StringBuilder encodedString = new StringBuilder(); char currentCharacter; for ( int i = 0; i < utfString.length(); ++i ) { currentCharacter = utfString.charAt( i ); switch ( currentCharacter ) { case '-': encodedString.append( "2D" ); break; case '.': encodedString.append( "2E" ); break; case '*': encodedString.append( "2A" ); break; case '_': encodedString.append( "5F" ); break; case '+': encodedString.append( "20" ); break; case '%': encodedString.append( utfString.charAt( ++i ) ); encodedString.append( utfString.charAt( ++i ) ); break; default: encodedString.append( Integer.toHexString( currentCharacter ).toUpperCase() ); break; } } Preferences.setString( username, "saveState", ( new BigInteger( encodedString.toString(), 36 ) ).toString( 10 ) ); if ( !KoLConstants.saveStateNames.contains( username ) ) { KoLConstants.saveStateNames.add( username ); } } public static final void removeSaveState( final String loginname ) { if ( loginname == null ) { return; } KoLConstants.saveStateNames.remove( loginname ); Preferences.setString( loginname, "saveState", "" ); } /** * Utility. The method used to decode a saved password. This should be called whenever a new password intends to be * stored in the global file. */ public static final String getSaveState( final String loginname ) { String password = Preferences.getString( loginname, "saveState" ); if ( password == null || password.length() == 0 || password.contains( "/" ) ) { return null; } String hexString = ( new BigInteger( password, 10 ) ).toString( 36 ); StringBuilder utfString = new StringBuilder(); for ( int i = 0; i < hexString.length(); ++i ) { utfString.append( '%' ); utfString.append( hexString.charAt( i ) ); utfString.append( hexString.charAt( ++i ) ); } return StringUtilities.getURLDecode( utfString.toString() ); } public static final boolean checkRequirements( final List<AdventureResult> requirements ) { return KoLmafia.checkRequirements( requirements, true ); } public static final boolean checkRequirements( final List<AdventureResult> requirements, final boolean retrieveItem ) { AdventureResult[] requirementsArray = new AdventureResult[ requirements.size() ]; requirements.toArray( requirementsArray ); int actualCount = 0; // Check the items required for this quest, // retrieving any items which might be inside // of a closet somewhere. for ( int i = 0; i < requirementsArray.length; ++i ) { if ( requirementsArray[ i ] == null ) { continue; } if ( requirementsArray[ i ].isItem() && retrieveItem ) { InventoryManager.retrieveItem( requirementsArray[ i ] ); } if ( requirementsArray[ i ].isItem() ) { actualCount = requirementsArray[ i ].getCount( KoLConstants.inventory ); } else if ( requirementsArray[ i ].isStatusEffect() ) { actualCount = requirementsArray[ i ].getCount( KoLConstants.activeEffects ); } else if ( requirementsArray[ i ].getName().equals( AdventureResult.MEAT ) ) { actualCount = KoLCharacter.getAvailableMeat(); } if ( actualCount >= requirementsArray[ i ].getCount() ) { requirements.remove( requirementsArray[ i ] ); } else if ( actualCount > 0 ) { AdventureResult.addResultToList( requirements, requirementsArray[ i ].getInstance( 0 - actualCount ) ); } } // If there are any missing requirements // be sure to return false. Otherwise, // you managed to get everything. return requirements.isEmpty(); } /** * Utility method used to purchase the given number of items from the mall using the given purchase requests. */ public static void makePurchases( final List<PurchaseRequest> results, final PurchaseRequest[] purchases, final int maxPurchases, final boolean isAutomated, final int priceLimit ) { int firstIndex = 0; if ( isAutomated ) { // PC stores can be cheaper than NPC stores. If we are // not allowed to purchase from the mall, skip through // requests until we find an NPC seller, if any. if ( !Preferences.getBoolean( "autoSatisfyWithMall" ) ) { while ( firstIndex < purchases.length ) { PurchaseRequest currentRequest = purchases[ firstIndex ]; if ( currentRequest.getQuantity() == PurchaseRequest.MAX_QUANTITY ) { break; } firstIndex++; } } // If we are allowed to purchase from the mall, make // sure that the price limit for automated purchases // makes sense. else if ( Preferences.getInteger( "autoBuyPriceLimit" ) == 0 ) { // this is probably due to an out-of-date defaults.txt Preferences.setInteger( "autoBuyPriceLimit", 20000 ); } } if ( firstIndex == purchases.length ) { return; } PurchaseRequest firstRequest = purchases[ firstIndex ]; List<AdventureResult> destination = // Only NPC stores have an infinite supply ( !KoLCharacter.canInteract() && firstRequest.getQuantity() != PurchaseRequest.MAX_QUANTITY ) ? KoLConstants.storage : KoLConstants.inventory; int remaining = maxPurchases; int itemId = 0; for ( int i = firstIndex; i < purchases.length && remaining > 0 && KoLmafia.permitsContinue(); ++i ) { PurchaseRequest currentRequest = (PurchaseRequest) purchases[ i ]; AdventureResult item = currentRequest.getItem(); itemId = item.getItemId(); if ( itemId == ItemPool.TEN_LEAF_CLOVER && destination == KoLConstants.inventory && InventoryManager.cloverProtectionActive() && !KoLCharacter.inBeecore() ) { // Clover protection will miraculously turn ten-leaf // clovers into disassembled clovers as soon as they // come into inventory item = ItemPool.get( ItemPool.DISASSEMBLED_CLOVER, item.getCount() ); } int initialCount = item.getCount( destination ); int currentCount = initialCount; int desiredCount = remaining == Integer.MAX_VALUE ? Integer.MAX_VALUE : initialCount + remaining; int currentPrice = currentRequest.getPrice(); if ( ( priceLimit > 0 && currentPrice > priceLimit ) || ( isAutomated && currentPrice > Preferences.getInteger( "autoBuyPriceLimit" ) ) ) { // KoLmafia.updateDisplay( MafiaState.ERROR, // "Stopped purchasing " + currentRequest.getItemName() + " @ " + KoLConstants.COMMA_FORMAT.format( currentPrice ) + "." ); // If we are purchasing multiple different items, the next item might be affordable. continue; } int previousLimit = currentRequest.getLimit(); currentRequest.setLimit( Math.min( currentRequest.getAvailableMeat() / currentPrice, Math.min( previousLimit, desiredCount - currentCount ) ) ); RequestThread.postRequest( currentRequest ); // We've purchased as many as we will from this store if ( KoLmafia.permitsContinue() ) { if ( currentRequest.getQuantity() == currentRequest.getLimit() ) { results.remove( currentRequest ); } else if ( currentRequest.getQuantity() == PurchaseRequest.MAX_QUANTITY ) { currentRequest.setLimit( PurchaseRequest.MAX_QUANTITY ); } else { if ( currentRequest.getLimit() == previousLimit ) { currentRequest.setCanPurchase( false ); } currentRequest.setQuantity( currentRequest.getQuantity() - currentRequest.getLimit() ); currentRequest.setLimit( previousLimit ); } } else { currentRequest.setLimit( previousLimit ); } // Update how many of the item we have post-purchase int purchased = item.getCount( destination ) - currentCount; remaining -= purchased; } if ( remaining == 0 || maxPurchases == Integer.MAX_VALUE ) { KoLmafia.updateDisplay( "Purchases complete." ); } else { KoLmafia.updateDisplay( "Desired purchase quantity not reached (wanted " + maxPurchases + ", got " + ( maxPurchases - remaining ) + ")" ); StoreManager.flushCache( itemId ); } } public static final void deleteAdventureOverride() { for ( int i = 0; i < KoLConstants.OVERRIDE_DATA.length; ++i ) { File dest = new File( KoLConstants.DATA_LOCATION, KoLConstants.OVERRIDE_DATA[ i ] ); if ( dest.exists() ) { dest.delete(); } } KoLmafia.updateDisplay( "Please restart KoLmafia to complete the update." ); } public static void gc() { int mem1 = (int) ( Runtime.getRuntime().freeMemory() >> 10 ); System.gc(); int mem2 = (int) ( Runtime.getRuntime().freeMemory() >> 10 ); RequestLogger.printLine( "Reclaimed " + ( mem2 - mem1 ) + " KB of memory" ); } public static final boolean isAdventuring() { return KoLmafia.isAdventuring; } public static String whoisPlayer( final String player ) { InternalChatRequest request = new InternalChatRequest( "/whois " + player ); RequestThread.postRequest( request ); return request.responseText; } public static boolean isPlayerOnline( final String player ) { // This player is currently online. // This player is currently online in channel clan. // This player is currently away from KoL in channel trade and listening to clan. String text = KoLmafia.whoisPlayer( player ); return text != null && text.contains( "This player is currently" ); } private static class UpdateCheckRunnable implements Runnable { public void run() { if ( KoLConstants.VERSION_NAME.startsWith( "KoLmafia r" ) ) { return; } long lastUpdate = Long.parseLong( Preferences.getString( "lastRssUpdate" ) ); if ( System.currentTimeMillis() - lastUpdate < 86400000L ) { return; } try { String line; BufferedReader reader = FileUtilities.getReader( "https://sourceforge.net/p/kolmafia/code/HEAD/tree/src/net/sourceforge/kolmafia/KoLConstants.java" ); String lastVersion = Preferences.getString( "lastRssVersion" ); String currentVersion = null; while ( ( line = reader.readLine() ) != null ) { if ( line.contains( "public static final String VERSION_NAME" ) ) { int quote1 = line.indexOf( "\"" ) + 1; int quote2 = line.lastIndexOf( "\"" ); currentVersion = line.substring( quote1, quote2 ); } } reader.close(); if ( currentVersion == null ) { return; } Preferences.setString( "lastRssVersion", currentVersion ); if ( currentVersion.equals( KoLConstants.VERSION_NAME ) || currentVersion.equals( lastVersion ) ) { return; } if ( InputFieldUtilities.confirm( "A new version of KoLmafia is now available. Would you like to download it now?" ) ) { RelayLoader.openSystemBrowser( "https://sourceforge.net/projects/kolmafia/files/" ); } } catch ( Exception e ) { System.out.println(e.getMessage()+" while trying to read from or close KolConstants."); } } } public static void about() { new LicenseDisplayListener().run(); } public static void quit() { if ( KoLmafia.SESSION_ENDING ) { return; } KoLmafia.SESSION_ENDING = true; LogoutManager.prepare(); QuitRunnable quitRunnable = new QuitRunnable(); if ( SwingUtilities.isEventDispatchThread() ) { KoLmafia.updateDisplay( "Logout in progress (interface will be unresponsive)..." ); KoLmafia.allowDisplayUpdate = false; Thread quitThread = new Thread( quitRunnable ); quitThread.start(); try { quitThread.join(); } catch ( InterruptedException e ) { System.out.println(e.getMessage()+" while trying to quit."); } } else { quitRunnable.run(); } System.exit( 0 ); } private static class QuitRunnable implements Runnable { public void run() { LogoutManager.logout(); Preferences.reset( null ); FlaggedItems.saveFlaggedItemList(); RequestLogger.closeSessionLog(); RequestLogger.closeDebugLog(); RequestLogger.closeMirror(); SystemTrayFrame.removeTrayIcon(); RelayServer.stop(); try { KoLmafia.SESSION_HOLDER.release(); KoLmafia.SESSION_CHANNEL.close(); KoLmafia.SESSION_FILE.delete(); } catch ( Exception e ) { // That means the file either doesn't exist or // the session holder was somehow closed. // Ignore and fall through. } } } public static void preferences() { KoLmafiaGUI.constructFrame( "OptionsFrame" ); } public static void preferencesThreaded() { RequestThread.runInParallel( new PreferencesRunnable() ); } private static class PreferencesRunnable implements Runnable { public void run() { KoLmafiaGUI.constructFrame( "OptionsFrame" ); } } }