/** * 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.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.KoLAdventure; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLConstants.MafiaState; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.RequestLogger; import net.sourceforge.kolmafia.RequestThread; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.session.ChoiceManager; import net.sourceforge.kolmafia.session.ResultProcessor; import net.sourceforge.kolmafia.utilities.ChoiceUtilities; import net.sourceforge.kolmafia.utilities.InputFieldUtilities; import net.sourceforge.kolmafia.utilities.StringUtilities; public class ArcadeRequest extends GenericRequest { public static final AdventureResult TOKEN = ItemPool.get( ItemPool.GG_TOKEN, 1 ); public static final AdventureResult TICKET = ItemPool.get( ItemPool.GG_TICKET, 1 ); private String action = null; public ArcadeRequest() { super( "place.php&whichplace=arcade", false ); } public ArcadeRequest( final String action ) { super( "", false ); // Construct a URL to submit via GET, just like the browser StringBuilder newURLString = new StringBuilder( "place.php?whichplace=arcade&action=" ); newURLString.append( action ); this.constructURLString( newURLString.toString(), false ); } public static final int getTurnsUsed( GenericRequest request ) { String action = request.getFormField( "action" ); if ( action == null ) { return 0; } if ( action.contains( "demonstar" ) || action.contains( "meteoid" ) || action.contains( "fighters" ) || action.contains( "fist" ) || action.contains( "spacetrip" )) { return 5; } return 0; } @Override public void processResults() { ArcadeRequest.parseResponse( this.getURLString(), this.responseText ); if ( this.action == null ) { return; } if ( this.action.equals( "arcade_skeeball" ) ) { // You don't have any Game Grid tokens, so you can't // play Skee-Ball. But don't feel bad. The Skee-Ball // machine is broken, so you wouldn't have been able to // play Skee-Ball anyway. if ( this.responseText.contains( "You don't have any Game Grid tokens" ) ) { KoLmafia.updateDisplay( MafiaState.ERROR, "You don't have any Game Grid tokens." ); } else { KoLmafia.updateDisplay( "Token transformed into tickets." ); } } } public static void parseResponse( final String urlString, final String responseText ) { if ( !urlString.contains( "whichplace=arcade" ) ) { return; } String action = GenericRequest.getAction( urlString ); if ( action == null ) { return; } if ( action.equals( "arcade_skeeball" ) ) { // You don't have any Game Grid tokens, so you can't // play Skee-Ball. But don't feel bad. The Skee-Ball // machine is broken, so you wouldn't have been able to // play Skee-Ball anyway. if ( !responseText.contains( "you can't play Skee-Ball" ) ) { ResultProcessor.processItem( ItemPool.GG_TOKEN, -1 ); } return; } if ( action.equals( "arcade_plumber" ) ) { // We visited Jackass Plumber for the day Preferences.setBoolean( "_defectiveTokenChecked", true ); if ( responseText.contains( "defective Game Grid token" ) ) { InputFieldUtilities.alert( "You found a defective Game Grid token!" ); } return; } } public static final boolean registerRequest( final String urlString ) { if ( !urlString.contains( "whichplace=arcade" ) ) { return false; } String action = GenericRequest.getAction( urlString ); if ( action == null ) { return false; } if ( Preferences.getInteger( "lastArcadeAscension" ) != KoLCharacter.getAscensions() ) { RequestThread.postRequest( new GenericRequest( "place.php?whichplace=town_wrong" ) ); } int count = TOKEN.getCount( KoLConstants.inventory ); boolean usesToken = false; String message = null; String name = null; if ( action.equals( "arcade_plumber" ) ) { message = "Visiting Jackass Plumber"; } // Other actions of interest require tokens. Do we have any? else if ( count < 1 ) { return true; } else if ( action.equals( "arcade_skeeball" ) ) { message = "Visiting Broken Skee-Ball Machine"; } // Other actions require turns. Do we have enough? else if ( KoLCharacter.getAdventuresLeft() < 5 ) { return true; } else if ( action.equals( "arcade_spacetrip" ) ) { name = "Space Trip"; usesToken = true; } else if ( action.equals( "arcade_demonstar" ) ) { name = "DemonStar"; usesToken = true; } else if ( action.equals( "arcade_meteoid" ) ) { name = "Meteoid"; usesToken = true; } else if ( action.equals( "arcade_fighters" ) ) { name = "The Fighters of Fighting"; usesToken = true; } else if ( action.equals( "arcade_fist" ) ) { name = "Dungeon Fist!"; usesToken = true; } else { return false; } if ( name != null ) { // We have a token and 5 adventures. message = "[" + KoLAdventure.getAdventureCount() + "] " + name; } // Ideally, we'd do this in parseResponse. However, in the case // of the five games, you will be in the middle of playing. // Easier to just pay the price here. if ( usesToken ) { ResultProcessor.processItem( ItemPool.GG_TOKEN, -1 ); } RequestLogger.updateSessionLog(); RequestLogger.updateSessionLog( message ); return true; } public static final boolean arcadeChoice( final int choice ) { // Do not look for "Encounters" inside arcade choices switch ( choice ) { case 460: case 461: case 462: case 463: case 464: case 465: case 467: case 468: case 469: case 470: case 472: case 473: case 474: case 475: case 476: case 477: case 478: case 479: case 480: case 481: case 482: case 483: case 484: // Space Trip case 471: // DemonStar case 485: // Fighters Of Fighting case 486: // Dungeon Fist! case 488: case 489: case 490: case 491: // Meteoid return true; } return false; } /* * Support for individual games */ private static final void logText( final String text ) { RequestLogger.printLine( text ); RequestLogger.updateSessionLog( text ); } private final static Pattern CHOICE_PATTERN = Pattern.compile( "<form.*?name='?option'? value='?(\\d+)'?.*?>.*?class='?button'?.*?value=['\"](.*?)['\"].*?></form>", Pattern.DOTALL ); // The strings tagging each available choice with associated index private static Integer [] indices = null; private static String [] choices = null; private static void parseChoiceNames( final String responseText ) { ArrayList options = new ArrayList(); ArrayList names = new ArrayList(); Matcher matcher = CHOICE_PATTERN.matcher( responseText ); while ( matcher.find() ) { options.add( new Integer( matcher.group( 1 ) ) ); names.add( StringUtilities.globalStringReplace( matcher.group( 2 ), " ", "" ) ); } ArcadeRequest.indices = new Integer [ options.size() ]; options.toArray( ArcadeRequest.indices ); ArcadeRequest.choices = new String [ names.size() ]; names.toArray( ArcadeRequest.choices ); } private static String findChoiceName( final int index ) { if ( indices != null && choices != null ) { for ( int i = 0; i < indices.length; ++i ) { if ( indices[i].intValue() == index ) { return choices[i]; } } } return null; } /* Space Trip */ private static int week; private static int crew; private static int money; private static int gas; private static int time; public static final void visitSpaceTripChoice( final String responseText ) { // Called when we visit Space Trip ArcadeRequest.week = 0; ArcadeRequest.crew = 30; ArcadeRequest.money = 0; ArcadeRequest.gas = 100; ArcadeRequest.time = 52; // Parse out the choice names ArcadeRequest.parseChoiceNames( responseText ); } public static final void logSpaceTripAction( final String responseText, final int lastChoice, final int lastDecision ) { // Called when we are about to take a choice in SpaceTrip String action = ArcadeRequest.findChoiceName( lastDecision ); if ( action == null ) { return; } boolean week = false; boolean log = false; // Don't log navigation around the space ship or base switch ( lastChoice ) { case 468: // Starbase Hub if ( !action.equals( "Visit the General Store" ) && !action.equals( "Visit the Military Surplus Store" ) && !action.equals( "Back to Navigation Console" ) ) { // Log any purchases log = true; } break; case 469: // General Store case 470: // Military Surplus Store if ( !action.equals( "Back to Starbase Hub" ) ) { // Log any purchases log = true; } break; case 461: // Navigation if ( !action.equals( "Back to the Bridge" ) ) { // Log sector selection and starbase log = true; } break; case 460: // Bridge case 462: // Diagnostics // Game control navigation break; case 463: // Alpha Quadrant case 464: // Beta Quadrant case 477: // Gamma Quadrant // Exploring in a Quadrant advances time if ( action.startsWith( "Launch an Astrozorian Commerce Grenade" ) || action.startsWith( "Investigate the Source" ) ) { log = true; } else if ( action.contains( "Scadian Homeworld" ) && responseText.contains( "Protector of Scadia" ) ) { log = true; } else if ( !action.startsWith( "Back to Navigation" ) ) { week = true; } break; default: // Log the action the player took log = true; break; } if ( week ) { ArcadeRequest.week++; ArcadeRequest.logText( "Week " + ArcadeRequest.week + ": " + action ); } if ( log ) { ArcadeRequest.logText( "Action: " + action ); } } private final static Pattern SPACE_TRIP_RESOURCE_PATTERN = Pattern.compile( "<tr><td><b>Crew:</b> (\\d*)<br><b>Gas:</b> (\\d*) gal.</td><td width=50></td><td><b>Money:</b> ([0123456789,]*) Crabs<br><b>Time Left:</b> (\\d*) weeks</td></tr>", Pattern.DOTALL ); private final static Pattern CRABS_PATTERN = Pattern.compile( "have (?:transferred|recovered) <b>([0123456789,]*).*?Crabs</b>", Pattern.DOTALL ); private final static Pattern CREW1_PATTERN = Pattern.compile( "(?:We lost )?([0123456789,]+) crew members(?: were lost)?", Pattern.DOTALL ); private final static Pattern CREW2_PATTERN = Pattern.compile( "([0123456789,]+) (?:sentient )?beings", Pattern.DOTALL ); private final static Pattern TOTAL_SCORE_PATTERN = Pattern.compile( "Total Score:.*?<b>([0123456789,]*)</b>", Pattern.DOTALL ); public static final void postChoiceSpaceTrip( final GenericRequest request, final int lastChoice, final int lastDecision ) { // Called when we have taken a choice in SpaceTrip // Log what we see String responseText = request.responseText; // Log action appropriately ArcadeRequest.logSpaceTripAction( responseText, lastChoice, lastDecision ); // If ten of your crew members have time to come to our big // party, I'm sure it would be wonderful for everybody! // // The biggest parties happen here! if ( responseText.contains( "come to our big party" ) || responseText.contains( "The biggest parties happen here" ) ) { ArcadeRequest.logText( "Encounter: Slavers" ); } // Biological scanners show no signs of organic life, but the // ship is broadcasting an identity signal: MINE-29-DEATH-149 // -- how shall we approach it, sir? // // The Murderbots at this colony must be programmed to kill // intruders on sight. else if ( responseText.contains( "no signs of organic life" ) || responseText.contains( "Murderbots at this colony" ) ) { ArcadeRequest.logText( "Encounter: Murderbot Mining Ship" ); } // Captain, we've discovered something interesting -- it is // definitely a Murderbot vessel, but it has no weapons, and is // equipped with a much larger than usual communications array. else if ( responseText.contains( "much larger than usual communications array" ) ) { ArcadeRequest.logText( "Encounter: Murderbot Control Ship" ); } // Captain, we've been ambushed by another Murderbot // vessel. Get ready for a fight! else if ( responseText.contains( "ambushed by another Murderbot vessel" ) ) { ArcadeRequest.logText( "Encounter: Murderbot Cruiser" ); } // Captain, we've neared the Scadian homeworld, and it is // currently under siege by a Murderbot Dreadnought. Which... // has detected us, apparently, and is now shooting at us. // // Captain, as soon as we got near the Murderhive, we were // immediately attacked by this Dreadnought. We're not going to // be able to get inside there without getting this ID // Transmitter fixed. else if ( responseText.contains( "under siege by a Murderbot Dreadnought" ) || responseText.contains( "attacked by this Dreadnought" ) ) { ArcadeRequest.logText( "Encounter: Murderbot Dreadnought" ); } // Captain, I've got good news and bad news. The good news is // that the ID Transmitter worked on the Dreadnoughts, and // allowed us to fly into the center of the Murderhive. The bad // news is that the Murderbot Mothership's computers didn't // fall for the trick, and now we're in some serious, serious // trouble. else if ( responseText.contains( "the Murderbot Mothership's computers didn't" ) ) { ArcadeRequest.logText( "Encounter: Murderbot Mothership" ); } // Hail to thee, noble traveler! I come a great distance, at // great peril, in search of aid for my Scadian countrymen. // Will you lend me your ear, good wanderer? else if ( responseText.contains( "aid for my Scadian countrymen" ) ) { ArcadeRequest.logText( "Encounter: Scadian Ship" ); ArcadeRequest.week--; } // Captain, we're being hailed by a Hipsterian vessel. else if ( responseText.contains( "being hailed by a Hipsterian vessel" ) ) { ArcadeRequest.logText( "Encounter: Hipsterian Ship" ); } // Ello, sah baldy. Mebbe mi can help yuh wid someting? // else if ( responseText.contains( "Ello, sah baldy" ) ) { ArcadeRequest.logText( "Encounter: Astrozorian Trade Vessel" ); } // Captain, it's... If... If it wasn't so evil, it would // be... beautiful. They would have should have sent a poet. else if ( responseText.contains( "They should have sent a poet" ) ) { ArcadeRequest.logText( "Encounter: The Source" ); } // We lost 5 crew members in the attack... // 3 crew members were lost... Matcher crewMatcher = CREW1_PATTERN.matcher( responseText ); if ( crewMatcher.find() ) { int crew = StringUtilities.parseInt( crewMatcher.group(1) ); ArcadeRequest.logText( "You lost " + KoLConstants.COMMA_FORMAT.format( crew ) + " crew members" ); } // We have launched the mineral payload in the direction of the // CHOAD company's nearest drop station, and they have // transferred <b>405 Crabs</b> to your account, Captain. // // We have uploaded the biological data into the ASPCA // mainframe, and they have transferred <b>388 Crabs</b> // to your account, Captain. // // Our salvage teams have recovered <b>322 Crabs</b> worth of // parts from the wreckage of the mining drone Matcher crabsMatcher = CRABS_PATTERN.matcher( responseText ); if ( crabsMatcher.find() ) { int crabs = StringUtilities.parseInt( crabsMatcher.group(1) ); ArcadeRequest.logText( "You gain " + KoLConstants.COMMA_FORMAT.format( crabs ) + " Crabs" ); } // Captain, we've managed to extract an intact stasis enclosure // from the wreck of the enemy ship, and it contained 15 // sentient beings. We've thawed them out, and they've // decided, in their gratitude, to join our crew. // // Captain, we've managed to rescue 24 beings from stasis pods // floating in the wreckage of the enemy ship. crewMatcher = CREW2_PATTERN.matcher( responseText ); if ( crewMatcher.find() ) { int crew = StringUtilities.parseInt( crewMatcher.group(1) ); ArcadeRequest.logText( "You rescue " + KoLConstants.COMMA_FORMAT.format( crew ) + " crew members" ); } // Look at current resources Matcher resourceMatcher = SPACE_TRIP_RESOURCE_PATTERN.matcher( responseText ); if ( resourceMatcher.find() ) { int crew = StringUtilities.parseInt( resourceMatcher.group(1) ); int gas = StringUtilities.parseInt( resourceMatcher.group(2) ); int money = StringUtilities.parseInt( resourceMatcher.group(3) ); int time = StringUtilities.parseInt( resourceMatcher.group(4) ); if ( crew != ArcadeRequest.crew || money != ArcadeRequest.money || gas != ArcadeRequest.gas || time != ArcadeRequest.time ) { ArcadeRequest.logText( "Crew: " + KoLConstants.COMMA_FORMAT.format( crew ) + ". Gas: " + KoLConstants.COMMA_FORMAT.format( gas ) + " gallons. Money: " + KoLConstants.COMMA_FORMAT.format( money ) + " Crabs. Time left: " + KoLConstants.COMMA_FORMAT.format( time ) + " weeks."); ArcadeRequest.crew = crew; ArcadeRequest.money = money; ArcadeRequest.gas = gas; ArcadeRequest.time = time; } } // Finally, see if the game is over Matcher totalMatcher = ArcadeRequest.TOTAL_SCORE_PATTERN.matcher( responseText ); if ( totalMatcher.find() ) { ArcadeRequest.logText( "Total Score: " + totalMatcher.group(1) ); // The game is over. No more choices. ArcadeRequest.indices = null; ArcadeRequest.choices = null; return; } // Parse out the new choice names ArcadeRequest.parseChoiceNames( responseText ); } /* End Space Trip */ /* DemonStar */ private static int blurstite; private static int wounds; public static final void visitDemonStarChoice( final String responseText ) { // Called when we visit DemonStar // Parse out the choice names ArcadeRequest.parseChoiceNames( responseText ); ArcadeRequest.blurstite = 0; ArcadeRequest.wounds = 0; } private final static Pattern DEMONSTAR_MOVE_PATTERN = Pattern.compile( "mv=([-01]+)(,|%2C)([-01]+)" ); private static final String parseDemonStarAction( final GenericRequest request, final int lastDecision ) { String action = ArcadeRequest.findChoiceName( lastDecision ); // Actions like "Mine" or "Fight" if ( action != null ) { return action; } Matcher matcher = ArcadeRequest.DEMONSTAR_MOVE_PATTERN.matcher( request.getURLString() ); if ( matcher.find() ) { int dx = StringUtilities.parseInt( matcher.group( 1 ) ); int dy = StringUtilities.parseInt( matcher.group( 3 ) ); switch( dx ) { case -1: switch ( dy ) { case -1: return "Move northwest"; case 0: return "Move west"; case 1: return "Move southwest"; } break; case 0: switch ( dy ) { case -1: return "Move north"; case 0: return "Stay put"; case 1: return "Move south"; } break; case 1: switch ( dy ) { case -1: return "Move northeast"; case 0: return "Move east"; case 1: return "Move southeast"; } break; } } return null; } public static final void postChoiceDemonStar( final GenericRequest request, final int lastDecision ) { // Called when we have taken a choice in DemonStar String action = ArcadeRequest.parseDemonStarAction( request, lastDecision ); if ( action != null ) { // Log the action the player took ArcadeRequest.logText( "Action: " + action ); } // Log what we see String responseText = request.responseText; // Looking out the viewscreen, you see that this region of // space is basically empty, except for a large gray asteroid, // floating serenely in the... well, in the nothing. if ( responseText.contains( "a large gray asteroid" ) ) { ArcadeRequest.logText( "Encounter: an asteroid" ); } // Also, there's an octagonal flying saucer with a large turret // on the top that quickly swivels to face you. // // You are interrupted in your chosen task by the nearby // tanklike flying saucer, which swoops toward you, firing red // bursts of energy from its central turre if ( responseText.contains( "octagonal flying saucer" ) || responseText.contains( "nearby tanklike flying saucer" ) ) { ArcadeRequest.logText( "Encounter: a warrior" ); } // Also, there's a strange red crab-like spaceship -- or // possibly robot? -- which is poking at the asteroid with its // claws. // Also, there's a strange red crab-like spaceship -- or // possibly robot? -- which has a large crystal clutched in its // claws and seems to be on its way somewhere. if ( responseText.contains( "strange red crab-like spaceship" ) ) { ArcadeRequest.logText( "Encounter: a worker" ); } // Looking out the viewscreen, you see that this region of // space is crowded as heck. A ton of crab-like worker drones // are constructing a giant demon face, of all things, for the // front of the massive battle-station they're building. Who // builds a giant space demon head? This is ridiculous. if ( responseText.contains( "ton of crab-like worker drones" ) ) { ArcadeRequest.logText( "Encounter: a worker" ); ArcadeRequest.logText( "Encounter: DemonStar under construction" ); } // You're fighting the DemonStar. if ( responseText.contains( "You're fighting the DemonStar" ) ) { ArcadeRequest.logText( "Encounter: the DemonStar" ); } // BEWARE! I LIVE! if ( responseText.contains( "BEWARE! I LIVE!" ) ) { ArcadeRequest.logText( "The DemonStar awakes." ); } // Blurstite crystal collected if ( responseText.contains( "Blurstite crystal collected" ) ) { ArcadeRequest.blurstite++; ArcadeRequest.logText( "You acquire a bomb. (" + ArcadeRequest.blurstite + ")" ); } // From somewhere in the distance, you hear an explosion. "A // blurstium charge has been intercepted by an enemy robot," // the computer reports. if ( responseText.contains( "blurstium charge has been intercepted" ) ) { ArcadeRequest.logText( "A bomb has been intercepted." ); } // From somewhere in the distance, you hear an explosion and a // loud metallic roar. <i?>"1 blurstium charge has located // the target and successfully detonated,"</i> says the // shipboard computer. if ( responseText.contains( "a loud metallic roar" ) ) { ArcadeRequest.wounds++; ArcadeRequest.logText( "A bomb wounds the DemonStar. (" + ArcadeRequest.wounds + ")" ); } // Finally, see if the game is over Matcher matcher = ArcadeRequest.FINAL_SCORE_PATTERN.matcher( responseText ); if ( matcher.find() ) { String message = ""; if ( responseText.contains( "YOU HAVE DESTROYED THE DEMONSTAR!" ) ) { message = "YOU HAVE DESTROYED THE DEMONSTAR! "; } else { message = "YOU HAVE FAILED. "; } ArcadeRequest.logText( message + matcher.group(0) ); // The game is over. No more choices. ArcadeRequest.indices = null; ArcadeRequest.choices = null; return; } // Parse out the new choice names ArcadeRequest.parseChoiceNames( responseText ); } /* End DemonStar */ /* Dungeon Fist! */ /* +----------+ +----------+ +----------+ +----------+ | | | | | | | | | EXIT X----+ Grunts +----+ MAGIC +----+ Ghosts | | | | | | POTION | | | +----------+ +-----+----+ +----------+ +-----+----+ | | +----------+ +-----+----+ +----------+ +-----+----+ | | | | | | | | | +----+ +----+ | | FOOD | | | | | | | | | +-----+----+ +-----+----+ +-----+----+ +----------+ | | | +-----+----+ +-----+----+ +-----+----+ +----------+ | | | Grunts | | | | | | +----+ COMBAT +----+ Death +----+ Demons | | | | POTION | | | | KEY | +----------+ +-----+----+ +-----+----+ +-----+----+ | | | +----------+ +-----+----+ +-----+----+ +-----+----+ | | | | | | | | | Demons +----+ START +----+ | | | | | | | | | | | +-----+----+ +----------+ +-----+----+ +-----+----+ | | | +-----+----+ +----------+ +-----X----+ +-----+----+ | | | | | | | Ghosts | | Ghosts +----+ MUSCLE | | TREASURE | | FOOD | | | | POTION | | | | KEY | +----------+ +----------+ +----------+ +----------+ */ public static final void visitDungeonFistChoice( final String responseText ) { // Called when we visit Dungeon Fist! // Parse out the choice names ArcadeRequest.parseChoiceNames( responseText ); } private final static Pattern FINAL_SCORE_PATTERN = Pattern.compile( "FINAL SCORE:? ([0123456789,]*)", Pattern.DOTALL ); public static final void postChoiceDungeonFist( final GenericRequest request, final int lastDecision ) { // Called when we have taken a choice in Dungeon Fist! String action = ArcadeRequest.findChoiceName( lastDecision ); if ( action != null ) { // Log the action the player took ArcadeRequest.logText( "Action: " + action ); } // Log what we see String responseText = request.responseText; // First: look for encounters if ( responseText.contains( "bright pink" ) ) { ArcadeRequest.logText( "Encounter: Grunts" ); } // You wipe off your blade and look thoughtfully at the big // stone box. Then you thoughtfully decide to smash it to bits. // // Now that that's taken care of, you turn to regard the large // stone box. That's probably not something you want to leave // intact, you reckon. // // Having dealt with the monsters, you decide it's time to deal // with this big stone box-thing. else if ( responseText.contains( "look thoughtfully at the big stone box" ) || responseText.contains( "it's time to deal with this big stone box" ) || responseText.contains( "turn to regard the large stone box" ) ) { ArcadeRequest.logText( "Encounter: Large Stone Boxes" ); } else if ( responseText.contains( "horrible demonic creatures" ) || responseText.contains( "fire-breathing demons" ) ) { ArcadeRequest.logText( "Encounter: Demons" ); } else if ( responseText.contains( "gray spectres" ) || responseText.contains( "angry tormented spirits" )) { ArcadeRequest.logText( "Encounter: Ghosts" ); } // Now that all the ghosts are taken care of, you decide you // probably shouldn't leave those piles of bones lying around. // // Finished with those mindless wraiths, you decide to clean up // These piles of bones. And by 'clean' I mean 'smash'. // // Having dealt with the ghosts, you decide to bust up their // bones as well. That'll teach 'em! // // The ghosts are all gone, but the little piles of bones // remain. Probably ought to do something about that. else if ( responseText.contains( "shouldn't leave those piles of bones" ) || responseText.contains( "decide to clean up these piles of bones" ) || responseText.contains( "decide to bust up their bones" ) || responseText.contains( "the little piles of bones remain" ) ) { ArcadeRequest.logText( "Encounter: Bone Piles" ); } else if ( responseText.contains( "A seven-foot tall humanoid figure" ) ) { ArcadeRequest.logText( "Encounter: Death" ); } // Second: look for items if ( responseText.contains( "you find a large brass key" ) ) { ArcadeRequest.logText( "You find a key" ); } else if ( responseText.contains( "A blue potion bottle rests on the floor in the alcove" ) ) { ArcadeRequest.logText( "You find a Magic Potion" ); } else if ( responseText.contains( "discover a large blue bottle" ) ) { ArcadeRequest.logText( "You find a Muscle Potion" ); } else if ( responseText.contains( "you find a large glowing blue bottle" ) ) { ArcadeRequest.logText( "You find a Combat Potion" ); } else if ( responseText.contains( "SOMEONE SHOT THE FOOD!" ) ) { ArcadeRequest.logText( "You shoot the food" ); } else if ( responseText.contains( "even if it isn't actually food" ) ) { ArcadeRequest.logText( "You find food" ); } // Third: look for room features: if ( responseText.contains( "strange light-blue metal" ) ) { ArcadeRequest.logText( "You find a locked door" ); if ( responseText.contains( "the wall is gone" ) || responseText.contains( "the wall disappears" ) ) { ArcadeRequest.logText( "You unlock the door" ); if ( responseText.contains( "a large wooden treasure chest" ) ) { ArcadeRequest.logText( "You find treasure" ); } else if ( responseText.contains( "a square black pit" ) ) { ArcadeRequest.logText( "You find the exit" ); } } } // Finally, see if the game is over Matcher matcher = ArcadeRequest.FINAL_SCORE_PATTERN.matcher( responseText ); if ( matcher.find() ) { String message = ""; if ( responseText.contains( "YOU HAVE ESCAPED THE DUNGEON!" ) ) { message = "YOU HAVE ESCAPED THE DUNGEON! "; } else if ( responseText.contains( "YOU HAVE DIED." ) ) { message = "YOU HAVE DIED. "; } ArcadeRequest.logText( message + matcher.group(0) ); // The game is over. No more choices. ArcadeRequest.indices = null; ArcadeRequest.choices = null; return; } // Parse out the new choice names ArcadeRequest.parseChoiceNames( responseText ); } public static final void decorateDungeonFist( final StringBuffer buffer ) { if ( !Preferences.getBoolean( "arcadeGameHints" ) ) { return; } // If you've finished the game once, KoL gives you a "Finish // from Memory" button which will give you 30 tickets. In that // case, it is pointless for us to provide a similar button. if ( buffer.indexOf( "You drop your token into" ) != -1 && buffer.indexOf( "Finish from Memory" ) == -1 ) { ChoiceManager.addGoalButton( buffer, "30 Game Grid tickets" ); } // With CAB or Stationary Buttons, need to put this inside the div's too. if( buffer.indexOf( "</div></div></body>" ) != -1 ) { StringUtilities.singleStringReplace( buffer, "</div></div></body>", "<center><p><img src='/images/otherimages/arcade/DungeonFistMap.png' width=544 height=672 alt='Snapshot of initial maze' title='Snapshot of initial maze'></center></div></div></body>" ); } else { StringUtilities.singleStringReplace( buffer, "</body>", "<center><p><img src='/images/otherimages/arcade/DungeonFistMap.png' width=544 height=672 alt='Snapshot of initial maze' title='Snapshot of initial maze'></center></body>" ); } } private static final String FistScript = "3111111111111111111111111111112112111111111111111111111111121" + "1111111111111111211122211111121111111111111111122211133111113"; public static final String autoDungeonFist( final int stepCount, final String responseText ) { if ( stepCount < 0 || stepCount >= FistScript.length() ) { return "0"; } // If you've won this game before, you can finish it automatically String option = ChoiceUtilities.actionOption( "Finish from Memory", responseText ); if ( option != null ) { return option; } RelayRequest.specialCommandStatus = "<progress value=" + stepCount + " max=" + FistScript.length() + " style=\"width: 100%;\">Dungeon Fist! step " + stepCount + " of " + FistScript.length() + "</progress>"; return FistScript.substring( stepCount, stepCount + 1 ); } /* End Dungeon Fist! */ /* Fighters of Fighting */ // Opponents private static final int KITTY = 0; private static final int MORBIDDA = 1; private static final int ROO = 2; private static final int SERENITY = 3; private static final int THORNY = 4; private static final int VASO = 5; private static final String [] OSTRING = new String[] { "Kitty the Zmobie Basher", "Morbidda", "Roo", "Serenity", "Thorny Toad", "Vaso De Agua", }; private static final int findOpponent( final String name ) { for ( int i = 0; i < OSTRING.length; ++i ) { if ( OSTRING[i].equals( name ) ) { return i; } } return -1; } // Moves private static final int HEAD_KICK = 0; private static final int GROIN_KICK = 1; private static final int LEG_SWEEP = 2; private static final int THROAT_PUNCH = 3; private static final int GUT_PUNCH = 4; private static final int KNEE_PUNCH = 5; private static final String [] MSTRING = new String[] { "Head Kick", "Groin Kick", "Leg Sweep", "Throat Punch", "Gut Punch", "Knee Punch", }; private static final String [] MCODE = new String[] { "hk", "gk", "lk", "tp", "gp", "kp", }; private static final int findPlayerMove( final GenericRequest request ) { String field = request.getFormField( "attack" ); if ( field != null ) { for ( int i = 0; i < MCODE.length; ++i ) { if ( MCODE[i].equals( field ) ) { return i; } } } return -1; } // Threat Strings: Indexed by opponent, threat private static final String [][] THREATS = new String[][] { // Kitty the Zmobie Basher { // hk, gk, ls, tp, gp, kp "launches a kick straight at your forehead", "get some paininess in your sexy parts", "ready to sweep your leg", "about to punch you in the throat", "like a punch to the gut", "aims a punch at your kneecap", }, // Morbidda { // hk, gk, ls, tp, gp, kp "launching itself at your head", "aims a knee square at your groin", "trying to trip you up", "aims it at your throat", "aims a punch at your solar plexus", "fist to kneecap", }, // Roo { // hk, gk, ls, tp, gp, kp "aims one big, flat foot at your head", "aims a foot right at your crotch", "aims his tail at your ankles", "punch you in the throat", "prepares to suckerpunch you in the gut", "aims a punch square at your kneecap", }, // Serenity { // hk, gk, ls, tp, gp, kp "a hard boot to the head", "a nice, solid kick to the gonads", "knock your ankles out from under you", "launches a fist at your throat", "punched in the small intestine", "about to punch you in the knee", }, // Thorny Toad { // hk, gk, ls, tp, gp, kp "a vicious kick to the head", "he's going for the groin", "crouches to try and sweep your legs", "launches a fist at your throat", "aims it square at your gut", "aims a punch at your knee", }, // Vaso De Agua { // hk, gk, ls, tp, gp, kp "you see his foot flying at your head", "a well-placed foot to the groin", "my feet had been knocked out from under me", "aims it straight at your throat", "aims a helpful fist at your gut", "about to punch you in the knee", }, }; private static final int findThreat( final int opponent, final String challenge ) { if ( opponent < 0 ) { return -1; } String [] challenges = THREATS[ opponent ]; for ( int i = 0; i < challenges.length; ++i ) { if ( challenge.contains( challenges[ i ] ) ) { return i; } } return -1; } private static final String findThreatName( final String name, final String challenge ) { int threat = ArcadeRequest.findThreat( findOpponent( name ), challenge ); return threat < 0 ? null : MSTRING[ threat ]; } // Effectiveness private static final int FAIL = 0; private static final int POOR = 1; private static final int FAIR = 2; private static final int GOOD = 3; private static final String [] ESTRING = new String[] { "Fail", "Poor", "Fair", "Good", }; private static final int [][][] EFFECTIVENESS = new int [][][] { // Kitty the Zmobie Basher { // vs HK: hk gk ls tp gp kp { GOOD, FAIR, FAIL, FAIL, FAIL, POOR }, // vs GK: hk gk ls tp gp kp { FAIR, FAIL, GOOD, POOR, FAIL, FAIL }, // vs LS: hk gk ls tp gp kp { FAIL, GOOD, FAIR, FAIL, POOR, FAIL }, // vs TP: hk gk ls tp gp kp { FAIL, FAIL, POOR, FAIR, FAIL, GOOD }, // vs GP: hk gk ls tp gp kp { POOR, FAIL, FAIL, FAIL, GOOD, FAIR }, // vs KP: hk gk ls tp gp kp { FAIL, POOR, FAIL, GOOD, FAIR, FAIL }, }, // Morbidda { // vs HK: hk gk ls tp gp kp { FAIL, GOOD, FAIR, POOR, FAIL, FAIL }, // vs GK: hk gk ls tp gp kp { GOOD, FAIR, FAIL, FAIL, POOR, FAIL }, // vs LS: hk gk ls tp gp kp { FAIR, FAIL, GOOD, FAIL, FAIL, POOR }, // vs TP: hk gk ls tp gp kp { POOR, FAIL, FAIL, GOOD, FAIR, FAIL }, // vs GP: hk gk ls tp gp kp { FAIL, POOR, FAIL, FAIR, FAIL, GOOD }, // vs KP: hk gk ls tp gp kp { FAIL, FAIL, POOR, FAIL, GOOD, FAIR }, }, // Roo { // vs HK: hk gk ls tp gp kp { FAIL, POOR, FAIL, FAIL, FAIR, GOOD }, // vs GK: hk gk ls tp gp kp { POOR, FAIL, FAIL, GOOD, FAIL, FAIR }, // vs LS: hk gk ls tp gp kp { FAIL, FAIL, POOR, FAIR, GOOD, FAIL }, // vs TP: hk gk ls tp gp kp { FAIR, GOOD, FAIL, FAIL, POOR, FAIL }, // vs GP: hk gk ls tp gp kp { FAIL, FAIR, GOOD, POOR, FAIL, FAIL }, // vs KP: hk gk ls tp gp kp { GOOD, FAIL, FAIR, FAIL, FAIL, POOR }, }, // Serenity { // *** Incomplete info in Wiki // vs HK: hk gk ls tp gp kp { FAIL, FAIL, GOOD, FAIL, FAIL, FAIL }, // vs GK: hk gk ls tp gp kp { FAIL, GOOD, FAIR, FAIL, FAIL, FAIL }, // vs LS: hk gk ls tp gp kp { GOOD, FAIL, FAIL, FAIL, FAIL, FAIL }, // vs TP: hk gk ls tp gp kp { FAIL, POOR, FAIL, FAIL, GOOD, FAIL }, // vs GP: hk gk ls tp gp kp { FAIL, FAIL, FAIL, GOOD, FAIL, FAIL }, // vs KP: hk gk ls tp gp kp { POOR, FAIL, FAIL, FAIR, FAIL, GOOD }, }, // Thorny Toad { // vs HK: hk gk ls tp gp kp { POOR, FAIL, FAIL, GOOD, FAIL, FAIR }, // vs GK: hk gk ls tp gp kp { FAIL, FAIL, POOR, FAIR, GOOD, FAIL }, // vs LS: hk gk ls tp gp kp { FAIL, POOR, FAIL, FAIL, FAIR, GOOD }, // vs TP: hk gk ls tp gp kp { GOOD, FAIL, FAIR, POOR, FAIL, FAIL }, // vs GP: hk gk ls tp gp kp { FAIR, GOOD, FAIL, FAIL, FAIL, POOR }, // vs KP: hk gk ls tp gp kp { FAIL, FAIR, GOOD, FAIL, POOR, FAIL }, }, // Vaso De Agua { // vs HK: hk gk ls tp gp kp { FAIL, FAIL, POOR, FAIR, GOOD, FAIL }, // vs GK: hk gk ls tp gp kp { FAIL, POOR, FAIL, FAIL, FAIR, GOOD }, // vs LS: hk gk ls tp gp kp { FAIR, FAIL, FAIL, GOOD, FAIL, POOR }, // vs TP: hk gk ls tp gp kp { FAIL, FAIR, GOOD, FAIL, FAIL, POOR }, // vs GP: hk gk ls tp gp kp { GOOD, FAIL, FAIR, FAIL, POOR, FAIL }, // vs KP: hk gk ls tp gp kp { FAIR, GOOD, FAIL, POOR, FAIL, FAIL }, }, }; private final static Pattern MATCH_PATTERN = Pattern.compile( ""(.*?) Vs. (.*?) FIGHT!"", Pattern.DOTALL ); private static int round = 0; public static final void visitFightersOfFightingChoice( final String responseText ) { // Called when we first visit the Fighters of Fighting. Matcher matcher = MATCH_PATTERN.matcher( responseText ); if ( !matcher.find() ) { return; } String message = "Match: " + matcher.group( 1 ) + " vs. " + matcher.group( 2 ); ArcadeRequest.logText( message ); // Reset round counter ArcadeRequest.round = 0; } private final static Pattern ROUND_PATTERN = Pattern.compile( "Results:.*?<td>(.*?)</td>.*?Score: ([0123456789,]*)</td>.*?title=\"(\\d+) HP\".*?title=\"(\\d+) HP\".*?<b>(.*?)</b>.*?>VS<.*?<b>(.*?)</b>", Pattern.DOTALL ); private static final void logRound( final String text ) { ArcadeRequest.logRound( null, text ); } private static final void logRound( final String move, final String text ) { Matcher matcher = ROUND_PATTERN.matcher( text ); if ( !matcher.find() ) { return; } StringBuilder buffer = new StringBuilder(); String challenge = matcher.group( 1 ); String score = matcher.group( 2 ); String pHP = matcher.group( 3 ); String oHP = matcher.group( 4 ); String player = matcher.group( 5 ); String opponent = matcher.group( 6 ); String threat = findThreatName( opponent, challenge ); buffer.append( "Round " ); buffer.append( String.valueOf( ArcadeRequest.round ) ); buffer.append( ": " ); if ( move != null ) { buffer.append( move ); buffer.append( " " ); } buffer.append( player ); buffer.append( " (" ); buffer.append( pHP ); buffer.append( " HP) " ); buffer.append( opponent ); buffer.append( " (" ); buffer.append( oHP ); buffer.append( " HP) Score: " ); buffer.append( score ); buffer.append( " Threat: " ); buffer.append( threat ); ArcadeRequest.logText( buffer.toString() ); ArcadeRequest.round++; } private final static Pattern FINAL_ROUND_PATTERN = Pattern.compile( "Game Over!<p>(?:<b>)?(.*?)(?:</b>)?<p>Score: ([0123456789,]*)", Pattern.DOTALL ); private static final void logFinalRound( final String move, final String text ) { Matcher matcher = FINAL_ROUND_PATTERN.matcher( text ); if ( !matcher.find() ) { return; } StringBuilder buffer = new StringBuilder(); String result = matcher.group( 1 ); String score = matcher.group( 2 ); buffer.append( "Round " ); buffer.append( String.valueOf( ArcadeRequest.round ) ); buffer.append( ": " ); buffer.append( move ); buffer.append( " Result: " ); buffer.append( result ); buffer.append( " Final Score: " ); buffer.append( score ); ArcadeRequest.logText( buffer.toString() ); } public static final void postChoiceFightersOfFighting( final GenericRequest request, final int lastDecision ) { String text = request.responseText; // If this is the very first round of the match, parse and log // the threat if ( lastDecision == 6 ) { ArcadeRequest.logRound( text ); return; } // Find the move the player used int move = findPlayerMove( request ); if ( move < 0 ) { return; } if ( text.contains( "Game Over!" ) ) { ArcadeRequest.logFinalRound( MSTRING[ move ], text ); } else { ArcadeRequest.logRound( MSTRING[ move ], text ); } } public static final String autoChoiceFightersOfFighting( final GenericRequest request ) { String text = request.responseText; // If this is the initial visit, decision = 6 Matcher matcher = MATCH_PATTERN.matcher( text ); if ( matcher.find() ) { request.clearDataFields(); return "6"; } // If it is an intermediate round, choose the best move matcher = ROUND_PATTERN.matcher( text ); if ( !matcher.find() ) { return null; } String challenge = matcher.group( 1 ); String oname = matcher.group( 6 ); int opponent = findOpponent( oname ); if ( opponent < 0 ) { return null; } int threat = findThreat( opponent, challenge ); if ( threat < 0 ) { return null; } int [] effects = EFFECTIVENESS[ opponent ][ threat ]; for ( int i = 0; i < effects.length; ++i ) { if ( effects[i] == GOOD ) { request.clearDataFields(); request.addFormField( "attack", MCODE[ i ] ); return "1"; } } return null; } public static final void decorateFightersOfFighting( final StringBuffer buffer ) { if ( !Preferences.getBoolean( "arcadeGameHints" ) ) { return; } String text = buffer.toString(); Matcher matcher = ROUND_PATTERN.matcher( text ); if ( !matcher.find() ) { return; } String challenge = matcher.group( 1 ); String oname = matcher.group( 6 ); int opponent = findOpponent( oname ); if ( opponent < 0 ) { return; } int threat = findThreat( opponent, challenge ); if ( threat < 0 ) { return; } int [] effects = EFFECTIVENESS[ opponent ][ threat ]; int index1 = text.indexOf( "<form method=\"post\" action=\"choice.php\">" ); if ( index1 < 0 ) { return; } buffer.setLength( 0 ); buffer.append( text.substring( 0, index1 ) ); for ( int i = 0; i < 6; ++i ) { int index2 = text.indexOf( "</form>", index1 ); // If KoL says we've run out of choices, quit now if ( index2 == -1 ) { break; } // Start spoiler text buffer.append( text.substring( index1, index2 ) ); buffer.append( "<br><font size=-1>(" ); // Say what the choice will give you buffer.append( ESTRING[ effects[ i ] ] ); // Finish spoiler text buffer.append( ")</font></form>" ); index1 = index2 + 7; } buffer.append( text.substring( index1 ) ); } /* End Fighters of Fighting */ /* Meteoid */ private static int energy; private static int bombs; private static int missiles; private static int crystals; public static final void visitMeteoidChoice( final String responseText ) { // Called when we visit Meteoid ArcadeRequest.energy = 100; ArcadeRequest.bombs = 1; ArcadeRequest.missiles = 1; ArcadeRequest.crystals = 0; // Parse out the choice names ArcadeRequest.parseChoiceNames( responseText ); } public static final void logMetoidAction( final String responseText, final int lastChoice, final int lastDecision ) { // Called when we are about to take a choice in SpaceTrip String action = ArcadeRequest.findChoiceName( lastDecision ); if ( action == null ) { return; } // Don't log navigation around the space ship or base switch ( lastChoice ) { case 488: // Bridge if ( action.equals( "Load up SpaceMall" ) ) { return; } break; case 489: // SpaceMall if ( action.equals( "Close SpaceMall" ) ) { return; } break; case 491: // End return; } ArcadeRequest.logText( "Action: " + action ); } // <b>Energy:</b> 80<br><b>Bombs:</b> 1<br><b>Missiles:</b> 1<br><b>Credcrystals:</b> 0<center> private final static Pattern METEOID_RESOURCE_PATTERN = Pattern.compile( "<b>Energy:</b> (\\d+)<br><b>Bombs:</b> (\\d+)<br><b>Missiles:</b> (\\d+)<br><b>Credcrystals:</b> (\\d+)<center>", Pattern.DOTALL ); private final static Pattern ENERGY_PATTERN = Pattern.compile( "left behind a ball of plasma worth ([0123456789,]*) energy", Pattern.DOTALL ); private final static Pattern CRYSTAL1_PATTERN = Pattern.compile( "left behind ([0123456789,]*) Credcrystal", Pattern.DOTALL ); private final static Pattern CRYSTAL2_PATTERN = Pattern.compile( "cache of Credcrystals.*?([0123456789,]*), to be exact", Pattern.DOTALL ); private final static Pattern GUARD_PATTERN = Pattern.compile( "The room was guarded by a fierce (.*?)!", Pattern.DOTALL ); public static final void postChoiceMeteoid( final GenericRequest request, final int lastChoice, final int lastDecision ) { // Called when we have taken a choice in Meteoid // Log what we see String responseText = request.responseText; // Log action appropriately ArcadeRequest.logMetoidAction( responseText, lastChoice, lastDecision ); // The room was guarded by a fierce <monster>! Matcher guardMatcher = GUARD_PATTERN.matcher( responseText ); if ( guardMatcher.find() ) { ArcadeRequest.logText( "Encounter: " + guardMatcher.group(1) ); } // The room contained a bizarre bird-man statue, seated and // holding a spherical container. else if ( responseText.contains( "bizarre bird-man statue" ) ) { ArcadeRequest.logText( "Encounter: Statue" ); } // The room contained a terminal whose screen displayed what // appeared to be map data about the surrounding environment. else if ( responseText.contains( "map data" ) ) { ArcadeRequest.logText( "Encounter: Map Terminal" ); } // The room contained a nano-charge station. My cybersuit's // computer beeped, notifying me that I could use the station // to replenish my bombs and missiles else if ( responseText.contains( "nano-charge station" ) ) { ArcadeRequest.logText( "Encounter: Charge Station" ); } // The room contained a teleporter keyed to the planetoid's // surface -- I could use it to get back to my ship! else if ( responseText.contains( "planetoid's surface" ) ) { ArcadeRequest.logText( "Encounter: Teleporter" ); } // The intense heat of the room leached 5 energy from my // cybersuit... if ( responseText.contains( "leached 5 energy" ) ) { ArcadeRequest.logText( "You lose 5 energy" ); } // It left behind a ball of plasma worth 20 energy. Matcher energyMatcher = ENERGY_PATTERN.matcher( responseText ); if ( energyMatcher.find() ) { int energy = StringUtilities.parseInt( energyMatcher.group(1) ); ArcadeRequest.logText( "You gain " + KoLConstants.COMMA_FORMAT.format( energy ) + " energy" ); } // It left behind 9 Credcrystals! // // I opened the sphere and found a cache of Credcrystals. 291, // to be exact. Matcher crystalMatcher = CRYSTAL1_PATTERN.matcher( responseText ); if ( crystalMatcher.find() ) { int crystals = StringUtilities.parseInt( crystalMatcher.group(1) ); ArcadeRequest.logText( "You gain " + KoLConstants.COMMA_FORMAT.format( crystals ) + " Credcrystals" ); } crystalMatcher = CRYSTAL2_PATTERN.matcher( responseText ); if ( crystalMatcher.find() ) { int crystals = StringUtilities.parseInt( crystalMatcher.group(1) ); ArcadeRequest.logText( "You gain " + KoLConstants.COMMA_FORMAT.format( crystals ) + " Credcrystals" ); } // Look at current resources Matcher resourceMatcher = METEOID_RESOURCE_PATTERN.matcher( responseText ); if ( resourceMatcher.find() ) { int energy = StringUtilities.parseInt( resourceMatcher.group(1) ); int bombs = StringUtilities.parseInt( resourceMatcher.group(2) ); int missiles = StringUtilities.parseInt( resourceMatcher.group(3) ); int crystals = StringUtilities.parseInt( resourceMatcher.group(4) ); if ( energy != ArcadeRequest.energy || bombs != ArcadeRequest.bombs || missiles != ArcadeRequest.missiles || crystals != ArcadeRequest.crystals ) { ArcadeRequest.logText( "Energy: " + KoLConstants.COMMA_FORMAT.format( energy ) + ". Bombs: " + KoLConstants.COMMA_FORMAT.format( bombs ) + ". Missiles: " + KoLConstants.COMMA_FORMAT.format( missiles ) + " Credcrystals: " + KoLConstants.COMMA_FORMAT.format( crystals ) + "." ); ArcadeRequest.energy = energy; ArcadeRequest.bombs = bombs; ArcadeRequest.missiles = missiles; ArcadeRequest.crystals = crystals; } } // Finally, see if the game is over Matcher totalMatcher = ArcadeRequest.TOTAL_SCORE_PATTERN.matcher( responseText ); if ( totalMatcher.find() ) { ArcadeRequest.logText( "Total Score: " + totalMatcher.group(1) ); // The game is over. No more choices. ArcadeRequest.indices = null; ArcadeRequest.choices = null; return; } // Parse out the new choice names ArcadeRequest.parseChoiceNames( responseText ); } /* End Meteoid */ public static final void checkJackassPlumber() { boolean checked = Preferences.getBoolean( "_defectiveTokenChecked" ); if ( checked ) { // already checked for a defective token return; } boolean unlocked = Preferences.getInteger( "lastArcadeAscension" ) == KoLCharacter.getAscensions(); boolean unlockable = unlocked || // Having those items doesn't matter if it's already unlocked TOKEN.getCount( KoLConstants.inventory ) > 0 || TICKET.getCount( KoLConstants.inventory ) > 0; if ( !unlocked && unlockable ) { Preferences.setInteger( "lastArcadeAscension", KoLCharacter.getAscensions() ); RequestThread.postRequest( new GenericRequest( "place.php?whichplace=town_wrong" ) ); unlocked = true; } if ( unlocked ) { ArcadeRequest request = new ArcadeRequest( "arcade_plumber" ); RequestThread.postRequest( request ); request.processResults(); } } }