/**
* Copyright (c) 2005-2017, KoLmafia development team
* http://kolmafia.sourceforge.net/
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* [1] Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* [2] Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* [3] Neither the name "KoLmafia" nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package net.sourceforge.kolmafia.request;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.kolmafia.CakeArenaManager;
import net.sourceforge.kolmafia.FamiliarData;
import net.sourceforge.kolmafia.KoLAdventure;
import net.sourceforge.kolmafia.KoLCharacter;
import net.sourceforge.kolmafia.KoLConstants.MafiaState;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.RequestLogger;
import net.sourceforge.kolmafia.objectpool.FamiliarPool;
import net.sourceforge.kolmafia.objectpool.ItemPool;
import net.sourceforge.kolmafia.session.ResultProcessor;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public class CakeArenaRequest
extends GenericRequest
{
private boolean isCompetition;
private int eventId;
private boolean ignoreCounters;
private String [] results;
private String suckage;
public CakeArenaRequest()
{
super( "arena.php" );
this.isCompetition = false;
}
public CakeArenaRequest( final int opponentId, final int eventId, final boolean ignoreCounters )
{
super( "arena.php" );
this.addFormField( "action", "go" );
this.addFormField( "whichopp", String.valueOf( opponentId ) );
this.addFormField( "event", String.valueOf( eventId ) );
this.isCompetition = true;
this.eventId = eventId;
this.ignoreCounters = ignoreCounters;
}
public CakeArenaRequest( final int opponentId, final int eventId )
{
this( opponentId, eventId, false );
}
@Override
public String toString()
{
return "Arena Battle";
}
@Override
public boolean stopForCounters()
{
return !this.ignoreCounters && super.stopForCounters();
}
@Override
public int getAdventuresUsed()
{
return this.isCompetition ? 1 : 0;
}
private static final Pattern EVENT_PATTERN = Pattern.compile( "event=(\\d*)" );
private static int getEvent( final String urlString )
{
Matcher matcher = EVENT_PATTERN.matcher( urlString );
return matcher.find() ? StringUtilities.parseInt( matcher.group(1) ) : -1;
}
private static final Pattern OPP_PATTERN = Pattern.compile( "whichopp=(\\d*)" );
private static int getOpponent( final String urlString )
{
Matcher matcher = OPP_PATTERN.matcher( urlString );
return matcher.find() ? StringUtilities.parseInt( matcher.group(1) ) : -1;
}
@Override
public void run()
{
if ( !this.isCompetition )
{
KoLmafia.updateDisplay( "Retrieving opponent list..." );
}
super.run();
}
@Override
public void processResults()
{
if ( this.responseText.indexOf( "You can't" ) != -1 ||
this.responseText.indexOf( "You shouldn't" ) != -1 ||
this.responseText.indexOf( "You don't" ) != -1 ||
this.responseText.indexOf( "You need" ) != -1 )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "Arena battles aborted!" );
return;
}
else if ( this.responseText.indexOf( "You're way too beaten" ) != -1 )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You're way too beaten up, Arena battles aborted!" );
return;
}
else if ( this.responseText.indexOf( "You're too drunk" ) != -1 )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You're too drunk, Arena battles aborted!" );
return;
}
CakeArenaRequest.parseResponse( this.getURLString(), this.responseText );
if ( this.isCompetition )
{
this.parseMatch();
}
else
{
KoLmafia.updateDisplay( "Opponent list retrieved." );
}
}
public static boolean parseResults( final String responseText )
{
// The Baby Bugged Bugbear might get a free familiar item. If
// so, it looks like you also get 3 lead necklaces. Nope.
//
// Congratulations on your %arenawins arena win. You've earned
// a prize from the Arena Goodies Sack!
FamiliarData familiar = KoLCharacter.getFamiliar();
if ( familiar.getId() == FamiliarPool.BUGBEAR &&
responseText.indexOf( "Congratulations on your %arenawins arena win" ) != -1 )
{
return ResultProcessor.processItem( ItemPool.BUGGED_BEANIE, 1 );
}
return ResultProcessor.processResults( false, responseText );
}
private static final Pattern WINCOUNT_PATTERN = Pattern.compile( "You have won (\\d*) time" );
private static final Pattern OPPONENT_PATTERN =
Pattern.compile( "<tr><td valign=center><input type=radio .*? name=whichopp value=(\\d+)>.*?<b>(.*?)</b> the (.*?)<br/?>(\\d*).*?</tr>" );
public static final void parseResponse( final String urlString, final String responseText )
{
if ( urlString.indexOf( "action=go" ) != -1 )
{
if ( responseText.indexOf( "You don't have enough Meat" ) != -1 )
{
return;
}
ResultProcessor.processMeat( -100 );
int eventId = CakeArenaRequest.getEvent( urlString );
String [] lines = CakeArenaRequest.contestLines( responseText );
// Log all the "special" lines between the start of the
// contest and the first result.
if ( lines != null )
{
for ( int i = 1; i < lines.length && !CakeArenaRequest.isContestResult( eventId, lines[ i ] ); ++i )
{
RequestLogger.updateSessionLog( CakeArenaRequest.prettyContestLine( lines[ i ] ) );
}
}
String message = CakeArenaRequest.resultMessage( responseText );
RequestLogger.updateSessionLog( message );
if ( !message.contains( "lost" ) )
{
KoLCharacter.setArenaWins( KoLCharacter.getArenaWins() + 1 );
}
return;
}
// Retrieve arena wins count
// "You have won 722 times. Only 8 wins left until your next
// prize!"
Matcher winMatcher = CakeArenaRequest.WINCOUNT_PATTERN.matcher( responseText );
if ( winMatcher.find() )
{
KoLCharacter.setArenaWins( StringUtilities.parseInt( winMatcher.group( 1 ) ) );
}
// Retrieve list of opponents
Matcher opponentMatcher = CakeArenaRequest.OPPONENT_PATTERN.matcher( responseText );
int lastMatchIndex = 0;
while ( opponentMatcher.find( lastMatchIndex ) )
{
lastMatchIndex = opponentMatcher.end() + 1;
int id = StringUtilities.parseInt( opponentMatcher.group( 1 ) );
String name = opponentMatcher.group( 2 );
String race = opponentMatcher.group( 3 );
int weight = StringUtilities.parseInt( opponentMatcher.group( 4 ) );
CakeArenaManager.registerOpponent( id, name, race, weight );
}
}
public final int earnedXP()
{
return CakeArenaRequest.earnedXP( this.responseText );
}
public static final Pattern WIN_PATTERN = Pattern.compile( "is the winner, and gains (\\d+) experience" );
private static final int earnedXP( final String responseText )
{
Matcher matcher = CakeArenaRequest.WIN_PATTERN.matcher( responseText );
return matcher.find() ? Integer.valueOf( matcher.group( 1 ) ).intValue() : 0;
}
// You enter Gorg against Pork Soda in an Ultimate Cage Match.
// Gorg is too busy being cute to fight very effectively.
// Pork Soda is too busy being cute to fight very effectively.
// Gorg struggles for 18 rounds, but is eventually knocked out.
// Gorg lost.
// or
// Gorg knocks Pork Soda out after 18 rounds.
// Gorg is the winner, and gains 2 experience!
// You enter Gonald against Citrus Maximus in a Scavenger Hunt.
// Gonald has no eyes, and so is not exactly the best choice for this event.
// Citrus Maximus has no eyes, and so is not exactly the best choice for this event.
// Gonald finds 12 items from the list.
// Citrus Maximus finds 12 items.
// Gonald is the winner, and gains 5 experience!
// <b>Gonald gains a pound!</b>
// Congratulations on your 290th arena win. You've earned a prize from the Arena Goodies Sack!
// You enter Ton against Mr. Joe Bangles in an Obstacle Course race.
// Ton is too short to get over most of the obstacles.
// Mr. Joe Bangles is too short to get over most of the obstacles.
// Ton makes it through the obstacle course in 199 seconds.
// Mr. Joe Bangles takes 200 seconds.
// Ton is the winner, and gains 5 experience!
// <b>Ton gains a pound!</b>
// Congratulations! Only 1 more win until you get a prize from the Arena Goodies Sack!
// You enter Trort against Pork Soda in a game of Hide and Seek.
// Trort buzzes incessantly, making it very difficult to remain concealed.
// Trort manages to stay hidden for 30 seconds.
// Pork Soda stays hidden for 47 seconds.
// Trort lost.
private static final Pattern CONTEST_PATTERN =
Pattern.compile( "<table><tr><td>(You enter.*?)</td></tr></table>" );
private static String [] contestLines( final String responseText )
{
Matcher contestMatcher = CakeArenaRequest.CONTEST_PATTERN.matcher( responseText );
if ( !contestMatcher.find() )
{
return null;
}
return StringUtilities.globalStringReplace( contestMatcher.group( 1 ), "<p><p>", "<p>(Missing \"this familiar sucks at this contest\" message)<p>" ).split( "<p>" );
}
private static String prettyContestLine( final String line )
{
return StringUtilities.globalStringReplace( line, "<br>", " / " );
}
private void parseMatch()
{
this.results = CakeArenaRequest.contestLines( this.responseText );
this.suckage = CakeArenaRequest.parseSuckage( this.results );
}
private static final Pattern ENTRY_PATTERN =
Pattern.compile( "You enter (.*?) against (.*?) in (?:a game of|an|a) (.*?)(?: race)?\\." );
private static boolean isContestResult( final int eventId, final String line )
{
switch ( eventId )
{
case 1:
// Gorg struggles for 18 rounds, but is eventually knocked out.
// Gorg knocks Pork Soda out after 18 rounds.
return ( line.contains( "is eventually knocked out" ) ||
( line.contains( "knocks" ) && line.contains( "out after" ) ) );
case 2:
// Gonald finds 12 items from the list.
return ( line.contains( "items from the list" ) );
case 3:
// Ton makes it through the obstacle course in 199 seconds.
return ( line.contains( "makes it through the obstacle course" ) );
case 4:
// Trort manages to stay hidden for 30 seconds.
return ( line.contains( "manages to stay hidden for" ) );
}
return false;
}
private static String parseSuckage( String [] lines )
{
// Look for special "this familiar sucks" message. Note the
// familiar can still win, even if such a message is present; a
// match in which both familiars suck is given to either
// contestant at random.
// The first line is always "You enter X against Y in Z
// The second line might be "your familiar sucks"
// ... or "the other familiar sucks"
// ... or "the first line of the contest result"
// Need at least 2 lines of results
if ( lines == null || lines.length < 2 )
{
return null;
}
// Line 1 describes the contest
String line1 = lines[ 0 ];
Matcher m = CakeArenaRequest.ENTRY_PATTERN.matcher( line1 );
if ( !m.find() )
{
return null;
}
int eventId = CakeArenaManager.eventNameToId( m.group( 3 ) );
String line2 = lines[ 1 ];
// If the second line is a contest result, neither familiar
// sucks at this event.
if ( CakeArenaRequest.isContestResult( eventId, line2 ) )
{
return null;
}
// "Familiar suckage" messages do not always include the name
// of the familiar. Fortunately, for the current opponents in
// the cake arena, they always do.
//
// *** If KoL ever adds arena opponents that have "special"
// *** suckage messages, this will need to change
String opponentName = m.group( 2 );
if ( line2.contains( opponentName ) )
{
// The other familiar sucks but not this one
return null;
}
return new String( CakeArenaRequest.prettyContestLine( line2 ) );
}
public final boolean badContest()
{
return this.suckage != null;
}
private static String resultMessage( final String responseText )
{
FamiliarData familiar = KoLCharacter.getFamiliar();
int xp = CakeArenaRequest.earnedXP( responseText );
if ( xp > 0 )
{
boolean gain = responseText.indexOf( "gains a pound" ) != -1;
familiar.addNonCombatExperience( xp );
return familiar.getName() + " gains " + xp + " experience" + ( gain ? " and a pound." : "." );
}
else
{
return familiar.getName() + " lost.";
}
}
public static final boolean registerRequest( final String urlString )
{
if ( !urlString.startsWith( "arena.php" ) )
{
return false;
}
if ( !urlString.contains( "action=go" ) )
{
return true;
}
FamiliarData familiar = KoLCharacter.getFamiliar();
if ( familiar == FamiliarData.NO_FAMILIAR )
{
return true;
}
if ( KoLCharacter.getAvailableMeat() < 100 )
{
return true;
}
int opponent = CakeArenaRequest.getOpponent( urlString );
if ( opponent < 0 )
{
return true;
}
int event = CakeArenaRequest.getEvent( urlString );
if ( event < 0 )
{
return true;
}
CakeArenaManager.ArenaOpponent ao = CakeArenaManager.getOpponent( opponent );
String eventName = CakeArenaManager.eventIdToName( event );
String message1 = "[" + KoLAdventure.getAdventureCount() + "] Cake-Shaped Arena";
String fam1 = familiar.getName() + ", the " + familiar.getModifiedWeight() + " lb. " + familiar.getRace();
String fam2 = ao == null ? ( "opponent #" + opponent ) : ao.getName() + ", the " + ao.getWeight() + " lb. " + ao.getRace();
String message2 = "Familiar: " + fam1;
String message3 = "Opponent: " + fam2;
String message4 = "Contest: " + eventName;
RequestLogger.printLine( "" );
RequestLogger.printLine( message1 );
RequestLogger.updateSessionLog();
RequestLogger.updateSessionLog( message1 );
RequestLogger.updateSessionLog( message2 );
RequestLogger.updateSessionLog( message3 );
RequestLogger.updateSessionLog( message4 );
return true;
}
}