/**
* 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.webui;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.kolmafia.AdventureResult;
import net.sourceforge.kolmafia.KoLCharacter;
import net.sourceforge.kolmafia.KoLConstants;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.MonsterData;
import net.sourceforge.kolmafia.combat.MonsterStatusTracker;
import net.sourceforge.kolmafia.objectpool.ItemPool;
import net.sourceforge.kolmafia.objectpool.SkillPool;
import net.sourceforge.kolmafia.persistence.SkillDatabase;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.request.FightRequest;
import net.sourceforge.kolmafia.request.GenericRequest;
import net.sourceforge.kolmafia.request.UseSkillRequest;
import net.sourceforge.kolmafia.session.ChoiceManager;
import net.sourceforge.kolmafia.session.EquipmentManager;
import net.sourceforge.kolmafia.session.InventoryManager;
import net.sourceforge.kolmafia.session.Limitmode;
import net.sourceforge.kolmafia.utilities.ChoiceUtilities;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public class StationaryButtonDecorator
{
private static final ArrayList<String> combatHotkeys = new ArrayList<String>();
private static final boolean builtInSkill( final String skillId )
{
if ( !StringUtilities.isNumeric( skillId ) )
{
return false;
}
int skillNumber = Integer.parseInt( skillId );
if ( skillNumber == SkillDatabase.getSkillId( KoLCharacter.getClassStun() ) )
{
return true;
}
// Do not save The Source skills in buttons since they are
// usable only on Source Agents
if ( ( skillNumber / 1000 ) == 21 )
{
return true;
}
switch ( skillNumber )
{
case SkillPool.OLFACTION:
case SkillPool.CANHANDLE:
case SkillPool.SHOOT:
return true;
}
return false;
}
public static final void addSkillButton( final String skillId )
{
if ( skillId == null || skillId.equals( "none" ) )
{
return;
}
// Don't add a button for using a built-in skill
if ( StationaryButtonDecorator.builtInSkill( skillId ) )
{
return;
}
int buttons = Preferences.getInteger( "relaySkillButtonCount" );
int maximumIndex = buttons + 1;
int insertIndex = 0;
// Examine all buttons and find a place for this skill.
for ( int i = 1; i < maximumIndex; )
{
String old = Preferences.getString( "stationaryButton" + i );
// Remove built-in skills.
if ( StationaryButtonDecorator.builtInSkill( old ) )
{
StationaryButtonDecorator.removeSkill( i, buttons );
continue;
}
// If the button is already there, use it.
if ( old.equals( skillId ) )
{
insertIndex = i;
}
// If we already have an insertion point, keep it
else if ( insertIndex != 0 )
{
}
// Choose first unused button.
else if ( old.equals( "" ) || old.equals( "none" ) )
{
insertIndex = i;
}
++i;
}
// If all buttons are in use, remove oldest and insert at end.
if ( insertIndex == 0 )
{
StationaryButtonDecorator.removeSkill( 1, buttons );
insertIndex = buttons;
}
Preferences.setString( "stationaryButton" + insertIndex, skillId );
}
private static final void removeSkill( final int index, int buttons )
{
for ( int i = index ; i <= buttons; ++i )
{
String next = Preferences.getString( "stationaryButton" + ( i+1 ) );
Preferences.setString( "stationaryButton" + i, next );
}
}
public static final void removeBuiltInSkills()
{
int buttons = Preferences.getInteger( "relaySkillButtonCount" );
int maximumIndex = buttons + 1;
// Examine all buttons and find a place for this skill.
for ( int i = 1; i < maximumIndex; )
{
String old = Preferences.getString( "stationaryButton" + i );
// Remove built-in skills.
if ( StationaryButtonDecorator.builtInSkill( old ) )
{
StationaryButtonDecorator.removeSkill( i, buttons );
continue;
}
i++;
}
}
public static final void decorate( final String urlString, final StringBuffer buffer )
{
if ( Preferences.getBoolean( "hideServerDebugText" ) )
{
int beginDebug = buffer.indexOf( "<div style='max-height" );
int endDebug = buffer.indexOf( "</div>", beginDebug ) + 6;
if ( beginDebug != -1 && endDebug != -1 )
{
buffer.delete( beginDebug, endDebug );
}
}
if ( Preferences.getBoolean( "serverAddsCustomCombat" ) )
{
// Apparently KoL is always using the Amazon server for the CAB
String bufferString = "<td><img src='" + KoLmafia.AMAZON_IMAGE_SERVER_PATH + "itemimages/book3.gif' id='skills'>";
int imageIndex = buffer.indexOf( bufferString );
if ( imageIndex != -1 )
{
boolean again = FightRequest.getCurrentRound() == 0;
String location = again ? getAdventureAgainLocation( buffer ) : "fight.php?action=custom";
// Add a "script" button to the left
String script = "<td><a href='" + location + "'><img src='" + KoLmafia.imageServerPath() + "itemimages/plexpock.gif'></td><td class=spacer></td>";
buffer.insert( imageIndex, script );
// Give it either the "script" or "again" label
int labelIndex = buffer.indexOf( "<tr class=label>", imageIndex ) + 16;
buffer.insert( labelIndex, again ? "<td>again</td><td></td>" : "<td>script</td><td></td>" );
// Also add spacers to the header
labelIndex = buffer.indexOf( "<tbody><tr class=label><td></td><td></td><td>1</td><td>2</td>" ) + 23;
buffer.insert( labelIndex, "<td></td><td></td>" );
return;
}
// We are going to craft our own CAB. Pull in the necessary Javascript.
int insertIndex = buffer.indexOf( "</head>" );
if ( insertIndex != -1 )
{
String css1 = KoLmafia.imageServerPath() + "actionbar.6.css";
String css2 = KoLmafia.imageServerPath() + "actionbar.ie.4.css";
buffer.insert( insertIndex, "<link rel='stylesheet' type='text/css' href='" + css1 + "'><!--[if IE]><link rel='stylesheet' type='text/css' href='" + css2 + "'><![endif]-->" );
// Build the CAB in a new StringBuilder
StringBuilder CAB = new StringBuilder();
boolean choice = urlString.startsWith( "choice.php" ) && buffer.indexOf( "choice.php", buffer.indexOf( "<body>" ) + 1 ) != -1;
CAB.append( "<img src='" );
CAB.append( KoLmafia.imageServerPath() );
CAB.append( "itemimages/blank.gif' id='dragged'>" );
CAB.append( "<div id='debug'></div>" );
CAB.append( "<div class=contextmenu id='skillmenu'></div>" );
CAB.append( "<div class=contextmenu id='itemsmenu'></div>" );
// *** Start of 'topbar' div
CAB.append( "<div id=topbar>" );
CAB.append( "<center><table class=actionbar cellpadding=0 cellspacing=1><tbody>" );
// *** Row 1 of table: class=label cols=19
CAB.append( "<tr class=label>" );
// Column 1
CAB.append( "<td> </td>" );
// Column 2-19
for ( int i = 2; i <= 19; ++i )
{
CAB.append( "<td></td>" );
}
CAB.append( "</tr>" );
// *** Row 2 of table: class=blueback cols=19
CAB.append( "<tr class=blueback>" );
// Column 1
CAB.append( "<td><a href='" );
CAB.append( choice ? "choice.php?action=auto" : getAdventureAgainLocation( buffer ) );
CAB.append( "'><img src='" );
CAB.append( KoLmafia.imageServerPath() );
CAB.append( "itemimages/plexpock.gif'></td>" );
// Column 2
CAB.append( "<td class=spacer></td>" );
// Column 3
CAB.append( "<td><img src='" );
CAB.append( KoLmafia.imageServerPath() );
CAB.append( "itemimages/blank.gif' id='skills'></td>" );
// Column 4
CAB.append( "<td class=spacer></td>" );
// Column 5-16
for ( int i = 5; i <= 16; ++i )
{
CAB.append( "<td><img src='" );
CAB.append( KoLmafia.imageServerPath() );
CAB.append( "itemimages/blank.gif'></td>" );
}
// Column 17
CAB.append( "<td class=spacer></td>" );
// Column 18
CAB.append( "<td class=spacer></td>" );
// Column 19
CAB.append( "<td><img src='" );
CAB.append( KoLmafia.imageServerPath() );
CAB.append( "itemimages/blank.gif'></td>" );
CAB.append( "</tr>" );
// *** Row 3 of table: class=label cols=19
CAB.append( "<tr class=label>" );
// Column 1
CAB.append( "<td>" );
CAB.append( choice ? "auto" : "again" );
CAB.append( "</td>" );
// Column 2-19
for ( int i = 2; i < 19; ++i )
{
CAB.append( "<td></td>" );
}
CAB.append( "</tr>" );
CAB.append( "</tbody></table></center>" );
CAB.append( "</div>" );
// *** End of 'topbar' div
// *** Start of 'content' div
CAB.append( "<div class='content' id='content_'>" );
CAB.append( "<div id='effdiv' style='display: none;'></div>" );
// *** Start of 'overflow' div
CAB.append( "<div style='overflow: auto;'>" );
insertIndex = buffer.indexOf( "<body>" ) + 6;
buffer.insert( insertIndex, CAB.toString() );
insertIndex = buffer.indexOf( "</body>" );
if ( insertIndex > -1 )
{
buffer.insert( insertIndex, "</div></div>" );
}
else
{
buffer.append( "</div></div>" );
}
// *** End of 'overflow' div
// *** End of 'content' div
}
return;
}
if ( !Preferences.getBoolean( "relayAddsCustomCombat" ) )
{
return;
}
StationaryButtonDecorator.removeBuiltInSkills();
// Add stylesheet that controls header/page content when stationary buttons used
int insertionPoint = buffer.indexOf( "</head>" );
if ( insertionPoint == -1 )
{
return;
}
buffer.insert( insertionPoint, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/" + KoLConstants.STATIONARYBUTTONS_CSS + "\">" );
buffer.insert( insertionPoint, "<script src=\"/images/jquery-1.9.1.js\"></script><script src=\"/" + KoLConstants.STATIONARYBUTTONS_JS + "\"></script>" );
insertionPoint = buffer.indexOf( "<body" );
if ( insertionPoint == -1 )
{
return;
}
insertionPoint = buffer.indexOf( ">", insertionPoint ) + 1;
StringBuffer actionBuffer = new StringBuffer();
// *** Start of 'page' div
actionBuffer.append( "<div id=\"page\">" );
// *** Start of 'mafiabuttons' div
actionBuffer.append( "<div id=\"mafiabuttons\">" );
actionBuffer.append( "<center><table width=\"95%\"><tr><td align=left>" );
// *** Start of 'btnwrap' div
actionBuffer.append( "<div id=\"btnwrap\">" );
boolean inCombat = urlString.startsWith( "fight.php" ) && FightRequest.getCurrentRound() > 0;
boolean inChoice = urlString.startsWith( "choice.php" ) && buffer.indexOf( "choice.php", buffer.indexOf( "<body>" ) + 1 ) != -1;
// You can have either hot keys or the macro editor, since the
// former make it impossible to type numbers in the macro field
boolean useHotKeys = !Preferences.getBoolean( "macroLens" );
if ( inCombat )
{
StationaryButtonDecorator.addCombatButtons( urlString, actionBuffer );
}
else if ( inChoice )
{
StationaryButtonDecorator.addChoiceButtons( actionBuffer );
}
else
{
StationaryButtonDecorator.addNonCombatButtons( buffer, actionBuffer );
}
actionBuffer.append( "</div>" );
// *** End of 'btnwrap' div
actionBuffer.append( "</td>" );
// If you are either in combat or finished with one, give the
// user the opportunity to update hot keys.
if ( useHotKeys && !inChoice )
{
actionBuffer.append( "<td align=right valign=top>" );
actionBuffer.append( "<select id=\"hotkeyViewer\" onchange=\"updateCombatHotkey();\">" );
actionBuffer.append( "<option>- update hotkeys -</option>" );
for ( int i = 0; i < StationaryButtonDecorator.combatHotkeys.size(); ++i )
{
actionBuffer.append( "<option>" );
actionBuffer.append( i );
actionBuffer.append( ": " );
actionBuffer.append( StationaryButtonDecorator.combatHotkeys.get( i ) );
actionBuffer.append( "</option>" );
}
actionBuffer.append( "</select>" );
actionBuffer.append( "</td>" );
}
actionBuffer.append( "</tr></table></center>" );
actionBuffer.append( "</div>" );
// *** End of 'mafiabuttons' div
// *** Start of 'content_' div
actionBuffer.append( "<div class='content' id='content_'>" );
actionBuffer.append( "<div id='effdiv' style='display: none;'></div>" );
// *** Start of 'extra' div
actionBuffer.append( "<div>" );
buffer.insert( insertionPoint, actionBuffer.toString() );
StringUtilities.insertBefore( buffer, "</body>", "</div>" );
// *** End of 'extra' div
StringUtilities.insertBefore( buffer, "</body>", "</div>" );
// *** End of 'content_' div
StringUtilities.insertBefore( buffer, "</body>", "</div>" );
// *** End of 'page' div
if ( useHotKeys )
{
StringUtilities.insertBefore( buffer, "</head>", "<script src=\"/" + KoLConstants.HOTKEYS_JS + "\"></script>" );
StringUtilities.insertAfter( buffer, "<body", " onkeyup=\"handleCombatHotkey(event,false);\" onkeydown=\"handleCombatHotkey(event,true);\" " );
}
}
public static final void addCombatButtons( final String urlString, final StringBuffer actionBuffer )
{
// If we fighting a source agent, create buttons for exactly
// those skills which are usable against them.
if ( KoLCharacter.inTheSource() && FightRequest.isSourceAgent() )
{
StationaryButtonDecorator.addScriptButton( urlString, actionBuffer, true );
for ( UseSkillRequest skill : KoLConstants.availableCombatSkills )
{
int skillId = skill.getSkillId();
if ( SkillDatabase.sourceAgentSkill( skillId ) )
{
StationaryButtonDecorator.addFightButton( actionBuffer, String.valueOf( skillId ), true );
}
}
return;
}
// If you are Batfellow, you cannot "attack". Instead, you have
// skills based on items you might have.
if ( KoLCharacter.getLimitmode() == Limitmode.BATMAN )
{
StationaryButtonDecorator.addScriptButton( urlString, actionBuffer, true );
StationaryButtonDecorator.addBatButton( actionBuffer, "Bat-Punch", 0 );
StationaryButtonDecorator.addBatButton( actionBuffer, "Bat-Kick", 0 );
StationaryButtonDecorator.addBatButton( actionBuffer, "Bat-oomerang", ItemPool.BAT_OOMERANG );
StationaryButtonDecorator.addBatButton( actionBuffer, "Bat-Jute", ItemPool.BAT_JUTE );
StationaryButtonDecorator.addBatButton( actionBuffer, "Bat-o-mite", ItemPool.BAT_O_MITE );
StationaryButtonDecorator.addBatButton( actionBuffer, "Ultracoagulator", ItemPool.ULTRACOAGULATOR );
StationaryButtonDecorator.addBatButton( actionBuffer, "Kickball", ItemPool.EXPLODING_KICKBALL );
StationaryButtonDecorator.addBatButton( actionBuffer, "Bat-Glue", ItemPool.GLOB_OF_BAT_GLUE );
StationaryButtonDecorator.addBatButton( actionBuffer, "Bat-Bearing", ItemPool.BAT_BEARING );
StationaryButtonDecorator.addBatButton( actionBuffer, "Use Bat-Aid", ItemPool.BAT_AID_BANDAGE );
return;
}
if ( Preferences.getBoolean( "relayScriptButtonFirst" ) )
{
StationaryButtonDecorator.addScriptButton( urlString, actionBuffer, true );
StationaryButtonDecorator.addFightButton( actionBuffer, "attack", FightRequest.getCurrentRound() > 0 );
}
else
{
StationaryButtonDecorator.addFightButton( actionBuffer, "attack", FightRequest.getCurrentRound() > 0 );
StationaryButtonDecorator.addScriptButton( urlString, actionBuffer, false );
}
boolean inBirdForm = KoLConstants.activeEffects.contains( FightRequest.BIRDFORM );
if ( KoLCharacter.isSneakyPete() )
{
// If you are Sneaky Pete and can steal, you can also mug
StationaryButtonDecorator.addFightButton( actionBuffer, "7201", FightRequest.canStillSteal() );
}
if ( KoLCharacter.canPickpocket() )
{
StationaryButtonDecorator.addFightButton( actionBuffer, "steal", FightRequest.canStillSteal() );
}
if ( EquipmentManager.usingChefstaff() )
{
boolean enabled = FightRequest.getCurrentRound() > 0;
StationaryButtonDecorator.addFightButton( actionBuffer, "jiggle", enabled );
}
if ( EquipmentManager.holsteredSixgun() )
{
StationaryButtonDecorator.addFightButton( actionBuffer, "shoot", !FightRequest.shotSixgun() );
}
if ( FightRequest.canHandleCan() )
{
StationaryButtonDecorator.addFightButton( actionBuffer, "shake", !FightRequest.handledCan() );
}
String classStun = KoLCharacter.getClassStun();
// Some skills can be available in combat but aren't always stuns. Disable if so or change to Shadow Noodles if appropriate.
if ( classStun.equals( "Shell Up" ) && KoLCharacter.getBlessingType() != KoLCharacter.STORM_BLESSING )
{
classStun = Preferences.getBoolean( "considerShadowNoodles" ) ? "Shadow Noodles" : "none";
}
int classStunId = SkillDatabase.getSkillId( classStun );
if ( !inBirdForm && KoLCharacter.hasSkill( classStun ) )
{
UseSkillRequest stunRequest = UseSkillRequest.getUnmodifiedInstance( classStun );
boolean enabled = FightRequest.getCurrentRound() > 0 &&
KoLConstants.availableCombatSkills.contains( stunRequest );
// Only enable Club Foot when character has Fury, as it's only a stun then.
enabled &= !( classStun.equals( "Club Foot" ) && KoLCharacter.getFury() == 0 );
// Only enable Soul Bubble when character has 5 Soulsauce, as it's only a stun then.
enabled &= !( classStun.equals( "Soul Bubble" ) && KoLCharacter.getSoulsauce() < 5 );
// In Class Act 2, Stuns don't work at +76 and higher monster level
enabled &= !( KoLCharacter.inClasscore2() && KoLCharacter.getMonsterLevelAdjustment() > 75 );
StationaryButtonDecorator.addFightButton(actionBuffer, String.valueOf( classStunId ), enabled );
}
if ( !inBirdForm && KoLCharacter.hasSkill( "Transcendent Olfaction" ) )
{
boolean enabled = FightRequest.getCurrentRound() > 0 &&
FightRequest.canOlfact();
StationaryButtonDecorator.addFightButton( actionBuffer, "19", enabled );
}
if ( !inBirdForm && FightRequest.canPirateInsult() )
{
boolean enabled = FightRequest.getCurrentRound() > 0;
StationaryButtonDecorator.addFightButton( actionBuffer, "insult", enabled );
}
if ( !inBirdForm && FightRequest.canJamFlyer() )
{
boolean enabled = FightRequest.getCurrentRound() > 0;
StationaryButtonDecorator.addFightButton( actionBuffer, "jam flyer", enabled );
}
if ( !inBirdForm && FightRequest.canRockFlyer() )
{
boolean enabled = FightRequest.getCurrentRound() > 0;
StationaryButtonDecorator.addFightButton( actionBuffer, "rock flyer", enabled );
}
int buttons = Preferences.getInteger( "relaySkillButtonCount" );
for ( int i = 1; i <= buttons; ++i )
{
String action = Preferences.getString( "stationaryButton" + i );
if ( action.equals( "" ) || action.equals( "none" ) )
{
continue;
}
// We use Skill IDs for button actions, but users can screw them up.
if ( !StringUtilities.isNumeric( action ) )
{
action = String.valueOf( SkillDatabase.getSkillId( action ) );
}
String name = SkillDatabase.getSkillName( Integer.parseInt( action ) );
boolean hasSkill = name != null && KoLCharacter.hasSkill( name );
boolean remove = false;
// If it's a completely bogus skill id, flush it
if ( name == null )
{
remove = true;
}
// If we are in bird form, we can only use birdform skills.
else if ( inBirdForm )
{
// Birdform skills do not appear on our list of
// known skills. Display only unknown skills
// but keep known skills in the preferences
if ( hasSkill )
{
continue;
}
}
// Otherwise, remove unknown skills from preferences
else if ( !hasSkill )
{
remove = true;
}
// We tie this skill to the "shake" button
if ( remove )
{
for ( int j = i; j < buttons; ++j )
{
Preferences.setString(
"stationaryButton" + j, Preferences.getString( "stationaryButton" + ( j + 1 ) ) );
}
Preferences.setString( "stationaryButton" + buttons, "" );
--i; // retry with the skill that's now in this position
continue;
}
// Show this skill.
StationaryButtonDecorator.addFightButton( actionBuffer, action, FightRequest.getCurrentRound() > 0 );
}
// Add conditionally available combat skills
// parsed from the fight page
for ( int i = 0; i < KoLConstants.availableCombatSkills.size(); ++i )
{
UseSkillRequest current = (UseSkillRequest) KoLConstants.availableCombatSkills.get( i );
int actionId = current.getSkillId();
if ( actionId >= 7000 && actionId < 8000 && actionId != classStunId && actionId != 7201 )
{
String action = String.valueOf( actionId );
StationaryButtonDecorator.addFightButton( actionBuffer, action, FightRequest.getCurrentRound() > 0 );
}
}
if ( StationaryButtonDecorator.combatHotkeys.isEmpty() )
{
StationaryButtonDecorator.reloadCombatHotkeyMap();
}
}
public static final void addBatButton( final StringBuffer actionBuffer, String skillName, int itemId )
{
if ( itemId != 0 && InventoryManager.getCount( itemId ) == 0 )
{
return;
}
int skillId = SkillDatabase.getSkillId( skillName );
if ( skillId != -1 )
{
StationaryButtonDecorator.addFightButton( actionBuffer, String.valueOf( skillId ), true );
}
}
public static final void addChoiceButtons( final StringBuffer buffer )
{
int choice = ChoiceManager.currentChoice();
// Certain choices require extra parameters
switch ( choice )
{
case 0:
return;
case 999:
// Shrubberatin'
return;
}
boolean goal = ChoiceManager.hasGoalButton( choice );
String name = goal ? "Go To Goal" : "auto";
String action = "choice.php?action=auto";
StationaryButtonDecorator.addButton( buffer, name, action, true, false );
StringBuilder actionBuffer = new StringBuilder();
TreeMap<Integer,String> choices = ChoiceUtilities.parseChoices( ChoiceManager.lastResponseText );
for ( Map.Entry<Integer,String> entry : choices.entrySet() )
{
actionBuffer.setLength( 0 );
actionBuffer.append( "choice.php?whichchoice=" );
actionBuffer.append( choice );
actionBuffer.append( "&option=" );
actionBuffer.append( entry.getKey().intValue() );
actionBuffer.append( "&pwd=" );
actionBuffer.append( GenericRequest.passwordHash );
StationaryButtonDecorator.addButton( buffer, entry.getValue(), actionBuffer.toString(), true, false );
}
}
public static final void addNonCombatButtons( final StringBuffer response, final StringBuffer buffer )
{
String name = "again";
String action = getAdventureAgainLocation( response );
boolean isEnabled = !action.equals( "main.php" );
boolean forceFocus = true;
StationaryButtonDecorator.addButton( buffer, name, action, isEnabled, forceFocus );
}
private static final void addScriptButton( final String urlString, final StringBuffer buffer, final boolean forceFocus )
{
String name;
String action;
boolean isEnabled = true;
if ( urlString.endsWith( "action=script" ) )
{
name = "abort";
action = "fight.php?action=abort";
}
else
{
name = "script";
action = "fight.php?action=custom";
}
StationaryButtonDecorator.addButton( buffer, name, action, isEnabled, forceFocus );
}
private static final void addFightButton( final StringBuffer buffer, final String action, boolean isEnabled )
{
boolean forceFocus = action.equals( "attack" );
String name = StationaryButtonDecorator.getActionName( action );
StringBuilder actionBuffer = new StringBuilder( "fight.php?action=" );
if ( action.equals( "attack" ) || action.equals( "steal" ) )
{
actionBuffer.append( action );
}
else if ( action.equals( "jiggle" ) )
{
actionBuffer.append( "chefstaff" );
isEnabled &= !FightRequest.alreadyJiggled();
}
else if ( action.equals( "shake" ) )
{
actionBuffer.append( "skill&whichskill=" );
actionBuffer.append( String.valueOf( SkillPool.CANHANDLE ) );
}
else if ( action.equals( "shoot" ) )
{
actionBuffer.append( "skill&whichskill=" );
actionBuffer.append( String.valueOf( SkillPool.SHOOT ) );
}
else if ( action.equals( "insult" ) )
{
int itemId =
KoLCharacter.inBeecore() ?
ItemPool.MARAUDER_MOCKERY_MANUAL :
ItemPool.PIRATE_INSULT_BOOK;
if ( KoLConstants.inventory.contains( ItemPool.get( itemId, 1 ) ) )
{
actionBuffer.append( "useitem&whichitem=" );
actionBuffer.append( String.valueOf( itemId ) );
}
else
{
isEnabled = false;
}
}
else if ( action.equals( "jam flyer" ) )
{
actionBuffer.append( "useitem&whichitem=2404" );
}
else if ( action.equals( "rock flyer" ) )
{
actionBuffer.append( "useitem&whichitem=2405" );
}
else
{
actionBuffer.append( "skill&whichskill=" );
actionBuffer.append( action );
int skillID = StringUtilities.parseInt( action );
UseSkillRequest actionRequest = UseSkillRequest.getUnmodifiedInstance( skillID );
isEnabled &= KoLConstants.availableCombatSkills.contains( actionRequest );
// Disable Lash if already used this Ed fight
if ( skillID == SkillPool.LASH_OF_COBRA && Preferences.getBoolean( "edUsedLash" ) )
{
isEnabled = false;
}
}
StationaryButtonDecorator.addButton( buffer, name, actionBuffer.toString(), isEnabled, forceFocus );
}
private static final void addButton( final StringBuffer buffer, final String name, final String action,
final boolean isEnabled, final boolean forceFocus )
{
buffer.append( "<input type=\"button\" onClick=\"document.location.href='" );
buffer.append( action );
buffer.append( "';void(0);\" value=\"" );
buffer.append( name );
buffer.append( "\"" );
if ( forceFocus )
{
buffer.append( " id=\"defaultButton\"" );
}
if ( !isEnabled )
{
buffer.append( " disabled" );
}
buffer.append( "> " );
}
private static final Pattern BODY_PATTERN = Pattern.compile( "<body>.*</body>", Pattern.DOTALL );
private static final Pattern LOCATION_PATTERN = Pattern.compile( "<[aA] (id=\"againlink\" )?href=[\"']?([^\"'>]*)", Pattern.DOTALL );
public static final String getAdventureAgainLocation( StringBuffer response )
{
// Get the "adventure again" link from the page.
// Search only in the body of the page
Matcher m = BODY_PATTERN.matcher( response );
if ( !m.find() )
{
// This will not happen
return "main.php";
}
boolean againLinkExists = response.indexOf( "id=againlink" ) != -1;
m = LOCATION_PATTERN.matcher( m.group(0) );
while ( m.find() )
{
// Skip Monster Manuel's link to a new factoid
// questlog.php?which=6&vl=p#mon1429
String again = m.group( 1 );
String link = m.group( 2 );
// If KoL says that this is the "adventure again" link, believe it
if ( again != null )
{
return link;
}
if ( againLinkExists && again == null )
{
continue;
}
if ( !link.contains( "questlog.php" ) &&
!link.contains( "desc_item.php" ) &&
!link.contains( "showplayer.php" ) )
{
return link;
}
}
// If there is none, perhaps we fought a monster as a result of
// using an item.
MonsterData monster = MonsterStatusTracker.getLastMonster();
if ( monster != null )
{
String monsterName = monster.getName();
if ( monsterName.equals( "giant sandworm" ) )
{
AdventureResult drumMachine = ItemPool.get( ItemPool.DRUM_MACHINE, 1 );
if ( KoLConstants.inventory.contains( drumMachine ) )
{
return "inv_use.php?pwd=" + GenericRequest.passwordHash + "&which=3&whichitem=" + ItemPool.DRUM_MACHINE;
}
// Look for more drum machines in the Oasis
return "adventure.php?snarfblat=122";
}
if ( monsterName.equals( "scary pirate" ) )
{
return "inv_use.php?pwd=" + GenericRequest.passwordHash +"&which=3&whichitem=" + ItemPool.CURSED_PIECE_OF_THIRTEEN;
}
}
return "main.php";
}
private static final String getActionName( final String action )
{
if ( action.equals( "attack" ) )
{
return FightRequest.getCurrentRound() == 0 ? "again" : "attack";
}
if ( action.equals( "insult" ) )
{
return "pirate insult";
}
if ( action.equals( "steal" ) || action.equals( "jiggle" ) ||
action.equals( "shake" ) || action.equals( "shoot" ) ||
action.equals( "script" ) ||
action.equals( "jam flyer" ) || action.equals( "rock flyer" ) )
{
return action;
}
int skillId = StringUtilities.parseInt( action );
String name = SkillDatabase.getSkillName( skillId ).toLowerCase();
switch ( skillId )
{
case 15: // CLEESH
case 7002: // Shake Hands
case 7003: // Hot Breath
case 7004: // Cold Breath
case 7005: // Spooky Breath
case 7006: // Stinky Breath
case 7007: // Sleazy Breath
name = StringUtilities.globalStringDelete( name, " " );
break;
case 7001: // Give In To Your Vampiric Urges
name = "bakula";
break;
case 7010: // red bottle-rocket
case 7011: // blue bottle-rocket
case 7012: // orange bottle-rocket
case 7013: // purple bottle-rocket
case 7014: // black bottle-rocket
name = StringUtilities.globalStringDelete( StringUtilities.globalStringDelete( name, "fire " ), "bottle-" );
break;
case 1003: // thrust-smack
name = "thrust";
break;
case 1004: // lunge-smack
name = "lunge";
break;
case 1005: // lunging thrust-smack
name = "lunging";
break;
case 2: // Chronic Indigestion
case 7009: // Magic Missile
case 3004: // Entangling Noodles
case 3009: // Lasagna Bandages
case 3019: // Fearful Fettucini
case 19: // Transcendent Olfaction
case 7063: // Falling Leaf Whirlwind
name = name.substring( name.lastIndexOf( " " ) + 1 );
break;
case 50: // Break It On Down
case 51: // Pop and Lock It
case 52: // Run Like the WInd
case 3003: // Minor Ray of Something
case 3005: // eXtreme Ray of Something
case 3007: // Cone of Whatever
case 3008: // Weapon of the Pastalord
case 3020: // Spaghetti Spear
case 4003: // Stream of Sauce
case 4009: // Wave of Sauce
case 5019: // Tango of Terror
case 7061: // Spring Raindrop Attack
case 7062: // Summer Siesta
case 7064: // Winter's Bite Technique
case 7201: // Mug for the Audience
name = name.substring( 0, name.indexOf( " " ) );
break;
case 5003: // Disco Eye-Poke
name = "eyepoke";
break;
case 5005: // Disco Dance of Doom
name = "dance1";
break;
case 5008: // Disco Dance II: Electric Boogaloo
name = "dance2";
break;
case 5036: // Disco Dance 3: Back in the Habit
name = "dance3";
break;
case 5012: // Disco Face Stab
name = "facestab";
break;
}
return name;
}
public static final void reloadCombatHotkeyMap()
{
StationaryButtonDecorator.combatHotkeys.clear();
for ( int i = 0; i <= 9; ++i )
{
StationaryButtonDecorator.combatHotkeys.add( Preferences.getString( "combatHotkey" + i ) );
}
}
}