/**
* Copyright (c) 2005-2017, KoLmafia development team
* http://kolmafia.sourceforge.net/
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* [1] Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* [2] Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* [3] Neither the name "KoLmafia" nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package net.sourceforge.kolmafia;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Frame;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JRadioButton;
import javax.swing.text.Element;
import javax.swing.text.StyleConstants;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.FormView;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.ImageView;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.MonsterData;
import net.sourceforge.kolmafia.chat.ChatPoller;
import net.sourceforge.kolmafia.combat.MonsterStatusTracker;
import net.sourceforge.kolmafia.objectpool.AdventurePool;
import net.sourceforge.kolmafia.objectpool.FamiliarPool;
import net.sourceforge.kolmafia.objectpool.ItemPool;
import net.sourceforge.kolmafia.persistence.AdventureDatabase;
import net.sourceforge.kolmafia.persistence.BountyDatabase;
import net.sourceforge.kolmafia.persistence.ItemDatabase;
import net.sourceforge.kolmafia.persistence.MonsterDatabase;
import net.sourceforge.kolmafia.persistence.QuestDatabase;
import net.sourceforge.kolmafia.persistence.QuestDatabase.Quest;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.request.BeerPongRequest;
import net.sourceforge.kolmafia.request.DwarfFactoryRequest;
import net.sourceforge.kolmafia.request.FightRequest;
import net.sourceforge.kolmafia.request.FloristRequest;
import net.sourceforge.kolmafia.request.GenericRequest;
import net.sourceforge.kolmafia.request.MallSearchRequest;
import net.sourceforge.kolmafia.request.PandamoniumRequest;
import net.sourceforge.kolmafia.request.PlaceRequest;
import net.sourceforge.kolmafia.request.RelayRequest;
import net.sourceforge.kolmafia.request.SpaaaceRequest;
import net.sourceforge.kolmafia.request.SpelunkyRequest;
import net.sourceforge.kolmafia.request.SuburbanDisRequest;
import net.sourceforge.kolmafia.request.ZapRequest;
import net.sourceforge.kolmafia.session.ChoiceManager;
import net.sourceforge.kolmafia.session.DvorakManager;
import net.sourceforge.kolmafia.session.EquipmentManager;
import net.sourceforge.kolmafia.session.EventManager;
import net.sourceforge.kolmafia.session.InventoryManager;
import net.sourceforge.kolmafia.session.IslandManager;
import net.sourceforge.kolmafia.session.Limitmode;
import net.sourceforge.kolmafia.session.NemesisManager;
import net.sourceforge.kolmafia.session.OceanManager;
import net.sourceforge.kolmafia.session.RabbitHoleManager;
import net.sourceforge.kolmafia.session.ResultProcessor;
import net.sourceforge.kolmafia.session.TavernManager;
import net.sourceforge.kolmafia.session.VolcanoMazeManager;
import net.sourceforge.kolmafia.swingui.RequestFrame;
import net.sourceforge.kolmafia.swingui.widget.RequestPane;
import net.sourceforge.kolmafia.utilities.FileUtilities;
import net.sourceforge.kolmafia.utilities.StringUtilities;
import net.sourceforge.kolmafia.webui.BarrelDecorator;
import net.sourceforge.kolmafia.webui.BasementDecorator;
import net.sourceforge.kolmafia.webui.BeerPongDecorator;
import net.sourceforge.kolmafia.webui.CharPaneDecorator;
import net.sourceforge.kolmafia.webui.DiscoCombatHelper;
import net.sourceforge.kolmafia.webui.FightDecorator;
import net.sourceforge.kolmafia.webui.HobopolisDecorator;
import net.sourceforge.kolmafia.webui.IslandDecorator;
import net.sourceforge.kolmafia.webui.MemoriesDecorator;
import net.sourceforge.kolmafia.webui.MineDecorator;
import net.sourceforge.kolmafia.webui.MoneyMakingGameDecorator;
import net.sourceforge.kolmafia.webui.NemesisDecorator;
import net.sourceforge.kolmafia.webui.StationaryButtonDecorator;
import net.sourceforge.kolmafia.webui.TopMenuDecorator;
import net.sourceforge.kolmafia.webui.UseItemDecorator;
import net.sourceforge.kolmafia.webui.UseLinkDecorator;
import net.sourceforge.kolmafia.webui.UseLinkDecorator.UseLink;
import net.sourceforge.kolmafia.webui.ValhallaDecorator;
public class RequestEditorKit
extends HTMLEditorKit
{
private static final Pattern FORM_PATTERN = Pattern.compile( "name=choiceform(\\d+)" );
//private static final Pattern CHOICE_PATTERN = Pattern.compile( "whichchoice\"? value=\"?(\\d+)\"?" );
private static final Pattern CHOICE2_PATTERN = Pattern.compile( "whichchoice=(\\d+)" );
private static final Pattern OPTION_PATTERN = Pattern.compile( "name=option value=(\\d+)" );
private static final Pattern OUTFIT_FORM_PATTERN = Pattern.compile( "<form name=outfit.*?</form>", Pattern.DOTALL );
private static final Pattern OPTGROUP_PATTERN = Pattern.compile( "<optgroup label=['\"]([^']*)['\"]>(.*?)</optgroup>", Pattern.DOTALL );
private static final Pattern NOLABEL_CUSTOM_OUTFITS_PATTERN = Pattern.compile( "\\(select an outfit\\)</option>(<option.*?)<optgroup", Pattern.DOTALL );
private static final Pattern ROUND_SEP_PATTERN = Pattern.compile( "<(?:b>Combat!</b>|hr.*?>)" );
private static final Pattern RCM_JS_PATTERN = Pattern.compile( "rcm\\.(\\d+\\.)?js" );
private static final RequestViewFactory DEFAULT_FACTORY = new RequestViewFactory();
/**
* Returns an extension of the standard <code>HTMLFacotry</code> which intercepts some of the form handling to
* ensure that <code>GenericRequest</code> objects are instantiated on form submission rather than the
* <code>HttpRequest</code> objects created by the default HTML editor kit.
*/
@Override
public ViewFactory getViewFactory()
{
return RequestEditorKit.DEFAULT_FACTORY;
}
/**
* Registers thethat is supposed to be used for handling data submission to the Kingdom of Loathing server.
*/
private static class RequestViewFactory
extends HTMLFactory
{
@Override
public View create( final Element elem )
{
if ( elem.getAttributes().getAttribute( StyleConstants.NameAttribute ) == HTML.Tag.INPUT )
{
return new KoLSubmitView( elem );
}
if ( elem.getAttributes().getAttribute( StyleConstants.NameAttribute ) == HTML.Tag.IMG )
{
return new KoLImageView( elem );
}
return super.create( elem );
}
}
private static class KoLImageView
extends ImageView
{
public KoLImageView( final Element elem )
{
super( elem );
}
@Override
public URL getImageURL()
{
String src = (String) this.getElement().getAttributes().getAttribute( HTML.Attribute.SRC );
if ( src == null )
{
return null;
}
File imageFile = FileUtilities.downloadImage( src );
try
{
return imageFile.toURI().toURL();
}
catch ( IOException e )
{
return null;
}
}
}
public static final String getFeatureRichHTML( final String location, final String text )
{
return RequestEditorKit.getFeatureRichHTML( location, text, true );
}
public static final void getFeatureRichHTML( final String location, final StringBuffer buffer )
{
RequestEditorKit.getFeatureRichHTML( location, buffer, true );
}
// Stupid bureacrats, always ruining everybody's fun with their permits
// and forms.
private static final String NO_PERMIT_TEXT =
"always ruining everybody's fun with their permits and forms.";
private static final String BUY_PERMIT_TEXT =
RequestEditorKit.NO_PERMIT_TEXT + " [<a href=\"hermit.php?autopermit=on\">buy a hermit permit</a>]";
// He looks at you with a disappointed sigh -- looks like you don't have
// anything worthless enough for him to want to trade for it.
private static final String NO_WORTHLESS_ITEM_TEXT =
"worthless enough for him to want to trade for it.";
private static final String BUY_WORTHLESS_ITEM_TEXT =
RequestEditorKit.NO_WORTHLESS_ITEM_TEXT + " [<a href=\"hermit.php?autoworthless=on\">fish for a worthless item</a>]";
private static final ArrayList<String> maps = new ArrayList<String>();
static
{
RequestEditorKit.maps.add( "place.php?whichplace=plains" );
RequestEditorKit.maps.add( "place.php?whichplace=bathole" );
RequestEditorKit.maps.add( "fernruin.php" );
RequestEditorKit.maps.add( "cobbsknob.php" );
RequestEditorKit.maps.add( "cobbsknob.php?action=tolabs" );
RequestEditorKit.maps.add( "cobbsknob.php?action=tomenagerie" );
RequestEditorKit.maps.add( "cyrpt.php" );
RequestEditorKit.maps.add( "place.php?whichplace=beanstalk" );
RequestEditorKit.maps.add( "woods.php" );
RequestEditorKit.maps.add( "friars.php" );
RequestEditorKit.maps.add( "pandamonium.php" );
RequestEditorKit.maps.add( "place.php?whichplace=mountains" );
RequestEditorKit.maps.add( "tutorial.php" );
RequestEditorKit.maps.add( "place.php?whichplace=mclargehuge" );
RequestEditorKit.maps.add( "island.php" );
RequestEditorKit.maps.add( "place.php?whichplace=cove" );
RequestEditorKit.maps.add( "bigisland.php" );
RequestEditorKit.maps.add( "postwarisland.php" );
RequestEditorKit.maps.add( "place.php?whichplace=desertbeach" );
RequestEditorKit.maps.add( "pyramid.php" );
RequestEditorKit.maps.add( "place.php?whichplace=town_wrong" );
RequestEditorKit.maps.add( "place.php?whichplace=town_right" );
RequestEditorKit.maps.add( "place.php?whichplace=spookyraven1" );
RequestEditorKit.maps.add( "place.php?whichplace=spookyraven2" );
RequestEditorKit.maps.add( "place.php?whichplace=wormwood" );
RequestEditorKit.maps.add( "manor3.php" );
RequestEditorKit.maps.add( "da.php" );
RequestEditorKit.maps.add( "canadia.php" );
RequestEditorKit.maps.add( "gnomes.php" );
RequestEditorKit.maps.add( "heydeze.php" );
RequestEditorKit.maps.add( "dwarffactory.php" );
}
public static final String getFeatureRichHTML( final String location, final String text, final boolean addComplexFeatures )
{
if ( text == null || text.length() == 0 )
{
return "";
}
StringBuffer buffer = new StringBuffer( text );
RequestEditorKit.getFeatureRichHTML( location, buffer, addComplexFeatures );
return buffer.toString();
}
public static final void getFeatureRichHTML( final String location, final StringBuffer buffer, final boolean addComplexFeatures )
{
if ( buffer.length() == 0 )
{
return;
}
// Skip all decorations on the raw KoL api.
if ( location.startsWith( "api.php" ) )
{
return;
}
// Remove bogus <body> tag preceding <head> tag. topmenu has
// this, but don't assume other pages are flawless
StringUtilities.singleStringReplace( buffer, "<body><head>", "<head>" );
// Apply individual page adjustments
RequestEditorKit.applyPageAdjustments( location, buffer, addComplexFeatures );
// Apply adjustments that should be on all pages
RequestEditorKit.applyGlobalAdjustments( location, buffer, addComplexFeatures );
}
protected static final void applyPageAdjustments( final String location, final StringBuffer buffer, final boolean addComplexFeatures )
{
// Check for charpane first, since it occurs frequently.
if ( location.startsWith( "charpane.php" ) )
{
if ( addComplexFeatures )
{
CharPaneDecorator.decorate( buffer );
}
return;
}
// Handle topmenu
if ( location.contains( "menu.php" ) )
{
TopMenuDecorator.decorate( buffer, location );
return;
}
// If clovers were auto-disassembled, show disassembled clovers
if ( ResultProcessor.disassembledClovers( location ) )
{
// Replace not only the bolded item name, but
// also alt and title tags of the image
StringUtilities.globalStringReplace( buffer, "ten-leaf clover", "disassembled clover" );
StringUtilities.singleStringReplace( buffer, "clover.gif", "disclover.gif" );
StringUtilities.singleStringReplace( buffer, "370834526", "328909735" );
}
// Override images, if requested
RelayRequest.overrideImages( buffer );
// Make changes which only apply to a single page.
if ( location.startsWith( "account.php" ) )
{
StringUtilities.singleStringReplace( buffer, "Manage Subscriptions", "Manage Subscriptions (this will not work in KoLmafia)" );
StringUtilities.singleStringReplace( buffer, "account_subscription.php\"", "#\" title='This will not work in KoLmafia'" );
}
else if ( location.startsWith( "account_combatmacros.php" ) )
{
StringUtilities.insertAfter( buffer, "</textarea>", "<script language=JavaScript src=\"/" + KoLConstants.MACROHELPER_JS + "\"></script>" );
}
else if ( location.startsWith( "adminmail.php" ) )
{
// Per KoL dev team request, add extra warning to the
// bug report form.
RequestEditorKit.addBugReportWarning( buffer );
}
else if ( location.startsWith( "adventure.php" ) )
{
RequestEditorKit.fixTavernCellar( buffer );
// RequestEditorKit.fixBallroom1( buffer );
RequestEditorKit.fixDucks( buffer );
StationaryButtonDecorator.decorate( location, buffer );
RequestEditorKit.fixBallroom2( buffer );
RequestEditorKit.fixGovernmentLab( buffer );
}
else if ( location.startsWith( "ascend.php" ) )
{
ValhallaDecorator.decorateGashJump( location, buffer );
}
else if ( location.startsWith( "ascensionhistory.php" ) )
{
// No Javascript in Java's HTML renderer
if ( addComplexFeatures )
{
StringUtilities.insertBefore(
buffer, "</head>", "<script language=\"Javascript\" src=\"/" + KoLConstants.SORTTABLE_JS + "\"></script>" );
StringUtilities.singleStringReplace(
buffer, "<table><tr><td class=small>",
"<table class=\"sortable\" id=\"history\"><tr><td class=small>" );
StringUtilities.globalStringReplace(
buffer, "<tr><td colspan=9", "<tr class=\"sortbottom\" style=\"display:none\"><td colspan=9" );
}
}
else if ( location.startsWith( "barrel.php" ) )
{
BarrelDecorator.decorate( buffer );
}
else if ( location.startsWith( "basement.php" ) )
{
BasementDecorator.decorate( buffer );
}
else if ( location.startsWith( "bathole.php" ) )
{
StringUtilities.globalStringReplace( buffer, "action=bathole.php", "action=adventure.php" );
}
else if ( location.startsWith( "beerpong.php" ) )
{
BeerPongDecorator.decorate( buffer );
}
else if ( location.startsWith( "bet.php" ) )
{
MoneyMakingGameDecorator.decorate( location, buffer );
}
else if ( location.startsWith( "bigisland.php" ) )
{
IslandDecorator.decorateBigIsland( location, buffer );
}
else if ( location.startsWith( "casino.php" ) )
{
if ( !InventoryManager.hasItem( ItemPool.TEN_LEAF_CLOVER ) )
{
StringUtilities.insertAfter( buffer, "<a href=\"casino.php?action=slot&whichslot=11\"", " onclick=\"return confirm('Are you sure you want to adventure here WITHOUT a ten-leaf clover?');\"" );
}
}
else if ( location.startsWith( "cave.php" ) )
{
NemesisManager.decorate( location, buffer );
}
else if ( location.startsWith( "choice.php" ) )
{
RequestEditorKit.fixTavernCellar( buffer );
StationaryButtonDecorator.decorate( location, buffer );
RequestEditorKit.addChoiceSpoilers( location, buffer );
RequestEditorKit.addBarrelSounds( buffer );
}
else if ( location.startsWith( "clan_hobopolis.php" ) )
{
HobopolisDecorator.decorate( location, buffer );
}
else if ( location.startsWith( "council.php" ) )
{
RequestEditorKit.decorateCouncil( buffer );
}
else if ( location.startsWith( "crypt.php" ) )
{
RequestEditorKit.decorateCrypt( buffer );
}
else if ( location.startsWith( "dwarffactory.php" ) )
{
DwarfFactoryRequest.decorate( location, buffer );
}
else if ( location.startsWith( "fight.php" ) )
{
// Remove bogus directive in monster images
StringUtilities.globalStringDelete( buffer, "crossorigin=\"Anonymous\"" );
RequestEditorKit.suppressInappropriateNags( buffer );
RequestEditorKit.fixTavernCellar( buffer );
// Decorate end of fight before stationary buttons
FightDecorator.decorateEndOfFight( buffer );
StationaryButtonDecorator.decorate( location, buffer );
DiscoCombatHelper.decorate( buffer );
RequestEditorKit.addFightModifiers( buffer );
RequestEditorKit.addTaleOfDread( buffer );
RequestEditorKit.addDesertProgress( buffer );
RequestEditorKit.addBlackForestProgress( buffer );
// Do any monster-specific decoration
FightDecorator.decorateMonster( buffer );
// Do any location-specific decoration
FightDecorator.decorateLocation( buffer );
}
else if ( location.startsWith( "hermit.php" ) )
{
StringUtilities.singleStringReplace( buffer, RequestEditorKit.NO_PERMIT_TEXT, RequestEditorKit.BUY_PERMIT_TEXT );
StringUtilities.singleStringReplace( buffer, RequestEditorKit.NO_WORTHLESS_ITEM_TEXT, RequestEditorKit.BUY_WORTHLESS_ITEM_TEXT );
}
else if ( location.startsWith( "inventory.php" ) )
{
RequestEditorKit.decorateInventory( buffer, addComplexFeatures );
UseItemDecorator.decorate( location, buffer );
}
else if ( location.startsWith( "inv_use.php" ) )
{
UseItemDecorator.decorate( location, buffer );
}
else if ( location.contains( "lchat.php" ) )
{
StringUtilities.globalStringDelete( buffer, "spacing: 0px;" );
StringUtilities.insertBefore(
buffer,
"if (postedgraf",
"if (postedgraf == \"/exit\") { document.location.href = \"chatlaunch.php\"; return true; } " );
}
else if ( location.startsWith( "mall.php" ) )
{
MallSearchRequest.decorateMallSearch( buffer );
}
else if ( location.startsWith( "mining.php" ) )
{
MineDecorator.decorate( location, buffer );
}
else if ( location.startsWith( "mrstore.php" ) )
{
StringUtilities.singleStringReplace( buffer, "account_subscription.php", "# title='This will not work in KoLmafia'" );
StringUtilities.singleStringReplace( buffer, "subscribing</a>", "subscribing (does not work in KoLmafia)</a>" );
}
else if ( location.startsWith( "multiuse.php" ) )
{
RequestEditorKit.addMultiuseModifiers( buffer );
}
else if ( location.startsWith( "ocean.php" ) )
{
OceanManager.decorate( buffer );
}
else if ( location.startsWith( "pandamonium.php" ) )
{
PandamoniumRequest.decoratePandamonium( location, buffer );
}
else if ( location.startsWith( "place.php?whichplace=arcade" ) )
{
StringBuilder note = new StringBuilder( "Arcade (" );
int count = InventoryManager.getCount( ItemPool.GG_TOKEN );
note.append( count );
note.append( " token" );
if ( count != 1 )
{
note.append( 's' );
}
note.append( ", " );
count = InventoryManager.getCount( ItemPool.GG_TICKET );
note.append( count );
note.append( " ticket" );
if ( count != 1 )
{
note.append( 's' );
}
note.append( ")</b>" );
StringUtilities.singleStringReplace( buffer,
"Arcade</b>", note.toString() );
}
else if ( location.startsWith( "place.php" ) )
{
PlaceRequest.decorate( location, buffer );
}
else if ( location.startsWith( "postwarisland.php" ) )
{
IslandDecorator.decoratePostwarIsland( location, buffer );
}
else if ( location.startsWith( "searchplayer.php" ) )
{
StringUtilities.insertAfter( buffer, "name=pvponly", " checked" );
StringUtilities.singleStringReplace( buffer, "value=0 checked", "value=0" );
if ( KoLCharacter.isHardcore() )
{
StringUtilities.insertAfter( buffer, "value=1", " checked" );
}
else
{
StringUtilities.insertAfter( buffer, "value=2", " checked" );
}
}
else if ( location.startsWith( "tiles.php" ) )
{
DvorakManager.decorate( buffer );
}
else if ( location.startsWith( "volcanomaze.php" ) )
{
VolcanoMazeManager.decorate( location, buffer );
}
else if ( location.startsWith( "wand.php" ) && !location.contains( "notrim=1" ) )
{
ZapRequest.decorate( buffer );
}
}
protected static final void applyGlobalAdjustments( final String location, final StringBuffer buffer, final boolean addComplexFeatures )
{
// Make basics.js and basics.css available to all pages
if ( addComplexFeatures )
{
StringUtilities.insertBefore(
buffer, "</head>", "<script language=\"Javascript\" src=\"/" + KoLConstants.BASICS_JS + "\"></script>" );
StringUtilities.insertBefore(
buffer, "</head>", "<link rel=\"stylesheet\" href=\"/" + KoLConstants.BASICS_CSS + "\" />" );
}
// Skip additional decorations for the character pane and the top menu
if ( location.startsWith( "charpane.php" ) || location.contains( "menu.php" ) )
{
return;
}
// Handle changes which happen on a lot of different pages
// rather than just one or two.
RequestEditorKit.changePunchcardNames( buffer );
RequestEditorKit.changePotionImages( buffer );
RequestEditorKit.decorateLevelGain( buffer );
RequestEditorKit.addAbsintheLink( buffer );
RequestEditorKit.addTransponderLink( buffer );
RequestEditorKit.addBatteryLink( buffer );
RequestEditorKit.addFolioLink( buffer );
RequestEditorKit.addNewLocationLinks( buffer );
RequestEditorKit.suppressPotentialMalware( buffer );
RequestEditorKit.extendRightClickMenu( buffer );
// Now do anything which doesn't work in Java's internal HTML renderer
if ( addComplexFeatures )
{
if ( RequestEditorKit.maps.contains( location ) )
{
buffer.insert(
buffer.indexOf( "</tr>" ),
"<td width=15 valign=bottom align=left bgcolor=blue><a style=\"color: white; font-weight: normal; font-size: small; text-decoration: underline\" href=\"javascript: attachSafetyText(); void(0);\">?</a>" );
buffer.insert( buffer.indexOf( "<td", buffer.indexOf( "</tr>" ) ) + 3, " colspan=2" );
}
if ( Preferences.getBoolean( "relayAddsUseLinks" ) )
{
UseLinkDecorator.decorate( location, buffer );
}
if ( buffer.indexOf( "showplayer.php" ) != -1 &&
!RCM_JS_PATTERN.matcher( buffer ).find() )
{
RequestEditorKit.addChatFeatures( buffer );
}
// Always select the contents of text fields when you
// click on them to make for easy editing.
if ( Preferences.getBoolean( "autoHighlightOnFocus" ) && buffer.indexOf( "</html>" ) != -1 )
{
StringUtilities.insertBefore( buffer, "</html>", "<script src=\"/" + KoLConstants.ONFOCUS_JS + "\"></script>" );
}
if ( location.contains( "fight.php" ) )
{
StringUtilities.insertBefore( buffer, "</html>", "<script src=\"/" + KoLConstants.COMBATFILTER_JS + "\"></script>" );
}
}
Matcher eventMatcher = EventManager.eventMatcher( buffer.toString() );
if ( EventManager.hasEvents() && ( eventMatcher != null || location.equals( "main.php" ) ) )
{
int eventTableInsertIndex = 0;
if ( eventMatcher != null )
{
eventTableInsertIndex = eventMatcher.start();
buffer.setLength( 0 );
buffer.append( eventMatcher.replaceFirst( "" ) );
}
else
{
eventTableInsertIndex = buffer.indexOf( "</div>" ) + 6;
}
StringBuilder eventsTable = new StringBuilder();
eventsTable.append( "<center><table width=95% cellspacing=0 cellpadding=0>" );
eventsTable.append( "<tr><td style=\"color: white;\" align=center bgcolor=orange>" );
eventsTable.append( "<b>New Events:</b>" );
eventsTable.append( "</td></tr>" );
eventsTable.append( "<tr><td style=\"padding: 5px; border: 1px solid orange;\" align=center>" );
Iterator eventHyperTextIterator = EventManager.getEventHyperTexts().iterator();
while ( eventHyperTextIterator.hasNext() )
{
eventsTable.append( eventHyperTextIterator.next() );
if ( eventHyperTextIterator.hasNext() )
{
eventsTable.append( "<br />" );
}
}
eventsTable.append( "</td></tr>" );
eventsTable.append( "<tr><td height=4></td></tr>" );
eventsTable.append( "</table></center>" );
buffer.insert( eventTableInsertIndex, eventsTable.toString() );
EventManager.clearEventHistory();
}
// Having done all the decoration on the page, do things that
// might modify or depend on those decorations
// Change border colors if the user wants something other than blue
String defaultColor = Preferences.getString( "defaultBorderColor" );
if ( !defaultColor.equals( "blue" ) )
{
StringUtilities.globalStringReplace( buffer, "bgcolor=blue", "bgcolor=\"" + defaultColor + "\"" );
StringUtilities.globalStringReplace( buffer, "border: 1px solid blue", "border: 1px solid " + defaultColor );
}
}
private static final void extendRightClickMenu(StringBuffer buffer) {
if ( buffer.indexOf( "pop_ircm_contents" ) != -1 )
{
StringUtilities.insertBefore( buffer, "</html>", "<script src=\"/" + KoLConstants.IRCM_JS + "\"></script>" );
}
}
private static final String TOPMENU_REFRESH = "<script>top.menupane.location.href=\"topmenu.php\";</script>";
public static final void addTopmenuRefresh( final StringBuffer buffer )
{
int index = buffer.indexOf( "</body>" );
if ( index != -1 )
{
buffer.insert( index, RequestEditorKit.TOPMENU_REFRESH );
}
}
private static final void decorateLevelGain( final StringBuffer buffer )
{
String test = "<b>You gain a Level!</b>";
int index = buffer.indexOf( test );
if ( index == -1 )
{
String test2 = "<b>You gain some Levels!</b>";
int index2 = buffer.indexOf( test2 );
if ( index2 == -1 )
{
return;
}
index = index2;
test = test2;
}
StringBuilder links = new StringBuilder();
boolean haveLinks = false;
int newLevel = KoLCharacter.getLevel();
links.append( "<font size=1>" );
// If we are Level 13 or less, the Council might have quests for us
if ( newLevel <= 13 )
{
// If we're Ed, and have already found we're talking to Amun instead, link to Amun
if ( KoLCharacter.isEd() && QuestDatabase.isQuestLaterThan( Quest.LARVA, QuestDatabase.UNSTARTED ) )
{
links.append( " [<a href=\"council.php\">Amun</a>]" );
}
else
{
links.append( " [<a href=\"council.php\">council</a>]" );
}
haveLinks = true;
}
// If we are an Avatar of Boris, we can learn a new skill
if ( KoLCharacter.inAxecore() && newLevel <= 15 )
{
links.append( " [<a href=\"da.php?place=gate1\">boris</a>]" );
haveLinks = true;
}
else if ( KoLCharacter.isJarlsberg() && newLevel <= 15 )
{
links.append( " [<a href=\"da.php?place=gate2\">jarlsberg</a>]" );
haveLinks = true;
}
else if ( KoLCharacter.isSneakyPete() && newLevel <= 15 )
{
links.append( " [<a href=\"da.php?place=gate3\">sneaky pete</a>]" );
haveLinks = true;
}
else if ( KoLCharacter.isEd() && newLevel <= 15 )
{
if ( newLevel % 3 == 0 )
{
links.append( " [<a href=\"/place.php?whichplace=edbase&action=edbase_door\">servant</a>]" );
}
else
{
if ( KoLCharacter.hasSkill( "Bounty of Renenutet" ) &&
KoLCharacter.hasSkill( "Wrath of Ra" ) &&
KoLCharacter.hasSkill( "Curse of Stench" ) )
{
links.append( " [<a href=\"/place.php?whichplace=edbase&action=edbase_door\">servant xp</a>]" );
}
else
{
links.append( " [<a href=\"/place.php?whichplace=edbase&action=edbase_book\">skill book</a>]" );
}
}
haveLinks = true;
}
// Otherwise, if we are level 15 or less, the guild might have a skill for us
// Only give a link if we have opened the guild
else if ( newLevel <= 15 && KoLCharacter.getGuildStoreOpen() )
{
links.append( " [<a href=\"guild.php\">guild</a>]" );
haveLinks = true;
}
links.append( "</font>" );
if ( haveLinks )
{
buffer.insert( index + test.length(), links.toString() );
}
}
private static final void addTransponderLink( final StringBuffer buffer )
{
// You can't get there anymore, because you don't know the
// transporter frequency. You consider beating up Kenneth to
// see if <i>he</i> remembers it, but you think better of it.
String test = "You consider beating up Kenneth to see if <i>he</i> remembers it, but you think better of it.";
int index = buffer.indexOf( test );
if ( index == -1 )
{
test = "You can't get here without the proper transporter frequency.";
index = buffer.indexOf( test );
}
if ( index == -1 )
{
return;
}
if ( SpaaaceRequest.TRANSPONDER.getCount( KoLConstants.inventory ) == 0 )
{
return;
}
UseLinkDecorator.UseLink link = new UseLinkDecorator.UseLink( ItemPool.TRANSPORTER_TRANSPONDER, 1, "use transponder", "inv_use.php?which=3&whichitem=" );
buffer.insert( index + test.length(), link.getItemHTML() );
}
private static final AdventureResult WARBEAR_BATTERY = ItemPool.get( ItemPool.WARBEAR_BATTERY, 1 );
private static final void addBatteryLink( final StringBuffer buffer )
{
// Your hoverbelt would totally do the trick to get you up
// there, only it's out of juice.
String test = "Your hoverbelt would totally do the trick to get you up there, only it's out of juice.";
int index = buffer.indexOf( test );
if ( index == -1 )
{
return;
}
if ( RequestEditorKit.WARBEAR_BATTERY.getCount( KoLConstants.inventory ) == 0 )
{
return;
}
UseLinkDecorator.UseLink link = new UseLinkDecorator.UseLink( ItemPool.WARBEAR_BATTERY, 1, "install warbear battery", "inv_use.php?which=3&whichitem=" );
buffer.insert( index + test.length(), link.getItemHTML() );
}
private static final void addFolioLink( final StringBuffer buffer )
{
// Remember that devilish folio you read?
// No, you don't! You don't have it all still in your head!
// Better find a new one you can read! I swear this:
// 'Til you do, you can't visit the Suburbs of Dis!
String test = "'Til you do, you can't visit the Suburbs of Dis!";
int index = buffer.indexOf( test );
if ( index == -1 )
{
return;
}
if ( SuburbanDisRequest.FOLIO.getCount( KoLConstants.inventory ) == 0 )
{
return;
}
UseLinkDecorator.UseLink link = new UseLinkDecorator.UseLink( ItemPool.DEVILISH_FOLIO, 1, "use devilish folio", "inv_use.php?which=3&whichitem=" );
buffer.insert( index + test.length(), link.getItemHTML() );
}
private static final void addAbsintheLink( final StringBuffer buffer )
{
// For some reason, you can't find your way back there.
String test = "For some reason, you can't find your way back there.";
int index = buffer.indexOf( test );
if ( index == -1 )
{
return;
}
if ( ItemPool.get( ItemPool.ABSINTHE, 1 ).getCount( KoLConstants.inventory ) == 0 )
{
return;
}
UseLinkDecorator.UseLink link = new UseLinkDecorator.UseLink( ItemPool.ABSINTHE, 1, "use absinthe", "inv_use.php?which=3&whichitem=" );
buffer.insert( index + test.length(), link.getItemHTML() );
}
// <table width=400 cellspacing=0 cellpadding=0><tr><td style="color: white;" align=center bgcolor=blue>f<b>New Area Unlocked</b></td></tr><tr><td style="padding: 5px; border: 1px solid blue;"><center><table><tr><td><center><table><tr><td valign=center><img src="http://images.kingdomofloathing.com/adventureimages/../otherimages/ocean/corrala.gif"></td><td valign=center class=small><b>The Coral Corral</b>, on <a class=nounder href=seafloor.php><b>The Sea Floor</b></a>.</td></tr></table></center></td></tr></table></center></td></tr><tr><td height=4></td></tr></table>
private static final Pattern NEW_LOCATION_PATTERN = Pattern.compile( "<table.*?<b>New Area Unlocked</b>.*?(<img[^>]*>).*?(<b>(.*?)</b>)", Pattern.DOTALL );
public static final void addNewLocationLinks( final StringBuffer buffer )
{
if ( buffer.indexOf( "New Area Unlocked" ) == -1 )
{
return;
}
Matcher matcher = NEW_LOCATION_PATTERN.matcher( buffer );
// The Trapper can unlock multiple new locations for you at once
while ( matcher.find() )
{
String image = matcher.group(1);
String boldloc = matcher.group(2);
String locname = matcher.group(3);
String url;
if ( locname.contains( "Degrassi Knoll" ) )
{
url = KoLCharacter.knollAvailable() ?
"place.php?whichplace=knoll_friendly" :
"place.php?whichplace=knoll_hostile";
}
else if ( locname.contains( "A Small Pyramid" ) )
{
url = "place.php?whichplace=desertbeach&action=db_pyramid1";
}
else if ( locname.contains( "An Ancient Altar" ) )
{
url = "place.php?whichplace=spelunky&action=spelunky_side6";
}
else
{
KoLAdventure adventure = AdventureDatabase.getAdventure( locname );
if ( adventure == null )
{
if ( locname.startsWith( "The " ) )
{
adventure = AdventureDatabase.getAdventure( locname.substring( 4) );
}
else
{
adventure = AdventureDatabase.getAdventure( "The " + locname );
}
}
if ( adventure == null )
{
continue;
}
url = adventure.getRequest().getURLString();
}
String search = matcher.group(0);
String replace;
// Make the image clickable to go to the url
StringBuilder rep = new StringBuilder();
rep.append( "<a href=\"" );
rep.append( url );
rep.append( "\">" );
rep.append( image );
rep.append( "</a>" );
replace = StringUtilities.singleStringReplace( search, image, rep.toString() );
// Make the location name clickable to go to the url
rep.setLength( 0 );
rep.append( "<a class=nounder href=\"" );
rep.append( url );
rep.append( "\">" );
rep.append( boldloc );
rep.append( "</a>" );
replace = StringUtilities.singleStringReplace( replace, boldloc, rep.toString() );
// Insert the replacements into the buffer
StringUtilities.singleStringReplace( buffer, search, replace );
if ( locname.equals( "The Spooky Forest" ) )
{
// The Distant Woods must be accessible before The Florist Friar
// can be used. This is the most reliable place to detect that.
FloristRequest.reset();
RequestThread.postRequest( new FloristRequest() );
}
}
}
// <script>
// (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
// (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
// m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
// })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
//
// ga('create', 'UA-47556088-1', 'kingdomofloathing.com');
// ga('send', 'pageview');
//
//</script>
private static final Pattern MALWARE1_PATTERN = Pattern.compile( "<script>[\\s]*\\(function\\(i,s,o,g,r,a,m\\).*?GoogleAnalyticsObject.*?</script>", Pattern.DOTALL );
// <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
// <!-- ROS_728x90 -->
// <ins class="adsbygoogle"
// style="display:inline-block;width:728px;height:90px"
// data-ad-client="ca-pub-5904875379193204"
// data-ad-slot="3053908571"></ins>
// <script>
// (adsbygoogle = window.adsbygoogle || []).push({});
// </script>
// <br><img src=/images/otherimages/1x1trans.gif height=4><br>
private static final Pattern MALWARE2_PATTERN = Pattern.compile( "<script async src=\"//.*?adsbygoogle.js\".*?1x1trans.gif.*?<br>", Pattern.DOTALL );
private static final void suppressPotentialMalware( final StringBuffer buffer )
{
// Always remove lag-inducing Javascript
if ( false && !Preferences.getBoolean( "suppressPotentialMalware" ) )
{
return;
}
if ( buffer.indexOf( "GoogleAnalyticsObject" ) != -1 )
{
Matcher matcher = RequestEditorKit.MALWARE1_PATTERN.matcher( buffer );
if ( matcher.find() )
{
StringUtilities.globalStringDelete( buffer, matcher.group( 0 ) );
}
}
if ( buffer.indexOf( "adsbygoogle" ) != -1 )
{
Matcher matcher = RequestEditorKit.MALWARE2_PATTERN.matcher( buffer );
if ( matcher.find() )
{
StringUtilities.globalStringDelete( buffer, matcher.group( 0 ) );
}
}
}
// *********************************************************************
//
// If you have donated to KoL within the last 90 days, periodically you
// get a nice "thank you" message on the fight page. I always smile when
// I get such a thank you.
//
// If you have not donated within the last 90 days, you get a "nag",
// suggesting that you donate and buy the current IOTM.
//
// I donate every month to buy the IOTM. A while ago, I decided to
// donate $50 and get five Mr. A's, to be spent one per month. More
// convenient for me, and better for KoL, since paying in advance always
// favors the vendor, who gets the interest on your money.
//
// It turns out that this means I get 3 months of "thank you" and 2
// months of "you haven't donated recently enough. Why don't you donate
// for a Mr. A so you can buy the IOTM you just bought with a Mr. A. you
// previously donated for?"
//
// Before I understood why this was happening, I sent a polite and
// friendly bug report asking why I was getting nags, when I was a
// regular donator. The response was that the advertising was behaving
// as coded, and therefore was "correct"
//
// I do not expect a coding change on KoL's end to stop inappropriate
// nags, so here is a simple self-service remedy.
private static final Pattern NAG_PATTERN = Pattern.compile( "<table.*?Please consider supporting the Kingdom.*?<td height=4>.*?<td height=4>.*?</table>", Pattern.DOTALL );
private static final void suppressInappropriateNags( final StringBuffer buffer )
{
if ( !Preferences.getBoolean( "suppressInappropriateNags" ) )
{
return;
}
if ( buffer.indexOf( "Please consider supporting the Kingdom!" ) != -1 )
{
Matcher matcher = RequestEditorKit.NAG_PATTERN.matcher( buffer );
if ( matcher.find() )
{
StringUtilities.globalStringDelete( buffer, matcher.group( 0 ) );
}
}
}
// *******************************************************************
private static final void decorateInventory( final StringBuffer buffer, final boolean addComplexFeatures )
{
// <table width=100%><tr><td colspan=2 width="210"></td><td width=20 rowspan=2></td><td class=small align=center valign=top rowspan=2><font size=2>[<a href="craft.php">craft stuff</a>] [<a href="sellstuff.php">sell stuff</a>]<br /></font></td><td width=20 rowspan=2></td><td colspan=2 width="210"></td></tr></table>
StringBuilder links = new StringBuilder();
boolean sushi = KoLCharacter.hasSushiMat();
if ( sushi )
{
links.append( "[<a href=\"sushi.php\">roll sushi</a>]" );
}
AdventureResult wand = KoLCharacter.getZapper();
if ( wand != null )
{
if ( links.length() > 0 )
{
links.append( " " );
}
links.append( "[<a href=\"wand.php?whichwand=" );
links.append( wand.getItemId() );
links.append( "\">zap items</a>]" );
}
if ( links.length() > 0 )
{
StringUtilities.globalStringDelete(
buffer,
"<td width=20 rowspan=2></td>" );
StringUtilities.singleStringReplace(
buffer,
"<br /></font></td>",
"<br />" + links.toString() + "<br /></font>" );
}
// Automatically name the outfit "backup" for simple save
// purposes while adventuring in browser.
StringUtilities.insertAfter(
buffer, "<input type=text name=outfitname", " value=\"Backup\"" );
if ( !addComplexFeatures )
{
return;
}
// Split out normal outfits, custom outfits, automatic outfits
Matcher fmatcher = OUTFIT_FORM_PATTERN.matcher( buffer );
if ( !fmatcher.find() )
{
return;
}
StringBuffer obuffer = new StringBuffer();
obuffer.append( "<table>" );
// If there aren't any normal outfits, the Custom Outfits label is absent
Matcher cmatcher = NOLABEL_CUSTOM_OUTFITS_PATTERN.matcher( fmatcher.group() );
if ( cmatcher.find() )
{
String options = cmatcher.group( 1 );
addOutfitGroup( obuffer, "outfit2", "Custom", "a custom", options );
}
// Find option groups in the whichoutfit drop down
Matcher omatcher = OPTGROUP_PATTERN.matcher( fmatcher.group() );
while ( omatcher.find() )
{
String group = omatcher.group( 1 );
String options = omatcher.group( 2 );
if ( group.equals( "Normal Outfits" ) )
{
addOutfitGroup( obuffer, "outfit", "Outfits", "an", options );
}
else if ( group.equals( "Custom Outfits" ) )
{
addOutfitGroup( obuffer, "outfit2", "Custom", "a custom", options );
}
else if ( group.equals( "Automatic Outfits" ) )
{
addOutfitGroup( obuffer, "outfit3", "Automatic", "an automatic", options );
}
}
obuffer.append( "</table>" );
// Replace the original form with a table of forms
buffer.replace( fmatcher.start(), fmatcher.end(), obuffer.toString() );
}
private static final void addOutfitGroup( final StringBuffer buffer, final String formName, final String label, final String type, final String options )
{
if ( options.length() == 0 )
{
return;
}
buffer.append( "<tr><td align=right><form name=" );
buffer.append( formName );
buffer.append( " action=inv_equip.php><input type=hidden name=action value=\"outfit\"><input type=hidden name=which value=2><b>" );
buffer.append( label );
buffer.append( ":</b> </td><td><select style=\"width: 250px\" name=whichoutfit><option value=0>(select " );
buffer.append( type );
buffer.append( " outfit)</option>" );
buffer.append( options );
buffer.append( "</select></td><td> <input class=button type=submit value=\"Dress Up!\"><br></form></td></tr>" );
}
public static final void addChatFeatures( final StringBuffer buffer )
{
StringUtilities.insertBefore(
buffer, "</html>",
"<script language=\"Javascript\"> var notchat = true; var " + ChatPoller.getRightClickMenu() + " </script>" + "<script language=\"Javascript\" src=\"/images/scripts/rcm.20101215.js\"></script>" );
StringUtilities.insertBefore( buffer, "</body>", "<div id='menu' class='rcm'></div>" );
}
private static final void addFightModifiers( final StringBuffer buffer )
{
// Change bang potion names in item dropdown
RequestEditorKit.changePotionNames( buffer );
// Hilight He-Boulder eye color messages
if ( KoLCharacter.getFamiliar().getId() == FamiliarPool.HE_BOULDER )
{
StringUtilities.globalStringReplace( buffer, "s red eye",
"s <font color=red>red eye</font>" );
StringUtilities.globalStringReplace( buffer, " blue eye",
" <font color=blue>blue eye</font>" );
StringUtilities.globalStringReplace( buffer, " yellow eye",
" <font color=olive>yellow eye</font>" );
}
RequestEditorKit.insertRoundNumbers( buffer );
if ( Preferences.getBoolean( "macroLens" ) )
{
String test = "<input type=\"hidden\" name=\"macrotext\" value=\"\">";
if ( buffer.indexOf( test ) == -1 )
{
String test2 = "<form name=runaway action=fight.php method=post>";
int index = buffer.indexOf( test2 );
if ( index != -1 )
{
buffer.insert( index,
"<form name=macro action=fight.php method=post><input type=hidden name=action value=\"macro\"><input type=\"hidden\" name=\"macrotext\" value=\"\"><tr><td align=center><select name=whichmacro><option value='0'>(select a macro)</option></select> <input class=button type=submit onclick=\"return killforms(this);\" value=\"Execute Macro\"></td></tr></form>" );
}
}
StringUtilities.singleStringReplace( buffer,
test,
"<tr><td><textarea name=\"macrotext\" cols=25 rows=10 placeholder=\"type macro here\"></textarea><script language=JavaScript src=\"/" + KoLConstants.MACROHELPER_JS + "\"></script></td></tr>" );
}
if ( buffer.indexOf( "but not before you grab one of its teeth" ) != -1 )
{
StringUtilities.singleStringReplace( buffer, "necklace",
"<a href=\"javascript:void(item('222160625'))\">necklace</a>" );
}
int runaway = FightRequest.freeRunawayChance();
if ( runaway > 0 )
{
int pos = buffer.indexOf( "type=submit value=\"Run Away\"" );
if ( pos != -1 )
{
buffer.insert( pos + 27, " (" + runaway + "% chance of being free)" );
}
}
// Add monster data HP/Atk/Def and item drop data
RequestEditorKit.annotateMonster( buffer );
// You slap a flyer up on your opponent. It enrages
// it.</td></tr>
int flyerIndex = buffer.indexOf( "You slap a flyer up on your opponent" );
if ( flyerIndex != -1 )
{
String message = "<tr><td colspan=2>" + RequestEditorKit.advertisingMessage() + "</td></tr>";
flyerIndex = buffer.indexOf( "</tr>", flyerIndex );
buffer.insert( flyerIndex + 5, message );
}
// You are slowed too much by the water, and a stupid dolphin
// swims up and snags <b>a seaweed</b> before you can grab
// it.<p>
int dolphinIndex = buffer.indexOf( "a stupid dolphin swims up and snags" );
if ( dolphinIndex != -1 )
{
// If we have a dolphin whistle in inventory, offer a link to use it.
if ( InventoryManager.hasItem( ItemPool.DOLPHIN_WHISTLE ) )
{
String message = "<br><font size=1>[<a href=\"inv_use.php?pwd=" + GenericRequest.passwordHash + "&which=3&whichitem=3997\">use dolphin whistle</a>]</font><br>";
dolphinIndex = buffer.indexOf( "<p>", dolphinIndex );
buffer.replace( dolphinIndex, dolphinIndex + 3, message );
}
}
Matcher matcher = DwarfFactoryRequest.attackMessage( buffer );
if ( matcher != null )
{
int attack = DwarfFactoryRequest.deduceAttack( matcher );
buffer.insert( matcher.end(), "<p>(Attack rating = " + attack + ")</p>" );
}
matcher = DwarfFactoryRequest.defenseMessage( buffer );
if ( matcher != null )
{
int defense = DwarfFactoryRequest.deduceDefense( matcher );
buffer.insert( matcher.end(), "<p>(Defense rating = " + defense + ")</p>" );
}
matcher = DwarfFactoryRequest.hpMessage( buffer );
if ( matcher != null )
{
// Must iterate over a copy of the buffer, since we'll be modifying it
matcher = DwarfFactoryRequest.hpMessage( buffer.toString() );
buffer.setLength( 0 );
do
{
int hp = DwarfFactoryRequest.deduceHP( matcher );
matcher.appendReplacement( buffer, "$0<p>(Hit Points = " + hp + ")</p>" );
}
while ( matcher.find() );
matcher.appendTail( buffer );
}
MonsterData monster = MonsterStatusTracker.getLastMonster();
String monsterName = monster != null ? monster.getName() : "";
// We want to decorate battlefield monsters, whether or not you
// actually find them on the battlefield.
if ( IslandManager.isBattlefieldMonster( monsterName ) )
{
IslandDecorator.decorateBattlefieldFight( buffer );
}
if ( monsterName.contains( "gremlin" ) )
{
IslandDecorator.decorateGremlinFight( monsterName, buffer );
}
switch ( KoLAdventure.lastAdventureId() )
{
case AdventurePool.THEMTHAR_HILLS:
IslandDecorator.decorateThemtharFight( buffer );
break;
case AdventurePool.SEASIDE_MEGALOPOLIS:
MemoriesDecorator.decorateMegalopolisFight( buffer );
break;
case AdventurePool.OUTSIDE_THE_CLUB:
NemesisDecorator.decorateRaverFight( buffer );
break;
}
}
public static final String advertisingMessage()
{
int ML = Preferences.getInteger( "flyeredML" );
float percent = Math.min( 100.0f * (float)ML/10000.0f, 100.0f );
return "You have completed " +
KoLConstants.FLOAT_FORMAT.format( percent ) +
"% of the necessary advertising.";
}
private static final void annotateMonster( final StringBuffer buffer )
{
MonsterData monster = MonsterStatusTracker.getLastMonster();
if ( monster == null )
{
return;
}
// Don't show monster unless we know combat stats or items
// or monster element
if ( monster.getHP() == 0 &&
monster.getItems().isEmpty() &&
monster.getDefenseElement() == MonsterDatabase.Element.NONE )
{
return;
}
StringBuffer monsterData = new StringBuffer( "<font size=2 color=gray>" );
int nameIndex = buffer.indexOf( "<span id='monname" );
int insertionPointForData;
if ( nameIndex != -1 )
{
int combatIndex = buffer.indexOf( "</span>", nameIndex );
if ( combatIndex == -1 )
{
return;
}
insertionPointForData = combatIndex + 7;
}
else
{
// find bold "Combat"
insertionPointForData = buffer.indexOf( "<b>" );
if ( insertionPointForData == -1 )
{
return;
}
// find bold monster name
nameIndex = buffer.indexOf( "<b>", insertionPointForData + 4 );
if ( nameIndex == -1 )
{
return;
}
buffer.insert( nameIndex, "<span id='monname'>" );
// find end of monster
int combatIndex = buffer.indexOf( "</td>", nameIndex );
if ( combatIndex == -1 )
{
return;
}
buffer.insert( combatIndex, "</span>" );
insertionPointForData = combatIndex + 7;
}
monsterData.append( "<br />HP: " );
monsterData.append( MonsterStatusTracker.getMonsterHealth() );
monsterData.append( ", Atk: " );
monsterData.append( MonsterStatusTracker.getMonsterAttack() );
monsterData.append( ", Def: " );
monsterData.append( MonsterStatusTracker.getMonsterDefense() );
monsterData.append( ", Type: " );
monsterData.append( MonsterStatusTracker.getMonsterPhylum().toString() );
String monsterName = monster.getName();
if ( FightRequest.isPirate( monster ) )
{
int count = BeerPongRequest.countPirateInsults();
monsterData.append( ", Insults: ");
monsterData.append( count );
monsterData.append( " (");
float odds = BeerPongRequest.pirateInsultOdds( count ) * 100.0f;
monsterData.append( KoLConstants.FLOAT_FORMAT.format( odds ) );
monsterData.append( "%)");
}
else if ( monsterName.equals( "black pudding" ) )
{
int count = Preferences.getInteger( "blackPuddingsDefeated" );
monsterData.append( ", Defeated: ");
monsterData.append( count );
}
else if ( monsterName.equals( "wall of skin" ) )
{
RequestEditorKit.selectOption( buffer, "whichitem", String.valueOf( ItemPool.BEEHIVE ) );
}
else if ( monsterName.equals( "wall of bones" ) )
{
RequestEditorKit.selectOption( buffer, "whichitem", String.valueOf( ItemPool.ELECTRIC_BONING_KNIFE ) );
}
String danceMoveStatus = NemesisDecorator.danceMoveStatus( monsterName );
if ( danceMoveStatus != null )
{
monsterData.append( "<br />" );
monsterData.append( danceMoveStatus );
}
List items = monster.getItems();
if ( !items.isEmpty() )
{
monsterData.append( "<br />Drops: " );
for ( int i = 0; i < items.size(); ++i )
{
if ( i != 0 )
{
monsterData.append( ", " );
}
AdventureResult item = (AdventureResult) items.get( i );
int rate = item.getCount() >> 16;
monsterData.append( item.getName() );
switch ( (char) item.getCount() & 0xFFFF )
{
case 'p':
monsterData.append( " (" );
monsterData.append( rate );
monsterData.append( " pp only)" );
break;
case 'n':
monsterData.append( " (" );
monsterData.append( rate );
monsterData.append( " no pp)" );
break;
case 'c':
monsterData.append( " (" );
monsterData.append( rate );
monsterData.append( " cond)" );
break;
case 'f':
monsterData.append( " (" );
monsterData.append( rate );
monsterData.append( " no mod)" );
break;
case 'a':
monsterData.append( " (stealable accordion)" );
break;
default:
monsterData.append( " (" );
monsterData.append( rate );
monsterData.append( ")" );
}
}
}
String bounty = BountyDatabase.getNameByMonster( MonsterDatabase.findMonster( monsterName, false ).getName() );
if ( bounty != null )
{
monsterData.append( items.isEmpty() ? "<br />Drops: " : ", " );
monsterData.append( bounty );
monsterData.append( " (bounty)" );
}
int minMeat = monster.getMinMeat();
int maxMeat = monster.getMaxMeat();
if ( maxMeat > 0 )
{
double modifier = Math.max( 0.0, ( KoLCharacter.getMeatDropPercentAdjustment() + 100.0 ) / 100.0 );
monsterData.append( "<br />Meat: " );
monsterData.append( String.valueOf( (int)Math.floor( minMeat * modifier ) ) );
monsterData.append( "-" );
monsterData.append( String.valueOf( (int)Math.floor( maxMeat * modifier ) ) );
}
int minSprinkles = monster.getMinSprinkles();
int maxSprinkles = monster.getMaxSprinkles();
if ( maxSprinkles > 0 )
{
double modifier = Math.max( 0.0, ( KoLCharacter.getSprinkleDropPercentAdjustment() + 100.0 ) / 100.0 );
monsterData.append( "<br />Sprinkles: " );
monsterData.append( String.valueOf( (int)Math.floor( minSprinkles * modifier ) ) );
if ( maxSprinkles != minSprinkles )
{
monsterData.append( "-" );
monsterData.append( String.valueOf( (int)Math.ceil( maxSprinkles * modifier ) ) );
}
}
IslandDecorator.appendMissingGremlinTool( monsterData );
if ( KoLCharacter.getLimitmode() == Limitmode.SPELUNKY )
{
SpelunkyRequest.decorateSpelunkyMonster( monsterData );
}
monsterData.append( "</font>" );
buffer.insert( insertionPointForData, monsterData.toString() );
// Insert color for monster element
MonsterDatabase.Element monsterElement = monster.getDefenseElement();
if ( monsterElement != MonsterDatabase.Element.NONE )
{
int insertionPointForElement = nameIndex + 6;
buffer.insert( insertionPointForElement, "class=\"element" + monsterElement + "\" " );
}
}
private static final void addMultiuseModifiers( final StringBuffer buffer )
{
// Change bang potion names in item dropdown
RequestEditorKit.changePotionNames( buffer );
}
private static final void changePotionImages( final StringBuffer buffer )
{
if ( buffer.indexOf( "exclam.gif" ) == -1 &&
buffer.indexOf( "vial.gif" ) == -1)
{
return;
}
if ( !Preferences.getBoolean( "relayShowSpoilers" ) )
{
return;
}
ArrayList<String> potionNames = new ArrayList<String>();
ArrayList<String> pluralNames = new ArrayList<String>();
ArrayList<String> potionEffects = new ArrayList<String>();
for ( int i = 819; i <= 827; ++i )
{
String name = ItemDatabase.getItemName( i );
String plural = ItemDatabase.getPluralName( i );
if ( buffer.indexOf( name ) != -1 || buffer.indexOf( plural ) != -1 )
{
String effect = Preferences.getString( "lastBangPotion" + i );
if ( !effect.equals( "" ) )
{
potionNames.add( name );
pluralNames.add( plural );
potionEffects.add( " of " + effect );
}
}
}
for ( int i = ItemPool.VIAL_OF_RED_SLIME; i <= ItemPool.VIAL_OF_PURPLE_SLIME; ++i )
{
String name = ItemDatabase.getItemName( i );
String plural = ItemDatabase.getPluralName( i );
if ( buffer.indexOf( name ) != -1 || buffer.indexOf( plural ) != -1 )
{
String effect = Preferences.getString( "lastSlimeVial" + i );
if ( !effect.equals( "" ) )
{
potionNames.add( name );
pluralNames.add( plural );
potionEffects.add( ": " + effect );
}
}
}
if ( potionNames.isEmpty() )
{
return;
}
for ( int i = 0; i < potionNames.size(); ++i )
{
String name = potionNames.get( i );
String plural = pluralNames.get( i );
String effect = potionEffects.get( i );
StringUtilities.globalStringReplace( buffer, name + "</b>", name + effect + "</b>" );
StringUtilities.globalStringReplace( buffer, plural + "</b>", plural + effect + "</b>" );
}
}
private static final void changePotionNames( final StringBuffer buffer )
{
for ( int i = 819; i <= 827; ++i )
{
String name = ItemDatabase.getItemName( i );
String plural = ItemDatabase.getPluralName( i );
if ( buffer.indexOf( name ) != -1 || buffer.indexOf( plural ) != -1 )
{
String effect = Preferences.getString( "lastBangPotion" + i );
if ( effect.equals( "" ) )
{
continue;
}
StringUtilities.globalStringReplace( buffer, name, name + " of " + effect );
StringUtilities.globalStringReplace( buffer, plural, plural + " of " + effect );
}
}
for ( int i = ItemPool.VIAL_OF_RED_SLIME; i <= ItemPool.VIAL_OF_PURPLE_SLIME; ++i )
{
String name = ItemDatabase.getItemName( i );
String plural = ItemDatabase.getPluralName( i );
if ( buffer.indexOf( name ) != -1 || buffer.indexOf( plural ) != -1 )
{
String effect = Preferences.getString( "lastSlimeVial" + i );
if ( effect.equals( "" ) )
{
continue;
}
StringUtilities.globalStringReplace( buffer, name, name + ": " + effect );
StringUtilities.globalStringReplace( buffer, plural, plural + ": " + effect );
}
}
}
private static final void changePunchcardNames( final StringBuffer buffer )
{
if ( buffer.indexOf( "El Vibrato punchcard" ) == -1 )
{
return;
}
for ( Object[] punchcard: ItemDatabase.PUNCHCARDS )
{
String name = (String) punchcard[1];
if ( buffer.indexOf( name ) != -1 )
{
StringUtilities.globalStringReplace( buffer, name, (String) punchcard[2] );
}
}
}
private static final void fixTavernCellar( final StringBuffer buffer )
{
// When you adventure in the Typical Tavern Cellar, the
// Adventure Again link takes you to the map. Fix that link
// as follows:
//
// (new) Explore Next Unexplored Square
// Go back to the Typical Tavern Cellar
int index = buffer.indexOf( "<a href=\"cellar.php\">" );
if ( index == -1 )
{
return;
}
int unexplored = TavernManager.nextUnexploredSquare();
if ( unexplored <= 0 )
{
return;
}
StringBuilder link = new StringBuilder();
link.append( "<a href=\"cellar.php?action=explore&whichspot=" );
link.append( String.valueOf( unexplored ) );
link.append( "\">Explore Next Unexplored Square</a><p>" );
buffer.insert( index, link.toString() );
}
private static final void addTaleOfDread( final StringBuffer buffer )
{
// You hear the scratching sounds of a quill pen from inside
// your sack. A new Tale of Dread has written itself in your
// scary storybook!
int index = buffer.indexOf( "A new Tale of Dread" );
if ( index == -1 )
{
return;
}
MonsterData monster = MonsterStatusTracker.getLastMonster();
String monsterName =
( monster != null ) ?
StringUtilities.singleStringReplace( monster.getName(), " (Dreadsylvanian)", "" ):
"";
String find = "your scary storybook!";
StringBuilder replace = new StringBuilder( find );
replace.append( " <font size=1>[<a href=\"" );
replace.append( "/KoLmafia/redirectedCommand?cmd=taleofdread " );
replace.append( monsterName );
replace.append( " redirect" );
replace.append( "&pwd=" );
replace.append( GenericRequest.passwordHash );
replace.append( "\">read it</a>]</font>" );
StringUtilities.singleStringReplace( buffer, find, replace.toString() );
}
private static final Pattern DESERT_EXPLORATION_PATTERN = Pattern.compile( "Desert exploration <b>\\+\\d+%</b>" );
private static final void addDesertProgress( final StringBuffer buffer )
{
String lastAdventure = Preferences.getString( "lastAdventure" );
if ( !lastAdventure.equals( "The Arid, Extra-Dry Desert" ) ||
buffer.indexOf( "WINWINWIN" ) == -1 )
{
return;
}
Matcher m = RequestEditorKit.DESERT_EXPLORATION_PATTERN.matcher( buffer );
if ( !m.find() )
{
return;
}
String progress = " (" + String.valueOf( Preferences.getInteger( "desertExploration" ) ) + "% explored)";
buffer.insert( m.end(), progress );
}
private static final Pattern FOREST_EXPLORATION_PATTERN = Pattern.compile( "(location on your black map\\.|Halloween falls on a Sunday, maybe\\.|realize why that would have been a bad idea\\.)" );
private static final void addBlackForestProgress( final StringBuffer buffer )
{
String lastAdventure = Preferences.getString( "lastAdventure" );
if ( !lastAdventure.equals( "The Black Forest" ) ||
buffer.indexOf( "WINWINWIN" ) == -1 )
{
return;
}
Matcher m = RequestEditorKit.FOREST_EXPLORATION_PATTERN.matcher( buffer );
if ( !m.find() )
{
return;
}
String progress = " (" + String.valueOf( Preferences.getInteger( "blackForestProgress" ) ) + "/5 Landmarks marked on your map)";
buffer.insert( m.end(), progress );
}
private static final void insertRoundNumbers( final StringBuffer buffer )
{
Matcher m = FightRequest.ONTURN_PATTERN.matcher( buffer );
if ( !m.find() )
{
return;
}
int round = StringUtilities.parseInt( m.group( 1 ) );
m = RequestEditorKit.ROUND_SEP_PATTERN.matcher( buffer.toString() );
buffer.setLength( 0 );
while ( m.find() )
{
if ( m.group().startsWith( "<b" ) )
{ // Initial round - add # after "Combat"
m.appendReplacement( buffer, "<b>Combat: Round " );
buffer.append( round++ );
if ( KoLCharacter.isEd() )
{
int edfight = Preferences.getInteger( "_edDefeats" );
if ( FightRequest.currentRound != 0 )
{
edfight++;
}
buffer.append( ", Fight " ).append( edfight );
}
buffer.append( "!</b>" );
}
else
{ // Subsequent rounds - replace <hr> with bar like title
m.appendReplacement( buffer, "<table width=100%><tr><td style=\"color: white;\" align=center bgcolor=blue><b>Round " );
buffer.append( round++ );
buffer.append( "!</b></td></tr></table>" );
}
}
m.appendTail( buffer );
}
private static final void addChoiceSpoilers( final String location, final StringBuffer buffer )
{
if ( !Preferences.getBoolean( "relayShowSpoilers" ) )
{
return;
}
// Make sure that it's an actual choice adventure
int choice = ChoiceManager.extractChoice( buffer.toString() );
if ( choice == 0 )
{
// It's a response to taking a choice.
RequestEditorKit.decorateChoiceResponse( location, buffer );
return;
}
// Do any choice-specific decorations
ChoiceManager.decorateChoice( choice, buffer );
String text = buffer.toString();
Matcher matcher = FORM_PATTERN.matcher( text );
if ( !matcher.find() )
{
return;
}
// Find the options for the choice we've encountered
Object[][] spoilers = ChoiceManager.choiceSpoilers( choice );
if ( spoilers == null )
{ // Don't give up - there may be a specified choice even if there
// are no spoilers.
spoilers = new Object[][] { null, null, {} };
}
int index1 = matcher.start();
int decision = ChoiceManager.getDecision( choice, text );
buffer.setLength( 0 );
buffer.append( text.substring( 0, index1 ) );
while ( true )
{
int index2 = text.indexOf( "</form>", index1 );
// If KoL says we've run out of choices, quit now
if ( index2 == -1 )
{
break;
}
String currentSection = text.substring( index1, index2 );
Matcher optionMatcher = RequestEditorKit.OPTION_PATTERN.matcher( currentSection );
if ( !optionMatcher.find() )
{ // this wasn't actually a choice option - strange!
buffer.append( currentSection );
buffer.append( "</form>" );
index1 = index2 + 7;
continue;
}
int i = StringUtilities.parseInt( optionMatcher.group( 1 ) );
if ( i != decision )
{
buffer.append( currentSection );
}
else
{
int pos = currentSection.lastIndexOf( "value=\"" );
buffer.append( currentSection.substring( 0, pos + 7 ) );
buffer.append( "→ " );
buffer.append( currentSection.substring( pos + 7 ) );
}
// Start spoiler text
while ( i > 0 )
{
// Say what the choice will give you
Object spoiler = ChoiceManager.choiceSpoiler( choice, i, spoilers[ 2 ] );
// If we have nothing to say about this option, don't say anything
if ( spoiler == null )
{
break;
}
String name = spoiler.toString();
if ( name.equals( "" ) )
{
break;
}
buffer.append( "<br><font size=-1>(" );
buffer.append( name );
// If this decision has an item associated with it, annotate it
if ( spoiler instanceof ChoiceManager.Option )
{
AdventureResult item = ((ChoiceManager.Option)spoiler).getItem();
// If this decision leads to an item...
if ( item != null )
{
// List # in inventory
buffer.append( "<img src=\"/images/itemimages/magnify.gif\" valign=middle onclick=\"descitem('" );
buffer.append( ItemDatabase.getDescriptionId( item.getItemId() ) );
buffer.append( "');\">" );
int available = KoLCharacter.hasEquipped( item ) ? 1 : 0;
available += item.getCount( KoLConstants.inventory );
buffer.append( available );
buffer.append( " in inventory" );
}
}
// Finish spoiler text
buffer.append( ")</font>" );
break;
}
buffer.append( "</form>" );
index1 = index2 + 7;
}
buffer.append( text.substring( index1 ) );
}
private static final void addBarrelSounds( final StringBuffer buffer )
{
if ( !Preferences.getBoolean( "relayAddSounds" ) )
{
return;
}
if ( buffer.indexOf( "barrelpart" ) != -1 )
{
StringUtilities.insertBefore( buffer, "</html>", "<script src=\"/" + KoLConstants.BARREL_SOUNDS_JS + "\"></script>" );
}
}
private static final void decorateChoiceResponse( final String location, final StringBuffer buffer )
{
Matcher matcher = RequestEditorKit.CHOICE2_PATTERN.matcher( location );
if ( !matcher.find() )
{
return;
}
int choice = StringUtilities.parseInt( matcher.group( 1 ) );
switch ( choice )
{
// The Oracle Will See You Now
case 3:
StringUtilities.singleStringReplace(
buffer, "It's actually a book. Read it.",
"It's actually a book. <font size=1>[<a href=\"inv_use.php?pwd=" + GenericRequest.passwordHash + "&which=3&whichitem=818\">read it</a>]</font>" );
break;
case 392:
MemoriesDecorator.decorateElementsResponse( buffer );
break;
case 443:
// Chess Puzzle
RabbitHoleManager.decorateChessPuzzleResponse( buffer );
break;
case 509: // Of Course!
case 1000: // Everything in Moderation
// You should probably go tell Bart you've fixed his rat problem.
// You should probably go tell Bart you've fixed his lack-of-rat problem.
StringUtilities.singleStringReplace(
buffer, "rat problem.",
"rat problem. <font size=1>[<a href=\"tavern.php?place=barkeep\">Visit Bart</a>]</font>" );
break;
case 537:
// Play Porko!
case 540:
// Big-Time Generator
SpaaaceRequest.decoratePorko( buffer );
break;
case 571:
// Your Minstrel Vamps
RequestEditorKit.addMinstrelNavigationLink( buffer, "Go to the Typical Tavern", "tavern.php" );
break;
case 572:
// Your Minstrel Clamps
RequestEditorKit.addMinstrelNavigationLink( buffer, "Go to the Knob Shaft", "adventure.php?snarfblat=101" );
break;
case 573:
// Your Minstrel Stamps
// Add a link to the Luter's Grave
RequestEditorKit.addMinstrelNavigationLink( buffer, "Go to the Luter's Grave",
"place.php?whichplace=plains&action=lutersgrave" );
break;
case 576:
// Your Minstrel Camps
RequestEditorKit.addMinstrelNavigationLink( buffer, "Go to the Icy Peak", "adventure.php?snarfblat=110" );
break;
case 577:
// Your Minstrel Scamp
RequestEditorKit.addMinstrelNavigationLink( buffer, "Go to the Ancient Buried Pyramid", "pyramid.php" );
break;
case 611: {
// The Horror...
int index = buffer.indexOf( "<p><a href=\"adventure.php?snarfblat=296\">Adventure Again (A-Boo Peak)</a>" );
int count = ItemPool.get( ItemPool.BOO_CLUE, 1 ).getCount( KoLConstants.inventory );
if ( index != -1 && count > 0 )
{
String link = "<a href=\"javascript:singleUse('inv_use.php','which=3&whichitem=5964&pwd=" + GenericRequest.passwordHash + "&ajax=1');void(0);\">Use another A-Boo clue</a>";
buffer.insert( index, link );
}
break;
}
case 1027: // The End of the Tale of Spelunking
case 1042: // Pick a Perk
SpelunkyRequest.decorateSpelunkyExit( buffer );
break;
}
}
private static final void addMinstrelNavigationLink( final StringBuffer buffer, final String tag, final String url )
{
int index = buffer.lastIndexOf( "<table>" );
if ( index == -1 )
{
return;
}
index = buffer.indexOf( "<p>", index );
if ( index == -1 )
{
return;
}
StringBuilder link = new StringBuilder();
link.append( "<p><a href=\"" );
link.append( url );
link.append( "\">" );
link.append( tag );
link.append( "</a>" );
buffer.insert( index, link.toString() );
}
private static final void decorateCouncil( final StringBuffer buffer )
{
if ( !KoLCharacter.inAxecore() )
{
return;
}
int index = buffer.lastIndexOf( "<p>" );
if ( index != -1 )
{
buffer.insert( index + 3, "<center><a href=\"da.php?place=gate1\">Bask in the Glory of Boris</a></center><br>" );
}
}
private static final void decorateCrypt( final StringBuffer buffer )
{
if ( Preferences.getInteger( "cyrptTotalEvilness") == 0 )
{
return;
}
int nookEvil = Preferences.getInteger( "cyrptNookEvilness" );
int nicheEvil = Preferences.getInteger( "cyrptNicheEvilness" );
int crannyEvil = Preferences.getInteger( "cyrptCrannyEvilness" );
int alcoveEvil = Preferences.getInteger( "cyrptAlcoveEvilness" );
String nookColor = nookEvil > 25 ? "000000" : "FF0000";
String nookHint = nookEvil > 25 ? "Item Drop" : "<b>BOSS</b>";
String nicheColor = nicheEvil > 25 ? "000000" : "FF0000";
String nicheHint = nicheEvil > 25 ? "Sniff Dirty Lihc" : "<b>BOSS</b>";
String crannyColor = crannyEvil > 25 ? "000000" : "FF0000";
String crannyHint = crannyEvil > 25 ? "ML & Noncombat" : "<b>BOSS</b>";
String alcoveColor = alcoveEvil > 25 ? "000000" : "FF0000";
String alcoveHint = alcoveEvil > 25 ? "Initiative" : "<b>BOSS</b>";
StringBuilder evilometer = new StringBuilder();
evilometer.append( "<table cellpadding=0 cellspacing=0><tr><td colspan=3>" );
evilometer.append( "<img src=\"" );
evilometer.append( KoLmafia.imageServerPath() );
evilometer.append( "otherimages/cyrpt/eo_top.gif\">" );
evilometer.append( "<tr><td><img src=\"" );
evilometer.append( KoLmafia.imageServerPath() );
evilometer.append( "otherimages/cyrpt/eo_left.gif\">" );
evilometer.append( "<td width=150><center>" );
if ( nookEvil > 0 )
{
evilometer.append( "<font size=2 color=\"#" );
evilometer.append( nookColor );
evilometer.append( "\"><b>Nook</b> - " );
evilometer.append( nookEvil );
evilometer.append( "<br><font size=1>" );
evilometer.append( nookHint );
evilometer.append( "<br></font></font>" );
}
if ( nicheEvil > 0 )
{
evilometer.append( "<font size=2 color=\"#" );
evilometer.append( nicheColor );
evilometer.append( "\"><b>Niche</b> - " );
evilometer.append( nicheEvil );
evilometer.append( "<br><font size=1>" );
evilometer.append( nicheHint );
evilometer.append( "<br></font></font>" );
}
if ( crannyEvil > 0 )
{
evilometer.append( "<font size=2 color=\"#" );
evilometer.append( crannyColor );
evilometer.append( "\"><b>Cranny</b> - " );
evilometer.append( crannyEvil );
evilometer.append( "<br><font size=1>" );
evilometer.append( crannyHint );
evilometer.append( "<br></font></font>" );
}
if ( alcoveEvil > 0 )
{
evilometer.append( "<font size=2 color=\"#" );
evilometer.append( alcoveColor );
evilometer.append( "\"><b>Alcove</b> - " );
evilometer.append( alcoveEvil );
evilometer.append( "<br><font size=1>" );
evilometer.append( alcoveHint );
evilometer.append( "<br></font></font>" );
}
evilometer.append( "<td><img src=\"" );
evilometer.append( KoLmafia.imageServerPath() );
evilometer.append( "otherimages/cyrpt/eo_right.gif\"><tr><td colspan=3>" );
evilometer.append( "<img src=\"" );
evilometer.append( KoLmafia.imageServerPath() );
evilometer.append( "otherimages/cyrpt/eo_bottom.gif\"></table>" );
String selector = "</map><table";
int index = buffer.indexOf( selector );
buffer.insert( index + selector.length(), "><tr><td><table" );
// <A href=place.php?whichplace=plains>Back to the Misspelled Cemetary</a>
// I expect that will change to "whichplace=cemetery" eventually.
index = buffer.indexOf( "</tr></table><p><center><A href=place.php" );
evilometer.insert( 0, "</table><td>" );
buffer.insert( index + 5, evilometer.toString() );
}
private static final void addBugReportWarning( final StringBuffer buffer )
{
// <div id="type_1">
// <table><tr><td><b>IMPORTANT:</b> If you can see this notice,
// the information you're about to submit may be seen by dev
// team volunteers in addition to the staff of Asymmetric
// Publications.<p>For the protection of your privacy, please
// do not submit any passwords, personal data, or donation
// information as a bug report! If you're having a donation or
// store issue, please select the appropriate category
// above.</td></tr></table>
// <p><b>Please describe the bug:</b></p>
// <textarea class="req" name=message cols=60 rows=10></textarea><br>
// </div>
int index = buffer.indexOf( "<p><b>Please describe the bug:</b></p>" );
if ( index == -1 )
{
return;
}
StringBuilder disclaimer = new StringBuilder();
disclaimer.append( "<p><span style=\"width: 95%; border: 3px solid red; color: red; padding: 5px; text-align: left; margin-bottom: 10px; background-color: rgb(254, 226, 226); display:block;\">" );
disclaimer.append( "You are currently running in the KoLmafia Relay Browser. It is possible that the bug you are experiencing is not in KoL itself, but is a result of KoLmafia, a Greasemonkey script, or another client-side modification. The KoL team requests that you verify that you can reproduce the bug in a vanilla browser with all add-ons and extensions disabled before submitting a bug report." );
disclaimer.append( "</span>" );
buffer.insert( index, disclaimer.toString() );
}
private static final void fixDucks( final StringBuffer buffer )
{
// KoL does not currently provide a link back to the farm after
// you defeat the last duck.
if ( buffer.indexOf( "ducks" ) == -1 )
{
return;
}
// But if they fix it and it now adds one, cool.
if ( buffer.indexOf( "island.php" ) != -1 )
{
return;
}
String war = IslandManager.warProgress();
String test;
String url;
if ( war.equals( "finished" ) )
{
// You wander around the farm for a while, but can't
// find any additional ducks to fight. Maybe some more
// will come out of hiding by tomorrow.
test = "any additional ducks";
url = "postwarisland.php";
}
else
{
// There are no more ducks here.
test = "There are no more ducks here.";
url = "bigisland.php?place=farm";
}
if ( buffer.indexOf( test ) == -1 )
{
return;
}
RequestEditorKit.addAdventureAgainSection( buffer, url, "Go back to The Mysterious Island of Mystery");
}
public static final void addAdventureAgainSection( final StringBuffer buffer, final String link, final String tag )
{
int index = buffer.indexOf( "</center></td></tr><tr><td height=4></td></tr></table>" );
if ( index == -1 )
{
return;
}
StringBuilder section = new StringBuilder();
section.append( "<center><p><a href=\"" );
section.append( link );
section.append( "\">" );
section.append( tag );
section.append( "</a></center>" );
buffer.insert( index, section.toString() );
}
private static final void fixBallroom1( final StringBuffer buffer )
{
// Things that go BEFORE Stationary Buttons have been generated
String link = null;
if ( buffer.indexOf( "Having a Ball in the Ballroom" ) != -1)
{
// Give the player a link to talk to Lady Spookyraven again (on the third floor)
//
// Unfortunately, place.php?whichplace=manor3&action=manor3_ladys does not work until you visit the third floor map
// link = "<p><a href=\"place.php?whichplace=manor3&action=manor3_ladys\">Talk to Lady Spookyraven on the Third Floor</a>";
//
// Unfortunately, place.php?whichplace=manor3 doesn't work until you visit the second floor map again.
// link = "<p><a href=\"place.php?whichplace=manor3\">Go to the Third Floor</a>";
//
// Which makes this whole function useless, unless we
// can figure out a way - via a sidepane command, say -
// to unlock the third floor and then talk to Lady S on
// the third floor
}
if ( link == null )
{
return;
}
int index = buffer.indexOf( "<p><a href=\"adventure.php?snarfblat=395\">" );
if ( index != -1 )
{
buffer.insert( index, link );
}
}
private static final AdventureResult DANCE_CARD = ItemPool.get( ItemPool.DANCE_CARD, 1);
private static final void fixBallroom2( final StringBuffer buffer )
{
// Things that go AFTER Stationary Buttons have been generated
String link = null;
if ( buffer.indexOf( "Rotting Matilda" ) != -1 )
{
// Give player a link to use another dance card
if ( DANCE_CARD.getCount( KoLConstants.inventory ) <= 0 )
{
return;
}
link = "<p><a href=\"javascript:singleUse('inv_use.php','which=3&whichitem=1963&pwd=" + GenericRequest.passwordHash + "&ajax=1');void(0);\">Use another dance card</a>";
}
if ( link == null )
{
return;
}
int index = buffer.indexOf( "<p><a href=\"adventure.php?snarfblat=395\">" );
if ( index != -1 )
{
buffer.insert( index, link );
}
}
private static final void fixGovernmentLab( final StringBuffer buffer )
{
// Things that go AFTER Stationary Buttons have been generated
String link = null;
String test = "without wearing a Personal Ventilation Unit.";
int index = buffer.indexOf( test );
if ( index == -1 )
{
return;
}
// Give player a link to equip the PVU
link = " <a href=\"javascript:singleUse('inv_equip.php',which=2&action=equip&slot=1&whichitem=7770&pwd=" + GenericRequest.passwordHash + "');void();\">[acc1]</a>" +
"<a href=\"javascript:singleUse('inv_equip.php',which=2&action=equip&slot=2&whichitem=7770&pwd=" + GenericRequest.passwordHash + "');void();\">[acc2]</a>" +
"<a href=\"javascript:singleUse('inv_equip.php',which=2&action=equip&slot=3&whichitem=7770&pwd=" + GenericRequest.passwordHash + "');void();\">[acc3]</a>";
UseLinkDecorator.UseLink link1 = new UseLink( ItemPool.VENTILATION_UNIT, 1,
UseLinkDecorator.getEquipmentSpeculation( "acc1", ItemPool.VENTILATION_UNIT, EquipmentManager.ACCESSORY1 ),
"inv_equip.php?which=2&action=equip&slot=1&whichitem=" );
UseLinkDecorator.UseLink link2 = new UseLink( ItemPool.VENTILATION_UNIT, 1,
UseLinkDecorator.getEquipmentSpeculation( "acc2", ItemPool.VENTILATION_UNIT, EquipmentManager.ACCESSORY2 ),
"inv_equip.php?which=2&action=equip&slot=2&whichitem=" );
UseLinkDecorator.UseLink link3 = new UseLink( ItemPool.VENTILATION_UNIT, 1,
UseLinkDecorator.getEquipmentSpeculation( "acc3", ItemPool.VENTILATION_UNIT, EquipmentManager.ACCESSORY3 ),
"inv_equip.php?which=2&action=equip&slot=3&whichitem=" );
buffer.insert( index + test.length(), link1.getItemHTML() + link2.getItemHTML() + link3.getItemHTML() );
}
private static class KoLSubmitView
extends FormView
{
public KoLSubmitView( final Element elem )
{
super( elem );
}
@Override
public Component createComponent()
{
Component c = super.createComponent();
if ( c != null && ( c instanceof JButton || c instanceof JRadioButton || c instanceof JCheckBox ) )
{
c.setBackground( Color.white );
}
return c;
}
@Override
public void submitData( final String data )
{
// Get the element
Element inputElement = this.getElement();
if ( inputElement == null )
{
return;
}
// Retrieve the frame which is being used by this form
// viewer.
RequestFrame frame = this.findFrame();
// If there is no frame, then there's nothing to
// refresh, so return.
if ( frame == null )
{
return;
}
// Retrieve the form element so that you know where you
// need to submit the data.
Element formElement = inputElement;
while ( formElement != null && formElement.getAttributes().getAttribute( StyleConstants.NameAttribute ) != HTML.Tag.FORM )
{
formElement = formElement.getParentElement();
}
// If the form element is null, then there was no
// enclosing form for the <INPUT> tag, so you can
// return, doing nothing.
if ( formElement == null )
{
return;
}
// Now that you know you have a form element,
// get the action field, attach the data, and
// refresh the appropriate request frame.
String action = (String) formElement.getAttributes().getAttribute( HTML.Attribute.ACTION );
// If there is no action, how do we know which page to
// connect to? We assume it's the originating page.
if ( action == null )
{
action = frame.getCurrentLocation();
}
// Now get the data fields we will submit to this form
String[] elements = data.split( "&" );
String[] fields = new String[ elements.length ];
if ( elements[ 0 ].length() > 0 )
{
for ( int i = 0; i < elements.length; ++i )
{
fields[ i ] = elements[ i ].substring( 0, elements[ i ].indexOf( "=" ) );
}
}
else
{
fields[ 0 ] = "";
}
// Prepare the element string -- make sure that
// you don't have duplicate fields.
for ( int i = 0; i < elements.length; ++i )
{
for ( int j = i + 1; j < elements.length; ++j )
{
if ( elements[ i ] != null && elements[ j ] != null && fields[ i ].equals( fields[ j ] ) )
{
elements[ j ] = null;
}
}
}
GenericRequest formSubmitter = new GenericRequest( "" );
if ( action.contains( "?" ) )
{
// For quirky URLs where there's a question mark
// in the middle of the URL, just string the data
// onto the URL. This is the way browsers work,
// so it's the way KoL expects the data.
StringBuilder actionString = new StringBuilder();
actionString.append( action );
for ( int i = 0; i < elements.length; ++i )
{
if ( elements[ i ] != null )
{
actionString.append( '&' );
actionString.append( elements[ i ] );
}
}
formSubmitter.constructURLString( GenericRequest.decodeField( actionString.toString(), "ISO-8859-1" ) );
}
else
{
// For normal URLs, the form data can be submitted
// just like in every other request.
formSubmitter.constructURLString( action );
if ( elements[ 0 ].length() > 0 )
{
for ( int i = 0; i < elements.length; ++i )
{
if ( elements[ i ] != null )
{
formSubmitter.addEncodedFormField( elements[ i ] );
}
}
}
}
frame.refresh( formSubmitter );
}
private RequestFrame findFrame()
{
// Goal: find the RequestFrame that contains the RequestPane that
// contains the HTML field containing this form submit button.
// Original solution: enumerate all Frames, choose the one containing
// text that matches the button name. This broke in the presence
// of HTML entities, and wasn't guaranteed to be unique anyway.
// Try 1: enumerate enclosing containers until an instance of
// RequestFrame is found. This works for standalone windows, but
// not for frames that open in a tab because they aren't actually
// part of the containment hierarchy in that case - the contentPane
// of the frame gets reparented into the main tabs.
// Try 2: enumerate containers to find the RequestPane, enumerate
// Frames to find the RequestFrame that owns it.
Container c = this.getContainer();
while ( c != null && !(c instanceof RequestPane) )
{
c = c.getParent();
}
Frame[] frames = Frame.getFrames();
for ( int i = 0; i < frames.length; ++i )
{
if ( frames[ i ] instanceof RequestFrame &&
((RequestFrame) frames[ i ]).mainDisplay == c )
{
return (RequestFrame) frames[ i ];
}
}
return null;
}
}
/**
* Utility method used to determine the GenericRequest that should be sent, given the appropriate location.
*/
public static final GenericRequest extractRequest( String location )
{
if ( location.contains( "pics.communityofloathing.com" ) )
{
FileUtilities.downloadImage( location );
location = location.substring( location.indexOf( "/" ) );
GenericRequest extractedRequest = new GenericRequest( location );
extractedRequest.responseCode = 200;
extractedRequest.responseText = "<html><img src=\"" + location + "\"></html>";
return extractedRequest;
}
return new GenericRequest( location );
}
/**
* Utility method used to deselect current selection and select a new
* one
*/
public static final void selectOption( final StringBuffer buffer, final String select, final String option )
{
// Find the correct select within the html
int start = buffer.indexOf( "<select name=" + select + ">" );
if ( start < 0)
{
return;
}
int end = buffer.indexOf( "</select>", start );
if ( end < 0)
{
return;
}
// Delete currently selected items
int index = start;
while (true)
{
index = buffer.indexOf( " selected", index );
if ( index == -1 || index >= end )
{
break;
}
buffer.delete( index, index + 9 );
}
// Select desired item
String selector = "value=" + option;
end = buffer.indexOf( "</select>", start );
index = buffer.indexOf( selector + ">", start );
if ( index == -1 || index >= end )
{
return;
}
buffer.insert( index + selector.length(), " selected" );
}
}