/** * 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.AdventureResult; import net.sourceforge.kolmafia.KoLAdventure; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLConstants.Stat; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.Modifiers; import net.sourceforge.kolmafia.MonsterData; import net.sourceforge.kolmafia.RequestLogger; import net.sourceforge.kolmafia.combat.MonsterStatusTracker; import net.sourceforge.kolmafia.objectpool.AdventurePool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.persistence.EquipmentDatabase; import net.sourceforge.kolmafia.persistence.ItemDatabase; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.session.ChoiceManager; import net.sourceforge.kolmafia.session.EquipmentManager; import net.sourceforge.kolmafia.session.InventoryManager; import net.sourceforge.kolmafia.utilities.StringUtilities; import org.json.JSONException; import org.json.JSONObject; public class SpelunkyRequest extends GenericRequest { private static final Pattern MUSCLE_PATTERN = Pattern.compile( "Mus:</td><td>(?:<font color=blue>|)<b>(\\d+)(?:</font> \\((\\d+)\\)|)</b>" ); private static final Pattern MOXIE_PATTERN = Pattern.compile( "Mox:</td><td>(?:<font color=blue>|)<b>(\\d+)(?:</font> \\((\\d+)\\)|)</b>" ); private static final Pattern HP_PATTERN = Pattern.compile( "HP:</td><td><b>(\\d+) / (\\d+)" ); private static final Pattern TURNS_PATTERN = Pattern.compile( "Ghost'>(?:</a>)?<br><b>(\\d+)</b>" ); private static final Pattern GOLD_PATTERN = Pattern.compile( "Gold: <b>\\$((\\d{1,3},\\d{3})|(\\d+))</b>" ); private static final Pattern BOMB_PATTERN = Pattern.compile( "Bombs' width=30 height=30></td><td valign=center align=left><b>(\\d+)</b>" ); private static final Pattern ROPE_PATTERN = Pattern.compile( "Ropes' width=30 height=30></td><td valign=center align=left><b>(\\d+)</b>" ); private static final Pattern KEY_PATTERN = Pattern.compile( "Keys' width=30 height=30></td><td valign=center align=left><b>(\\d+)</b>" ); private static final Pattern BUDDY_PATTERN = Pattern.compile( "Buddy:</b(?:.*)alt='(.*?)' " ); private static final Pattern GEAR_SECTION_PATTERN = Pattern.compile( "Gear:</b(.*?)</table>" ); private static final Pattern EQUIPMENT_PATTERN = Pattern.compile( "descitem\\((\\d+)\\)" ); private static final Pattern SHOP_PATTERN = Pattern.compile( "Buddy:</b(?:.*)alt='(.*?)' " ); private static final Pattern TURNS_STATUS_PATTERN = Pattern.compile( "Turns: (\\d+)" ); private static final Pattern GOLD_STATUS_PATTERN = Pattern.compile( "Gold: (\\d+)" ); private static final Pattern BOMB_STATUS_PATTERN = Pattern.compile( "Bombs: (\\d+)" ); private static final Pattern ROPE_STATUS_PATTERN = Pattern.compile( "Ropes: (\\d+)" ); private static final Pattern KEY_STATUS_PATTERN = Pattern.compile( "Keys: (\\d+)" ); private static final Pattern BUDDY_STATUS_PATTERN = Pattern.compile( "Buddy: (.*?)," ); private static final Pattern UNLOCK_STATUS_PATTERN = Pattern.compile( "Unlocks: (.*)" ); private static final String[][] BUDDY = { { "Skeleton", "spelbuddy1.gif", "Punches opponent" }, { "Helpful Guy", "spelbuddy2.gif", "Delevels opponent" }, { "Damselfly", "spelbuddy3.gif", "Heals after combat" }, { "Resourceful Kid", "spelbuddy4.gif", "Finds extra gold" }, { "Golden Monkey", "spelbuddy5.gif", "Finds extra gold" }, }; private static final AdventureResult WHIP = ItemPool.get( ItemPool.SPELUNKY_WHIP, 1 ); private static final AdventureResult MACHETE = ItemPool.get( ItemPool.SPELUNKY_MACHETE, 1 ); private static final AdventureResult SHOTGUN = ItemPool.get( ItemPool.SPELUNKY_SHOTGUN, 1 ); private static final AdventureResult YELLOW_CAPE = ItemPool.get( ItemPool.SPELUNKY_CAPE, 1 ); private static final AdventureResult JETPACK = ItemPool.get( ItemPool.SPELUNKY_JETPACK, 1 ); private static final AdventureResult XRAY_GOGGLES = ItemPool.get( ItemPool.SPELUNKY_GOGGLES, 1 ); private static final AdventureResult CLOWN_CROWN = ItemPool.get( ItemPool.SPELUNKY_CROWN, 1 ); private static final AdventureResult SPRING_BOOTS = ItemPool.get( ItemPool.SPELUNKY_SPRING_BOOTS, 1 ); private static final AdventureResult HEAVY_PICKAXE = ItemPool.get( ItemPool.SPELUNKY_PICKAXE, 1 ); private static final AdventureResult SKULL = ItemPool.get( ItemPool.SPELUNKY_SKULL, 1 ); private static final AdventureResult ROCK = ItemPool.get( ItemPool.SPELUNKY_ROCK, 1 ); private static final AdventureResult POT = ItemPool.get( ItemPool.SPELUNKY_POT, 1 ); private static final AdventureResult TORCH = ItemPool.get( ItemPool.SPELUNKY_TORCH, 1 ); private static final AdventureResult[] ITEMS = { // Weapons SpelunkyRequest.WHIP, SpelunkyRequest.MACHETE, SpelunkyRequest.SHOTGUN, ItemPool.get( ItemPool.SPELUNKY_BOOMERANG, 1 ), ItemPool.get( ItemPool.SPELUNKY_RIFLE, 1 ), ItemPool.get( ItemPool.SPELUNKY_STAFF, 1 ), // Off-hand items SpelunkyRequest.SKULL, SpelunkyRequest.ROCK, SpelunkyRequest.POT, SpelunkyRequest.HEAVY_PICKAXE, SpelunkyRequest.TORCH, ItemPool.get( ItemPool.SPELUNKY_COFFEE_CUP, 1 ), ItemPool.get( ItemPool.SPELUNKY_JOKE_BOOK, 1 ), // Hats ItemPool.get( ItemPool.SPELUNKY_FEDORA, 1 ), ItemPool.get( ItemPool.SPELUNKY_HELMET, 1 ), SpelunkyRequest.XRAY_GOGGLES, SpelunkyRequest.CLOWN_CROWN, // Back items SpelunkyRequest.YELLOW_CAPE, SpelunkyRequest.JETPACK, // Accessories SpelunkyRequest.SPRING_BOOTS, ItemPool.get( ItemPool.SPELUNKY_SPIKED_BOOTS, 1 ), }; private static final HashMap<String,String> adventureImages = new HashMap<String,String>(); static { SpelunkyRequest.adventureImages.put( "The Mines", "mines.gif" ); SpelunkyRequest.adventureImages.put( "The Jungle", "jungle.gif" ); SpelunkyRequest.adventureImages.put( "The Ice Caves", "icecaves.gif" ); SpelunkyRequest.adventureImages.put( "The Temple Ruins", "templeruins.gif" ); SpelunkyRequest.adventureImages.put( "Hell", "heckofirezzz.gif" ); SpelunkyRequest.adventureImages.put( "The Snake Pit", "snakepit.gif" ); SpelunkyRequest.adventureImages.put( "The Spider Hole", "spiderhole.gif" ); SpelunkyRequest.adventureImages.put( "The Ancient Burial Ground", "burialground.gif" ); SpelunkyRequest.adventureImages.put( "The Beehive", "beehive.gif" ); SpelunkyRequest.adventureImages.put( "The Crashed U. F. O.", "ufo.gif" ); SpelunkyRequest.adventureImages.put( "The City of Goooold", "citygold.gif" ); SpelunkyRequest.adventureImages.put( "LOLmec's Lair", "lolmec.gif" ); SpelunkyRequest.adventureImages.put( "Yomama's Throne", "yomama.gif" ); }; public static String adventureImage( KoLAdventure adventure ) { return SpelunkyRequest.adventureImage( adventure.getAdventureName() ); } public static String adventureImage( String adventureName ) { String image = SpelunkyRequest.adventureImages.get( adventureName ); return image == null ? null : "otherimages/spelunky/" + image; } public SpelunkyRequest() { super( "place.php" ); } public static void reset() { Preferences.resetToDefault( "spelunkyNextNoncombat" ); Preferences.resetToDefault( "spelunkySacrifices" ); Preferences.resetToDefault( "spelunkyStatus" ); Preferences.resetToDefault( "spelunkyWinCount" ); SpelunkyRequest.resetItems(); } public static void resetItems() { EquipmentManager.removeAllEquipment(); for ( AdventureResult item : SpelunkyRequest.ITEMS ) { int count = item.getCount( KoLConstants.inventory ); if ( count > 0 ) { AdventureResult result = item.getInstance( -count ); AdventureResult.addResultToList( KoLConstants.inventory, result ); AdventureResult.addResultToList( KoLConstants.tally, result ); } } } public static void parseCharpane( final String responseText ) { if ( !responseText.contains( ">Last Spelunk</a>" ) ) { return; } CharPaneRequest.parseAvatar( responseText ); boolean ghostWaving = false; String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); int buffedMus = 0; int buffedMox = 0; long baseMusPoints = 0; long baseMoxPoints = 0; Matcher matcher = SpelunkyRequest.MUSCLE_PATTERN.matcher( responseText ); if ( matcher.find() ) { buffedMus = StringUtilities.parseInt( matcher.group( 1 ) ); int baseMus = buffedMus; if ( matcher.group( 2 ) != null ) { baseMus = StringUtilities.parseInt( matcher.group( 2 ) ); } baseMusPoints = KoLCharacter.calculatePointSubpoints( baseMus ); } matcher = SpelunkyRequest.MOXIE_PATTERN.matcher( responseText ); if ( matcher.find() ) { buffedMox = StringUtilities.parseInt( matcher.group( 1 ) ); int baseMox = buffedMox; if ( matcher.group( 2 ) != null ) { baseMox = StringUtilities.parseInt( matcher.group( 2 ) ); } baseMoxPoints = KoLCharacter.calculatePointSubpoints( baseMox ); } KoLCharacter.setStatPoints( buffedMus, baseMusPoints, 0, 0, buffedMox, baseMoxPoints ); matcher = SpelunkyRequest.HP_PATTERN.matcher( responseText ); if ( matcher.find() ) { int currentHP = StringUtilities.parseInt( matcher.group( 1 ) ); int maximumHP = StringUtilities.parseInt( matcher.group( 2 ) ); KoLCharacter.setHP( currentHP, maximumHP, maximumHP ); } matcher = SpelunkyRequest.TURNS_PATTERN.matcher( responseText ); int turnsLeft = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.GOLD_PATTERN.matcher( responseText ); int gold = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ).replaceAll( ",", "" ) ) : 0; matcher = SpelunkyRequest.BOMB_PATTERN.matcher( responseText ); int bombs = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.ROPE_PATTERN.matcher( responseText ); int ropes = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.KEY_PATTERN.matcher( responseText ); int keys = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.BUDDY_PATTERN.matcher( responseText ); String buddy = matcher.find() ? matcher.group( 1 ) : ""; matcher = SpelunkyRequest.UNLOCK_STATUS_PATTERN.matcher( spelunkyStatus ); String unlocks = matcher.find() ? matcher.group( 1 ) : ""; matcher = SpelunkyRequest.GEAR_SECTION_PATTERN.matcher( responseText ); String gear = matcher.find() ? matcher.group( 1 ) : ""; matcher = SpelunkyRequest.EQUIPMENT_PATTERN.matcher( gear ); while ( matcher.find() ) { String descId = matcher.group( 1 ); int itemId = ItemDatabase.getItemIdFromDescription( descId ); AdventureResult item = ItemPool.get( itemId, 1 ); switch ( ItemDatabase.getConsumptionType( itemId ) ) { case KoLConstants.EQUIP_HAT: EquipmentManager.setEquipment( EquipmentManager.HAT, item ); break; case KoLConstants.EQUIP_WEAPON: EquipmentManager.setEquipment( EquipmentManager.WEAPON, item ); break; case KoLConstants.EQUIP_OFFHAND: EquipmentManager.setEquipment( EquipmentManager.OFFHAND, item ); switch ( itemId ) { case ItemPool.SPELUNKY_SKULL: KoLCharacter.addAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Pot" ); KoLCharacter.removeAvailableSkill( "Throw Torch" ); break; case ItemPool.SPELUNKY_ROCK: KoLCharacter.addAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Pot" ); KoLCharacter.removeAvailableSkill( "Throw Torch" ); break; case ItemPool.SPELUNKY_POT: KoLCharacter.addAvailableSkill( "Throw Pot" ); KoLCharacter.removeAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Torch" ); break; case ItemPool.SPELUNKY_TORCH: KoLCharacter.addAvailableSkill( "Throw Torch" ); KoLCharacter.removeAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Pot" ); break; case ItemPool.SPELUNKY_COFFEE_CUP: case ItemPool.SPELUNKY_PICKAXE: KoLCharacter.removeAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Pot" ); KoLCharacter.removeAvailableSkill( "Throw Torch" ); break; } break; case KoLConstants.EQUIP_CONTAINER: EquipmentManager.setEquipment( EquipmentManager.CONTAINER, item ); break; case KoLConstants.EQUIP_ACCESSORY: EquipmentManager.setEquipment( EquipmentManager.ACCESSORY1, item ); break; } } if ( gear.contains( ">hat<" ) ) { EquipmentManager.setEquipment( EquipmentManager.HAT, EquipmentRequest.UNEQUIP ); } if ( gear.contains( ">off<" ) ) { EquipmentManager.setEquipment( EquipmentManager.OFFHAND, EquipmentRequest.UNEQUIP ); KoLCharacter.removeAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Pot" ); KoLCharacter.removeAvailableSkill( "Throw Torch" ); } if ( gear.contains( ">back<" ) ) { EquipmentManager.setEquipment( EquipmentManager.CONTAINER, EquipmentRequest.UNEQUIP ); } if ( gear.contains( ">hand<" ) ) { EquipmentManager.setEquipment( EquipmentManager.WEAPON, EquipmentRequest.UNEQUIP ); } if ( gear.contains( ">shoes<" ) ) { EquipmentManager.setEquipment( EquipmentManager.ACCESSORY1, EquipmentRequest.UNEQUIP ); EquipmentManager.setEquipment( EquipmentManager.ACCESSORY2, EquipmentRequest.UNEQUIP ); EquipmentManager.setEquipment( EquipmentManager.ACCESSORY3, EquipmentRequest.UNEQUIP ); } if ( responseText.contains( "spelghostarms.gif" ) ) { ghostWaving = true; } StringBuffer newUnlocks = new StringBuffer( unlocks ); if ( responseText.contains( "'Sticky Bombs'" ) && !unlocks.contains( "Sticky Bombs" ) ) { newUnlocks.append( ", Sticky Bombs" ); } // Make right skills available based on resources if ( bombs > 0 ) { KoLCharacter.addAvailableSkill( "Throw Bomb" ); } else { KoLCharacter.removeAvailableSkill( "Throw Bomb" ); } if ( bombs >= 10 ) { KoLCharacter.addAvailableSkill( "Throw Ten Bombs" ); } else { KoLCharacter.removeAvailableSkill( "Throw Ten Bombs" ); } if ( ropes > 0 ) { KoLCharacter.addAvailableSkill( "Use Rope" ); } else { KoLCharacter.removeAvailableSkill( "Use Rope" ); } // Have we gained a buddy? Log it if ( turnsLeft != 40 && !buddy.equals( "" ) && !spelunkyStatus.contains( buddy ) ) { StringBuilder buddyMessage = new StringBuilder( "" ); buddyMessage.append( "You have found a new Buddy, " ); buddyMessage.append( buddy ); String message = buddyMessage.toString(); RequestLogger.printLine( message ); RequestLogger.updateSessionLog( message ); } // Write status string StringBuilder statusString = new StringBuilder( "" ); statusString.append( "Turns: " ); statusString.append( turnsLeft ); if ( ghostWaving ) { statusString.append( ", Non-combat Due" ); } statusString.append( ", Gold: " ); statusString.append( gold ); statusString.append( ", Bombs: " ); statusString.append( bombs ); statusString.append( ", Ropes: " ); statusString.append( ropes ); statusString.append( ", Keys: " ); statusString.append( keys ); statusString.append( ", Buddy: " ); statusString.append( buddy ); statusString.append( ", Unlocks: " ); statusString.append( newUnlocks ); Preferences.setString( "spelunkyStatus", statusString.toString() ); String upgradeString = Preferences.getString( "spelunkyUpgrades" ); StringBuilder newUpgradeString = new StringBuilder( upgradeString ); // If we have all upgrades, no point looking at upgrades // If first turn, update upgrades unlocked if ( !upgradeString.equals( "YYYYYYYYY" ) && turnsLeft == 40 ) { if ( gold == 100 ) { newUpgradeString.replace( 3, 6, "YYY" ); } else if ( bombs == 3 ) { newUpgradeString.replace( 3, 4, "Y" ); } if ( keys == 1 ) { newUpgradeString.replace( 6, 9, "YYY" ); } else if ( responseText.contains( "hobofedora.gif" ) ) { newUpgradeString.replace( 6, 8, "YY" ); } else if ( ropes == 3 ) { newUpgradeString.replace( 6, 7, "Y" ); } Preferences.setString( "spelunkyUpgrades", newUpgradeString.toString() ); } KoLCharacter.recalculateAdjustments(); KoLCharacter.updateStatus(); } public static final void parseStatus( final JSONObject JSON ) throws JSONException { // api.php is not a complete replacement for charpane.php in // Spelunky, but parse equipment, at least. JSONObject equip = JSON.getJSONObject( "equipment" ); Iterator keys = equip.keys(); while ( keys.hasNext() ) { String slotName = (String) keys.next(); if ( slotName.equals( "fakehands" ) ) { continue; } int slot = EquipmentRequest.phpSlotNumber( slotName ); if ( slot == -1 ) { continue; } int itemId = equip.getInt( slotName ); AdventureResult item = EquipmentManager.equippedItem( itemId ); switch ( slot ) { case EquipmentManager.HAT: EquipmentManager.setEquipment( EquipmentManager.HAT, item ); break; case EquipmentManager.WEAPON: EquipmentManager.setEquipment( EquipmentManager.WEAPON, item ); break; case EquipmentManager.OFFHAND: EquipmentManager.setEquipment( EquipmentManager.OFFHAND, item ); switch ( itemId ) { case ItemPool.SPELUNKY_SKULL: KoLCharacter.addAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Pot" ); KoLCharacter.removeAvailableSkill( "Throw Torch" ); break; case ItemPool.SPELUNKY_ROCK: KoLCharacter.addAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Pot" ); KoLCharacter.removeAvailableSkill( "Throw Torch" ); break; case ItemPool.SPELUNKY_POT: KoLCharacter.addAvailableSkill( "Throw Pot" ); KoLCharacter.removeAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Torch" ); break; case ItemPool.SPELUNKY_TORCH: KoLCharacter.addAvailableSkill( "Throw Torch" ); KoLCharacter.removeAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Pot" ); break; default: KoLCharacter.removeAvailableSkill( "Throw Rock" ); KoLCharacter.removeAvailableSkill( "Throw Skull" ); KoLCharacter.removeAvailableSkill( "Throw Pot" ); KoLCharacter.removeAvailableSkill( "Throw Torch" ); break; } break; case EquipmentManager.CONTAINER: EquipmentManager.setEquipment( EquipmentManager.CONTAINER, item ); break; case EquipmentManager.ACCESSORY1: EquipmentManager.setEquipment( EquipmentManager.ACCESSORY1, item ); break; } } KoLCharacter.recalculateAdjustments(); KoLCharacter.updateStatus(); } public static void parseResponse( final String urlString, final String responseText ) { if ( !urlString.contains( "whichplace=spelunky" ) ) { return; } String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); Matcher matcher = SpelunkyRequest.TURNS_STATUS_PATTERN.matcher( spelunkyStatus ); int turnsLeft = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.GOLD_STATUS_PATTERN.matcher( spelunkyStatus ); int gold = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.BOMB_STATUS_PATTERN.matcher( spelunkyStatus ); int bombs = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.ROPE_STATUS_PATTERN.matcher( spelunkyStatus ); int ropes = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.KEY_STATUS_PATTERN.matcher( spelunkyStatus ); int keys = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.BUDDY_STATUS_PATTERN.matcher( spelunkyStatus ); String buddy = matcher.find() ? matcher.group( 1 ) : ""; matcher = SpelunkyRequest.UNLOCK_STATUS_PATTERN.matcher( spelunkyStatus ); String unlocks = matcher.find() ? matcher.group( 1 ) : ""; boolean ghostWaving = spelunkyStatus.contains( "Non-combat Due" ); boolean jungleUnlocked = unlocks.contains( "Jungle" ) || responseText.contains( "spelunky/jungle.gif" ); boolean iceCavesUnlocked = unlocks.contains( "Ice Caves" ) || responseText.contains( "spelunky/icecaves.gif" ); boolean templeRuinsUnlocked = unlocks.contains( "Temple Ruins" ) || responseText.contains( "spelunky/templeruins.gif" ); boolean snakePitUnlocked = unlocks.contains( "Snake Pit" ) || responseText.contains( "spelunky/snakepit.gif" ); boolean spiderHoleUnlocked = unlocks.contains( "Spider Hole" ) || responseText.contains( "spelunky/spiderhole.gif" ); boolean burialGroundUnlocked = unlocks.contains( "Burial Ground" ) || responseText.contains( "spelunky/burialground.gif" ); boolean beehiveUnlocked = unlocks.contains( "Beehive" ) || responseText.contains( "spelunky/beehive.gif" ); boolean crashedUFOUnlocked = unlocks.contains( "Crashed UFO" ) || responseText.contains( "spelunky/ufo.gif" ); boolean altarUnlocked = unlocks.contains( "Altar" ) || responseText.contains( "spelunky/altar.gif" ); boolean cityOfGooooldUnlocked = unlocks.contains( "City of Goooold" ) || responseText.contains( "spelunky/citygold.gif" ); boolean LOLmecLairUnlocked = unlocks.contains( "LOLmec's Lair" ) || responseText.contains( "spelunky/lolmec.gif" ); boolean HellUnlocked = unlocks.contains( "Hell" ) || responseText.contains( "spelunky/heckofirezzz.gif" ); boolean YomamaThroneUnlocked = unlocks.contains( "Yomama's Throne" ) || responseText.contains( "spelunky/yomama.gif" ); StringBuffer newUnlocks = new StringBuffer( unlocks ); if ( jungleUnlocked && !unlocks.contains( "Jungle" ) ) { if ( !unlocks.equals( "" ) ) { newUnlocks.append( ", " ); } newUnlocks.append( "Jungle" ); } if ( iceCavesUnlocked && !unlocks.contains( "Ice Caves" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "Ice Caves" ); } if ( templeRuinsUnlocked && !unlocks.contains( "Temple Ruins" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "Temple Ruins" ); } if ( snakePitUnlocked && !unlocks.contains( "SnakePit" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "Snake Pit" ); } if ( spiderHoleUnlocked && !unlocks.contains( "Spider Hole" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "Spider Hole" ); } if ( beehiveUnlocked && !unlocks.contains( "Beehive" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "Beehive" ); } if ( burialGroundUnlocked && !unlocks.contains( "Burial Ground" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "Burial Ground" ); } if ( crashedUFOUnlocked && !unlocks.contains( "Crashed UFO" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "Crashed UFO" ); } if ( altarUnlocked && !unlocks.contains( "Altar" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "Altar" ); } if ( cityOfGooooldUnlocked && !unlocks.contains( "City of Goooold" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "City of Goooold" ); } if ( LOLmecLairUnlocked && !unlocks.contains( "LOLmec's Lair" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "LOLmec's Lair" ); } if ( HellUnlocked && !unlocks.contains( "Hell" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "Hell" ); } if ( YomamaThroneUnlocked && !unlocks.contains( "Yomama's Throne" ) ) { if ( !unlocks.equals( "" ) || newUnlocks.length() > 0 ) { newUnlocks.append( ", " ); } newUnlocks.append( "Yomama's Throne" ); } // Write status string StringBuilder statusString = new StringBuilder( "" ); statusString.append( "Turns: " ); statusString.append( turnsLeft ); if ( ghostWaving ) { statusString.append( ", Non-combat Due" ); } statusString.append( ", Gold: " ); statusString.append( gold ); statusString.append( ", Bombs: " ); statusString.append( bombs ); statusString.append( ", Ropes: " ); statusString.append( ropes ); statusString.append( ", Keys: " ); statusString.append( keys ); statusString.append( ", Buddy: " ); statusString.append( buddy ); statusString.append( ", Unlocks: " ); statusString.append( newUnlocks ); Preferences.setString( "spelunkyStatus", statusString.toString() ); String upgradeString = Preferences.getString( "spelunkyUpgrades" ); StringBuilder newUpgradeString = new StringBuilder( upgradeString ); // If we have all upgrades, no point looking at upgrades // If first turn, update unlocks if ( !upgradeString.equals( "YYYYYYYYY" ) && turnsLeft == 40 ) { if ( iceCavesUnlocked ) { newUpgradeString.replace( 0, 2, "YY" ); } else if ( jungleUnlocked ) { newUpgradeString.replace( 0, 1, "Y" ); } Preferences.setString( "spelunkyUpgrades", newUpgradeString.toString() ); } } public static void wonFight( final String monsterName, final String responseText ) { // Check for unlocks if ( responseText.contains( "New Area Unlocked" ) ) { if ( responseText.contains( "The Jungle" ) ) { SpelunkyRequest.unlock( "The Jungle", "Jungle" ); } if ( responseText.contains( "The Ice Caves" ) ) { SpelunkyRequest.unlock( "The Ice Caves", "Ice Caves" ); } if ( responseText.contains( "The Temple Ruins" ) ) { SpelunkyRequest.unlock( "The Temple Ruins", "Temple Ruins" ); } if ( responseText.contains( "LOLmec's Lair" ) ) { SpelunkyRequest.unlock( "LOLmec's Lair", "LOLmec's Lair" ); } } if ( monsterName.equals( "spider queen" ) ) { SpelunkyRequest.spiderQueenDefeated(); } if ( !monsterName.equals( "shopkeeper" ) && !monsterName.equals( "ghost (Spelunky)" ) ) { SpelunkyRequest.incrementWinCount(); } } public static void spiderQueenDefeated() { String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); if ( !spelunkyStatus.contains( "Sticky Bombs" ) ) { Preferences.setString( "spelunkyStatus", ( spelunkyStatus + ", Sticky Bombs" ) ); String message = "You have unlocked Sticky Bombs"; RequestLogger.printLine( message ); RequestLogger.updateSessionLog( message ); } } public static void incrementNonCombatPhase() { if ( Preferences.increment( "spelunkyNextNoncombat", 1 ) > 3 ) { Preferences.setInteger( "spelunkyNextNoncombat", 1 ); } } public static void incrementWinCount() { // Once the win count reaches 3, a noncombat is available. If // you adventure in Hell, which does not have a non-combat, the // noncombat continues to be available. If you do not take it // before winning 3 more fights, a noncombat remains available // - but at the next phase step. if ( Preferences.increment( "spelunkyWinCount" ) == 6 ) { SpelunkyRequest.incrementNonCombatPhase(); Preferences.setInteger( "spelunkyWinCount", 3 ); } } public static void parseChoice( final int choice, final String responseText, final int decision ) { // Sacrifice doesn't increment win count or counter if ( choice != 1040 && choice != 1041 ) { // If you win more than three fights before taking a // noncombat, the extra wins count towards the next // noncombat Preferences.decrement( "spelunkyWinCount", 3 ); // Shopkeeper doesn't increment the counter til you leave or fight if ( choice != 1028 || decision >= 5 ) { SpelunkyRequest.incrementNonCombatPhase(); } } switch ( choice ) { case 1030: // It's a Trap! A Dart Trap if ( responseText.contains( "The Spider Hole" ) ) { SpelunkyRequest.unlock( "The Spider Hole", "Spider Hole" ); } else if ( responseText.contains( "The Snake Pit" ) ) { SpelunkyRequest.unlock( "The Snake Pit", "Snake Pit" ); } break; case 1032: // It's a Trap! A Tiki Trap. if ( responseText.contains( "The Ancient Burial Ground" ) ) { SpelunkyRequest.unlock( "The Ancient Burial Ground", "Burial Ground" ); } else if ( responseText.contains( "The Beehive" ) ) { SpelunkyRequest.unlock( "The Beehive", "Beehive" ); } break; case 1034: // A Landmine if ( responseText.contains( "An Ancient Altar" ) ) { SpelunkyRequest.unlock( "An Ancient Altar", "Altar" ); } else if ( responseText.contains( "The Crashed U. F. O." ) ) { SpelunkyRequest.unlock( "The Crashed U. F. O.", "Crashed UFO" ); } break; case 1037: // It's a Trap! A Smashy Trap. if ( responseText.contains( "The City of Goooold" ) ) { SpelunkyRequest.unlock( "The City of Goooold", "City of Goooold" ); } break; case 1041: // Spelunkrifice if ( decision == 1 ) { String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); Matcher matcher = SpelunkyRequest.BUDDY_STATUS_PATTERN.matcher( spelunkyStatus ); String buddy = matcher.find() ? matcher.group( 1 ) : ""; StringBuilder sacrificeMessage = new StringBuilder( "" ); sacrificeMessage.append( "You have sacrificed your Buddy, " ); sacrificeMessage.append( buddy ); String message = sacrificeMessage.toString(); RequestLogger.printLine( message ); RequestLogger.updateSessionLog( message ); Preferences.increment( "spelunkySacrifices", 1 ); } break; } SpelunkyRequest.gainGold( responseText ); } public static void unlock( final String logLocation, final String prefLocation ) { String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); if ( !spelunkyStatus.contains( prefLocation ) ) { Preferences.setString( "spelunkyStatus", ( spelunkyStatus + ", " + prefLocation ) ); String message = "You have unlocked " + logLocation; RequestLogger.printLine( message ); RequestLogger.updateSessionLog( message ); } } private static final Pattern GOLD_GAIN_PATTERN = Pattern.compile( "(?:goldnug.gif|coinpurse.gif|lolmecidol.gif) (?:.*?)+<b>(\\d+) Gold!</b>" ); private static void gainGold( final String responseText ) { Matcher goldMatcher = SpelunkyRequest.GOLD_GAIN_PATTERN.matcher( responseText ); while ( goldMatcher.find() ) { String gain = goldMatcher.group( 1 ); String updateMessage = "You gain " + gain + " gold"; RequestLogger.updateSessionLog( updateMessage ); KoLmafia.updateDisplay( updateMessage ); } } public static void upgrade( final int choice ) { String upgradeString = Preferences.getString( "spelunkyUpgrades" ); StringBuilder newUpgradeString = new StringBuilder( upgradeString ); if ( choice >= 1 && choice <= 9 && !upgradeString.equals( "YYYYYYYYY" ) ) { newUpgradeString.replace( choice - 1, choice, "Y" ); // Log upgrade StringBuilder upgradeMessage = new StringBuilder( "" ); upgradeMessage.append( "Spelunky Finished. Upgrade chosen is " ); switch( choice ) { case 1: upgradeMessage.append( "Unlock Jungle." ); break; case 2: upgradeMessage.append( "Unlock Ice Caves." ); break; case 3: upgradeMessage.append( "Unlock Temple Ruins." ); break; case 4: upgradeMessage.append( "Start with +2 bombs." ); break; case 5: upgradeMessage.append( "More Shopkeeper items for sale." ); break; case 6: upgradeMessage.append( "Begin with 100 gold." ); break; case 7: upgradeMessage.append( "Start with +2 Ropes." ); break; case 8: upgradeMessage.append( "Start with Fedora." ); break; case 9: upgradeMessage.append( "Start with key." ); break; } String message = upgradeMessage.toString(); RequestLogger.printLine(); RequestLogger.printLine( message ); RequestLogger.updateSessionLog(); RequestLogger.updateSessionLog( message ); } Preferences.setString( "spelunkyUpgrades", newUpgradeString.toString() ); } public static void logShop( final String responseText, final int decision ) { // We are choosing to buy from shop Matcher matcher = ChoiceManager.DECISION_BUTTON_PATTERN.matcher( responseText ); while ( matcher.find() ) { int choice = StringUtilities.parseInt( matcher.group( 1 ) ); String choiceText = matcher.group( 2 ); if ( choice == decision && choice != 6 ) { String message = "Buying" + choiceText.substring( 3 ); RequestLogger.printLine( message ); RequestLogger.updateSessionLog( message ); } String upgradeString = Preferences.getString( "spelunkyUpgrades" ); if ( choice == 4 && !upgradeString.equals( "YYYYYYYYY" ) ) { StringBuilder newUpgradeString = new StringBuilder( upgradeString ); newUpgradeString.replace( 4, 5, "Y" ); Preferences.setString( "spelunkyUpgrades", newUpgradeString.toString() ); } } } public static String getBuddyName() { String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); Matcher matcher = SpelunkyRequest.BUDDY_STATUS_PATTERN.matcher( spelunkyStatus ); String buddy = matcher.find() ? matcher.group( 1 ) : null; return buddy; } public static String getBuddyImageName() { String buddy = SpelunkyRequest.getBuddyName(); if ( buddy == null ) { return null; } for ( String[] s : BUDDY ) { if ( buddy.contains( s[ 0 ] ) ) { return s[1]; } } return null; } public static String getBuddyRole() { String buddy = SpelunkyRequest.getBuddyName(); if ( buddy == null ) { return null; } for ( String[] s : BUDDY ) { if ( buddy.contains( s[ 0 ] ) ) { return s[2]; } } return null; } public static int getGold() { String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); Matcher matcher = SpelunkyRequest.GOLD_STATUS_PATTERN.matcher( spelunkyStatus ); return matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; } public static int getBomb() { String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); Matcher matcher = SpelunkyRequest.BOMB_STATUS_PATTERN.matcher( spelunkyStatus ); return matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; } public static int getRope() { String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); Matcher matcher = SpelunkyRequest.ROPE_STATUS_PATTERN.matcher( spelunkyStatus ); return matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; } public static int getKey() { String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); Matcher matcher = SpelunkyRequest.KEY_STATUS_PATTERN.matcher( spelunkyStatus ); return matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; } public static int getTurnsLeft() { String spelunkyStatus = Preferences.getString( "spelunkyStatus" ); Matcher matcher = SpelunkyRequest.TURNS_STATUS_PATTERN.matcher( spelunkyStatus ); return matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; } public static boolean registerRequest( final String urlString ) { // place.php?whichplace=spelunky String action = GenericRequest.getAction( urlString ); if ( action == null ) { return true; } String location = null; if ( action.equals( "spelunky_camp" ) ) { location = urlString.contains( "ghostyghostghost=clown" ) ? "Base Camp" : "Rest at Base Camp"; } else if ( action.equals( "spelunky_side6" ) ) { location = "The Altar"; } else if ( action.equals( "spelunky_quit" ) ) { location = "Exit"; } else if ( action.equals( "spelunky_board" ) ) { return true; } else { return false; } String message = "{" + SpelunkyRequest.getTurnsLeft() + "} " + location; RequestLogger.printLine(); RequestLogger.printLine( message ); RequestLogger.updateSessionLog(); RequestLogger.updateSessionLog( message ); return true; } public static final void decorateSpelunkyExit( final StringBuffer buffer ) { int index = buffer.indexOf( "<center><A href=main.php>Back to the Main Map</a></center>" ); if ( index == -1 ) { return; } index += 8; StringBuilder section = new StringBuilder(); section.append( "<a href=\"" ); section.append( "inv_use.php?pwd=" ); section.append( GenericRequest.passwordHash ); section.append( "&which=3&whichitem=8063" ); section.append( "\">" ); section.append( "Read another copy of Tales of Spelunking" ); section.append( "</a></center><center><p>" ); buffer.insert( index, section.toString() ); } public static final void decorateSpelunkyMonster( final StringBuffer buffer ) { // Simplified, since Skills, Elemental Damage, and Bonus // Critical Hits are not applicable // There are special combat rules in effect during a Spelunking adventure: // // * You never miss or fumble. // * The monster never misses or fumbles. // Player stats int muscle = KoLCharacter.getAdjustedMuscle(); int moxie = KoLCharacter.getAdjustedMoxie(); // Monster stats MonsterData monster = MonsterStatusTracker.getLastMonster(); int monsterAttack = MonsterStatusTracker.getMonsterAttack(); int monsterDefense = MonsterStatusTracker.getMonsterDefense(); int physicalResistance = monster == null ? 0 : monster.getPhysicalResistance(); // Calculate your expected combat damage AdventureResult weapon = EquipmentManager.getEquipment( EquipmentManager.WEAPON ); AdventureResult offhand = EquipmentManager.getEquipment( EquipmentManager.OFFHAND ); int weaponItemId = weapon.getItemId(); int offhandItemId = offhand.getItemId(); // Hit stat is MUSCLE, MOXIE, or NONE, if no weapon equipped. Stat stat = EquipmentDatabase.getWeaponStat( weaponItemId ); // Damage from your hit stat is the amount that it exceeds the // monster's defense, or 3/4 of that for ranged weapons int statDamage = stat == Stat.MUSCLE ? Math.max( muscle - monsterDefense, 0 ) : stat == Stat.MOXIE ? Math.max( ( moxie - monsterDefense ) * 3 / 4, 0 ) : // Can you fight bare-handed in Spelunky? Math.max( ( muscle / 4 ) - monsterDefense, 0 ); // Weapon power determines damage range: 10% - 20% int power = EquipmentDatabase.getPower( weaponItemId ); // (Minimum) Damage from your weapon is one tenth the weapon's power int weaponDamageMin = Math.max( Math.round( power / 10.0f ), 1 ); int weaponDamageMax = weaponDamageMin * 2; // Spelunky weapons can have bonus damage int bonusWeaponDamage = (int)Modifiers.getNumericModifier( "Item", weaponItemId, "Weapon Damage" ); int bonusOffhandDamage = (int)Modifiers.getNumericModifier( "Item", offhandItemId, "Weapon Damage" ); int bonusRangedDamage = (int)Modifiers.getNumericModifier( "Item", weaponItemId, "Ranged Damage" ); int bonusDamage = bonusWeaponDamage + ( stat == Stat.MOXIE ? bonusRangedDamage : 0 ) + bonusOffhandDamage; buffer.append( "<br />Your damage: " ); buffer.append( SpelunkyRequest.adjustedDamageString( statDamage + weaponDamageMin + bonusDamage, physicalResistance ) ); buffer.append( "-" ); buffer.append( SpelunkyRequest.adjustedDamageString( statDamage + weaponDamageMax + bonusDamage, physicalResistance ) ); // You have a 9% chance of scoring a critical hit, which // doubles the weapon damage component of combat damage // * You never miss or fumble. buffer.append( " (9% critical) = " ); buffer.append( SpelunkyRequest.adjustedDamageString( statDamage + (int)Math.floor( weaponDamageMin * 1.09 ) + bonusDamage, physicalResistance ) ); buffer.append( "-" ); buffer.append( SpelunkyRequest.adjustedDamageString( statDamage + (int)Math.floor( weaponDamageMax * 1.09 ) + bonusDamage, physicalResistance ) ); // Append monster's expected combat damage // Raw monster damage int monsterStatDamage = Math.max( monsterAttack - moxie, 0 ); // Some Spelunky items provide Damage Reduction int dr = (int)KoLCharacter.currentNumericModifier( Modifiers.DAMAGE_REDUCTION ); int monsterDamageMin = Math.max( monsterStatDamage + monsterAttack / 5 - dr, 1 ); int monsterDamageMax = Math.max( monsterStatDamage + monsterAttack / 4 - dr, 1 ); buffer.append( "<br />His damage: " ); buffer.append( String.valueOf( monsterDamageMin ) ); buffer.append( "-" ); buffer.append( String.valueOf( monsterDamageMax ) ); // * The monster never misses or fumbles. /* // Monster Hit chance // // http://kolspading.com/forums/viewtopic.php?f=3&t=36 // // Monster Hit Chance formula is: hit_successful = ( (Monster Attack - Player Moxie) + rand(0,9) - rand(0,9) >= 0 // // When your Moxie is 9 or more under the Monster's Attack, you will always be hit (unless it fumbles). // When your Moxie is 10 or more above the Monster's Attack, you will never be hit (unless it crits). // // flat 6% chance for a critical. // flat 6% chance for a fumble. float monsterHitChance = CombatUtilities.hitChance( monsterAttack, moxie, 0.06f, 0.06f ); buffer.append( " (" ); buffer.append( String.valueOf( (int)Math.round( 100.0f * monsterHitChance ) ) ); buffer.append( "% hit 6% fumble 6% critical) = " ); buffer.append( String.valueOf( (int)Math.round( monsterHitChance * monsterDamageMin ) ) ); buffer.append( "-" ); buffer.append( String.valueOf( (int)Math.round( monsterHitChance * monsterDamageMax ) ) ); */ } private static final String adjustedDamageString( int damage, int physicalResistance ) { if ( physicalResistance > 0 ) { damage = ( damage * ( 100 - physicalResistance) ) / 100; } if ( damage == 0) { damage = 1; } return String.valueOf( damage ); } public static final String spelunkyWarning( final KoLAdventure adventure, final String confirm ) { // If we have won fewer than 3 combats since the last // noncombat, no noncombat is due. if ( Preferences.getInteger( "spelunkyWinCount" ) < 3 ) { // *** here is where combat warnings would be created return null; } String location = adventure.getAdventureName(); // A noncombat is due. Tell the user about all the available // noncombat options and give him a chance to confirm that he // really intends to adventure in the location he has selected int phase = Preferences.getInteger( "spelunkyNextNoncombat" ); String message = "The ghost is waving and a phase " + phase + " noncombat is available. Click on the icon above to adventure in " + location + " or equip yourself appropriately and click on one of the locations below to go there instead."; String status = Preferences.getString( "spelunkyStatus" ); Matcher matcher = SpelunkyRequest.BOMB_STATUS_PATTERN.matcher( status ); int bombs = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.ROPE_STATUS_PATTERN.matcher( status ); int ropes = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.KEY_STATUS_PATTERN.matcher( status ); int keys = matcher.find() ? StringUtilities.parseInt( matcher.group( 1 ) ) : 0; matcher = SpelunkyRequest.BUDDY_STATUS_PATTERN.matcher( status ); String buddy = matcher.find() ? matcher.group( 1 ) : ""; matcher = SpelunkyRequest.UNLOCK_STATUS_PATTERN.matcher( status ); String unlocks = matcher.find() ? matcher.group( 1 ) : ""; boolean mines = true; boolean jungle = unlocks.contains( "Jungle" ); boolean iceCaves = unlocks.contains( "Ice Caves" ); boolean templeRuins = unlocks.contains( "Temple Ruins" ); boolean snakePit = unlocks.contains( "Snake Pit" ); boolean spiderHole = unlocks.contains( "Spider Hole" ); boolean burialGround = unlocks.contains( "Burial Ground" ); boolean beehive = unlocks.contains( "Beehive" ); boolean crashedUFO = unlocks.contains( "Crashed UFO" ); boolean altar = unlocks.contains( "Altar" ); boolean cityOfGoooold = unlocks.contains( "City of Goooold" ); StringBuilder buffer = new StringBuilder( message ); buffer.append( "<p><table>" ); if ( mines ) { buffer.append( "<tr>" ); buffer.append( "<td>" ); buffer.append( spelunkyLocationLink( "The Mines", AdventurePool.THE_MINES_ID, confirm ) ); buffer.append( "</td><td>" ); if ( phase == 1 ) { buffer.append( "gain 20 gold" ); // Assume you can only have 1 pot if ( !SpelunkyRequest.haveItem( SpelunkyRequest.POT ) ) { buffer.append( "<br>" ); buffer.append( "pot" ); } } else if ( phase == 2 ) { buffer.append( "A Shop" ); } else if ( phase == 3 ) { String divider = ""; if ( SpelunkyRequest.haveItem( SpelunkyRequest.WHIP ) ) { buffer.append( divider ); buffer.append( "use equipped whip and take 5 damage" ); divider = "<br>"; } if ( !snakePit && !spiderHole ) { if ( bombs > 0 ) { buffer.append( divider ); buffer.append( "use a bomb to open the Snake Pit" ); divider = "<br>"; } if ( ropes > 0 ) { buffer.append( divider ); buffer.append( "use a rope to open the Spider Pit" ); divider = "<br>"; } } if ( SpelunkyRequest.haveItem( SpelunkyRequest.SKULL ) || SpelunkyRequest.haveItem( SpelunkyRequest.ROCK ) || SpelunkyRequest.haveItem( SpelunkyRequest.POT ) || SpelunkyRequest.haveItem( SpelunkyRequest.TORCH )) { buffer.append( divider ); buffer.append( "discard equipped off-hand item to take no damage" ); divider = "<br>"; } buffer.append( divider ); buffer.append( "use no resources and take 10 damage" ); divider = "<br>"; } buffer.append( "</td>" ); buffer.append( "</tr>" ); } if ( jungle ) { buffer.append( "<tr>" ); buffer.append( "<td>" ); buffer.append( spelunkyLocationLink( "The Jungle", AdventurePool.THE_JUNGLE_ID, confirm ) ); buffer.append( "</td><td>" ); if ( phase == 1 ) { buffer.append( "A Shop" ); } else if ( phase == 2 ) { buffer.append( "gain 20-25 gold or a buddy" ); String divider = "<br>"; // Assume you can only have 1 shotgun if ( SpelunkyRequest.haveItem( SpelunkyRequest.HEAVY_PICKAXE ) && !SpelunkyRequest.haveItem( SpelunkyRequest.SHOTGUN )) { buffer.append( divider ); buffer.append( "wield pick-axe to get a shotgun" ); } // Assume you can only have 1 The Clown Crown if ( SpelunkyRequest.haveItem( SpelunkyRequest.XRAY_GOGGLES ) && !SpelunkyRequest.haveItem( SpelunkyRequest.CLOWN_CROWN )) { buffer.append( divider ); buffer.append( "wear x-ray goggles to find The Clown Crown" ); } } else if ( phase == 3 ) { String divider = ""; if ( SpelunkyRequest.haveItem( SpelunkyRequest.SPRING_BOOTS ) ) { buffer.append( divider ); buffer.append( "avoid trap using equipped spring boots" ); divider = "<br>"; } if ( !beehive && !burialGround ) { if ( bombs > 0 ) { buffer.append( divider ); buffer.append( "use a bomb to open The Beehive" ); if ( !status.contains( "Sticky Bombs" ) ) { buffer.append( " and take 15 damage" ); } divider = "<br>"; } if ( ropes > 0 ) { buffer.append( divider ); buffer.append( "use a rope to open the The Ancient Burial Ground" ); buffer.append( " and take 15 damage" ); if ( SpelunkyRequest.haveItem( SpelunkyRequest.YELLOW_CAPE ) || SpelunkyRequest.haveItem( SpelunkyRequest.JETPACK )) { buffer.append( " (unless you equip a back item)" ); } divider = "<br>"; } buffer.append( divider ); buffer.append( "take 30 damage" ); } } buffer.append( "</td>" ); buffer.append( "</tr>" ); } if ( iceCaves ) { buffer.append( "<tr>" ); buffer.append( "<td>" ); buffer.append( spelunkyLocationLink( "The Ice Caves", AdventurePool.THE_ICE_CAVES_ID, confirm ) ); buffer.append( "</td><td>" ); if ( phase == 1 ) { buffer.append( "A Shop" ); } else if ( phase == 2 ) { buffer.append( "gain 50-60 gold" ); if ( SpelunkyRequest.haveItem( SpelunkyRequest.TORCH ) ) { buffer.append( "<br>" ); buffer.append( buddy.equals( "" ) ? "get a buddy" : "gain 60-70 gold" ); } } else if ( phase == 3 ) { String divider = ""; if ( !altar && !crashedUFO ) { buffer.append( divider ); buffer.append( "take 10 damage and open The Altar" ); divider = "<br>"; if ( ropes >= 3 ) { buffer.append( divider ); buffer.append( "use 3 ropes and open The Crashed U.F.O." ); divider = "<br>"; } } buffer.append( divider ); buffer.append( "take 30 damage" ); } buffer.append( "</td>" ); buffer.append( "</tr>" ); } if ( templeRuins ) { buffer.append( "<tr>" ); buffer.append( "<td>" ); buffer.append( spelunkyLocationLink( "The Temple Ruins", AdventurePool.THE_TEMPLE_RUINS_ID, confirm ) ); buffer.append( "</td><td>" ); if ( phase == 1 ) { buffer.append( "A Crate" ); } else if ( phase == 2 ) { String divider = ""; if ( SpelunkyRequest.haveItem( SpelunkyRequest.JETPACK ) ) { buffer.append( divider ); buffer.append( "gain 250 gold using jetpack" ); divider = "<br>"; } if ( SpelunkyRequest.haveItem( SpelunkyRequest.YELLOW_CAPE ) && SpelunkyRequest.haveItem( SpelunkyRequest.SPRING_BOOTS )) { buffer.append( divider ); buffer.append( "gain 250 gold using yellow cape and spring boots" ); divider = "<br>"; } if ( buddy.equals( "Resourceful Kid" ) ) { buffer.append( divider ); buffer.append( "gain 250 gold using Resourceful Kid" ); divider = "<br>"; } buffer.append( divider ); buffer.append( "gain 250 gold and take 50 damage" ); divider = "<br>"; } else if ( phase == 3 ) { String divider = ""; if ( keys > 0 && !cityOfGoooold ) { buffer.append( divider ); buffer.append( "use key to open The City of Goooold" ); divider = "<br>"; } buffer.append( divider ); buffer.append( "take 40 damage" ); divider = "<br>"; } buffer.append( "</td>" ); buffer.append( "</tr>" ); } if ( snakePit ) { buffer.append( "<tr>" ); buffer.append( "<td>" ); buffer.append( spelunkyLocationLink( "The Snake Pit", AdventurePool.THE_SNAKE_PIT_ID, confirm ) ); buffer.append( "</td><td>" ); buffer.append( "A Crate" ); buffer.append( "</td>" ); buffer.append( "</tr>" ); } if ( spiderHole ) { buffer.append( "<tr>" ); buffer.append( "<td>" ); buffer.append( spelunkyLocationLink( "The Spider Hole", AdventurePool.THE_SPIDER_HOLE_ID, confirm ) ); buffer.append( "</td><td>" ); buffer.append( "gain 15-20 gold" ); String divider = "<br>"; if ( SpelunkyRequest.haveItem( SpelunkyRequest.MACHETE ) ) { buffer.append( divider ); buffer.append( "wield sturdy machete to " ); buffer.append( buddy.equals( "" ) ? "get a buddy" : "gain 30-40 gold" ); } if ( SpelunkyRequest.haveItem( SpelunkyRequest.TORCH ) ) { buffer.append( divider ); buffer.append( "wield torch to gain 30-50 gold" ); } buffer.append( "</td>" ); buffer.append( "</tr>" ); } if ( burialGround ) { buffer.append( "<tr>" ); buffer.append( "<td>" ); buffer.append( spelunkyLocationLink( "The Ancient Burial Ground", AdventurePool.THE_ANCIENT_BURIAL_GROUND_ID, confirm ) ); buffer.append( "</td><td>" ); buffer.append( "gain 20-25 gold or a buddy" ); String divider = "<br>"; // Assume you can only have 1 shotgun if ( SpelunkyRequest.haveItem( SpelunkyRequest.HEAVY_PICKAXE ) && !SpelunkyRequest.haveItem( SpelunkyRequest.SHOTGUN )) { buffer.append( divider ); buffer.append( "wield pick-axe to get a shotgun" ); } // Assume you can only have 1 The Clown Crown if ( SpelunkyRequest.haveItem( SpelunkyRequest.XRAY_GOGGLES ) && !SpelunkyRequest.haveItem( SpelunkyRequest.CLOWN_CROWN )) { buffer.append( divider ); buffer.append( "wear x-ray goggles to find The Clown Crown" ); } buffer.append( "</td>" ); buffer.append( "</tr>" ); } if ( beehive ) { buffer.append( "<tr>" ); buffer.append( "<td>" ); buffer.append( spelunkyLocationLink( "The Beehive", AdventurePool.THE_BEEHIVE_ID, confirm ) ); buffer.append( "</td><td>" ); buffer.append( "A Crate" ); buffer.append( "</td>" ); buffer.append( "</tr>" ); } if ( crashedUFO ) { buffer.append( "<tr>" ); buffer.append( "<td>" ); buffer.append( spelunkyLocationLink( "The Crashed U.F.O.", AdventurePool.THE_CRASHED_UFO_ID, confirm ) ); buffer.append( "</td><td>" ); buffer.append( "A Crate" ); buffer.append( "</td>" ); buffer.append( "</tr>" ); } if ( cityOfGoooold ) { buffer.append( "<tr>" ); buffer.append( "<td>" ); buffer.append( spelunkyLocationLink( "The City of Goooold", AdventurePool.THE_CITY_OF_GOOOOLD_ID, confirm ) ); buffer.append( "</td><td>" ); String divider = ""; if ( keys > 0 ) { buffer.append( divider ); buffer.append( "use key to gain 150 gold" ); divider = "<br>"; } if ( bombs > 0 ) { buffer.append( divider ); buffer.append( "use bomb to gain 80-100 gold" ); divider = "<br>"; } buffer.append( divider ); buffer.append( "gain 50-60 gold and take 20 damage" ); buffer.append( "</td>" ); buffer.append( "</tr>" ); } buffer.append( "</table>" ); return buffer.toString(); } private static final boolean haveItem( final AdventureResult item ) { return item.getCount( KoLConstants.inventory ) > 0 || InventoryManager.getEquippedCount( item ) > 0; } private static final String spelunkyLocationLink( String name, final String id, final String confirm ) { StringBuilder link = new StringBuilder(); link.append( "<a href=\"adventure.php?snarfblat=" ); link.append( id ); link.append( "&" ); link.append( confirm ); link.append( "=on" ); link.append( "\"><img src=\"/images/" ); link.append( SpelunkyRequest.adventureImage( name ) ); link.append( "\" height=105 border=0 alt=\"" ); link.append( name ); link.append( "\" title=\"" ); link.append( name ); link.append( "\"></a>" ); return link.toString(); } }