/**
* Copyright (c) 2005-2017, KoLmafia development team
* http://kolmafia.sourceforge.net/
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* [1] Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* [2] Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* [3] Neither the name "KoLmafia" nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package net.sourceforge.kolmafia.webui;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.kolmafia.AdventureResult;
import net.sourceforge.kolmafia.KoLCharacter;
import net.sourceforge.kolmafia.RequestThread;
import net.sourceforge.kolmafia.persistence.ConsumablesDatabase;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.request.AdventureRequest;
import net.sourceforge.kolmafia.request.BarrelRequest;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public abstract class BarrelDecorator
{
private static long unsmashedSquares = 0;
private static int unsmashedUser = -1;
private static final int B = 1; // flag for booze items found in barrels
private static final int RSHIFT = 8;
private static final int R = 1 << RSHIFT; // flag for restorative items
private static final int SSHIFT = 16;
private static final int S = 1 << SSHIFT; // flag for spleen/stat-gain items
private static final int MASK = 0xFF;
// AFH spading (see // http://www.alliancefromhell.com/forum/viewtopic.php?t=949)
// revealed that there are only 9 possible layouts for the barrels,
// each of which consists of 3 consecutive rows from a wrap-around list
// of 9 row layouts. I've repeated the first two rows at the end here,
// rather than doing modulo math on every access to this array.
private static final int[] BARRELROLL =
{
B,S,R, B,R,S, R,S,B, S,B,R, B,R,S, R,B,S, S,R,B, B,R,S, R,S,B,
B,S,R, B,R,S
};
public static final int barrelToQuad( int barrel ) // 1..36 => 0..8
{
int x = ( ( barrel - 1 ) % 6 ) / 2;
int y = ( barrel - 1 ) / 12;
return y * 3 + x;
}
public static final int quadToBarrel( int quad ) // 0..8 => 1..36
{ // Corresponding barrels are at N, N+1, N+6, and N+7
int x = quad % 3;
int y = quad / 3;
return y * 12 + x * 2 + 1;
}
private static int[] compute()
{
String layout = Preferences.getString("barrelLayout");
if ( layout.length() < 9 )
{
layout = "?????????";
}
int [] knowns = new int[9];
for ( int i = 0; i < 9; ++i )
{
int val = -1;
switch ( layout.charAt(i) )
{
case 'B':
case 'b':
val = B;
break;
case 'R':
case 'r':
val = R;
break;
case 'S':
case 's':
val = S;
}
knowns[i] = val;
}
int [] possibles = { 0,0,0, 0,0,0, 0,0,0 };
tryOnePattern:
for ( int pattern = 0; pattern <= BARRELROLL.length - 9; pattern += 3 )
{
for ( int i = 0; i < 9; ++i )
{
if ( (BARRELROLL[pattern + i] & knowns[i]) == 0 ) continue tryOnePattern;
}
// This pattern matches all the known results
for ( int i = 0; i < 9; ++i )
{
possibles[i] += BARRELROLL[pattern + i];
}
}
return possibles;
}
private static final Pattern UNSMASHED = Pattern.compile( "smash=(\\d+)&pwd=(\\w+)'><img src=[^>]*?(?:images.kingdomofloathing.com|/images)/otherimages/mountains/smallbarrel.gif'.*?>" );
public static final void decorate( final StringBuffer buffer )
{
if ( !Preferences.getBoolean( "relayShowSpoilers" ) )
{
return;
}
int [] possibles = compute();
Matcher m = UNSMASHED.matcher( buffer.toString() );
buffer.setLength( 0 );
while ( m.find() )
{
int square = StringUtilities.parseInt( m.group(1) );
int quad = barrelToQuad( square );
if ( quad < 0 || quad >= 9 )
{
continue; // shouldn't happen
}
int possible = possibles[quad];
if ( possible == 0 )
{
continue; // no patterns matched - shouldn't happen
}
int b = possible & MASK; // split into individual types
int r = (possible >> RSHIFT) & MASK;
int s = (possible >> SSHIFT) & MASK;
int total = b + r + s;
String tooltip = "An Unsmashed Barrel (1) " + (b * 100 / total) + "% booze, " +
(r * 100 / total) + "% restores, " + (s * 100 / total) + "% spleen";
// Now, generate a filename for the replacement image,
// from the letters b, r, & s. Letters grouped
// together have the same probability of appearing.
// Groups separated by dashes have descending
// probability as you go right. It turns out that only
// 19 such patterns can ever be generated:
//
// b, b-r, b-r-s, b-rs, b-s, br, brs, bs, r, r-b,
// r-bs, r-s, r-s-b, rs, s, s-b, s-b-r, s-br, and
// s-r.
StringBuffer filename = new StringBuffer();
while ( true )
{
int max = b;
if ( r > max )
{
max = r;
}
if ( s > max )
{
max = s;
}
if ( b == max )
{
filename.append( "b" );
b = 0;
}
if ( r == max )
{
filename.append( "r" );
r = 0;
}
if ( s == max )
{
filename.append( "s" );
s = 0;
}
if ( b + r + s == 0 )
{
break;
}
filename.append( "-" );
}
m.appendReplacement(
buffer,
"smash=$1&pwd=$2'>" + "<img src='/images/otherimages/barrels/" + filename + ".gif' " + "border=0 alt=\"" + tooltip + "\" title=\"" + tooltip + "\">" );
}
m.appendTail( buffer );
}
private static final Pattern SMASH_PATTERN = Pattern.compile( "smash=(\\d+)" );
public static final void beginSmash( String url )
{
Matcher m = SMASH_PATTERN.matcher( url );
int square = 0;
if ( m.find() )
{
square = StringUtilities.parseInt( m.group( 1 ) );
AdventureRequest.setNameOverride( "Mimic", square <= 12 ? "Mimic (Top 2 Rows)" :
square <= 24 ? "Mimic (Middle 2 Rows)" : "Mimic (Bottom 2 Rows)" );
}
Preferences.setInteger( "lastBarrelSmashed", square );
}
public static final void gainItem( AdventureResult item )
{
int square = Preferences.getInteger( "lastBarrelSmashed" );
if ( (square < 1) || (square > 36) )
{
return;
}
// Potential problem here: certain familiars and skills can
// cause unrelated items to be acquired during combat with a
// barrel mimic. The worst possibility appears to be Summon
// Hobo Underling -> Ask the Hobo For a Drink. I'm hoping that
// in all such cases, the unrelated item will be processed
// before the actual barrel item, and therefore the layout will
// end up in the correct state. It can still get confused if
// the player is defeated by the mimic (or runs away) after an
// unrelated item drop; but it's hard to imagine that happening
// to anyone who has the Summon Hobo Underling skill available.
// Also a possible problem: Oyster Egg Day drops!
char type;
String name = item.getName();
if ( ConsumablesDatabase.getInebriety( name ) > 0 )
{
type = 'B';
}
else if ( ConsumablesDatabase.getSpleenHit( name ) == 1 &&
!name.endsWith( "egg" ) )
{
type = 'S';
}
// Restorative items aren't nearly so easy to recognize!
else if ( name.startsWith( "Doc Galaktik" ) || name.endsWith( "seltzer" ) ||
name.endsWith( "Cola" ) || name.endsWith( "water" ) )
{
type = 'R';
}
else
{
return; // clover, or unrelated item - doesn't identify the barrel
}
StringBuffer layout = new StringBuffer( Preferences.getString( "barrelLayout" ) );
while ( layout.length() < 9 )
{
layout.append( '?' );
}
layout.setCharAt( barrelToQuad( square ), type );
Preferences.setString( "barrelLayout", layout.toString() );
}
public static final int recommendSquare()
{
if ( unsmashedSquares == 0L || unsmashedUser != KoLCharacter.getUserId() )
{ // need to visit the page to determine which barrels are available
RequestThread.postRequest( new BarrelRequest() );
}
int rows = Preferences.getInteger( "barrelGoal" );
int square = 0;
int[] possibles = compute();
if ( (rows & 1) != 0 )
{
square = recommendRow( possibles, 0 ) ;
}
if ( (rows & 2) != 0 && square == 0 )
{
square = recommendRow( possibles, 3 ) ;
}
if ( (rows & 4) != 0 && square == 0 )
{
square = recommendRow( possibles, 6 ) ;
}
return square;
}
private static final int recommendRow( final int[] possibles, final int startQuad )
{
int maxprob = -1;
int quad = -1;
for ( int i = startQuad; i < startQuad + 3; ++i )
{
int prob = possibles[ i ] & MASK;
if ( prob > maxprob )
{
maxprob = prob;
quad = i;
}
}
int square = quadToBarrel( quad );
if ( (unsmashedSquares & (1L << (square))) != 0L )
{
return square;
}
if ( (unsmashedSquares & (1L << (square + 1))) != 0L )
{
return square + 1;
}
if ( (unsmashedSquares & (1L << (square + 6))) != 0L )
{
return square + 6;
}
if ( (unsmashedSquares & (1L << (square + 7))) != 0L )
{
return square + 7;
}
return 0;
}
public static void parseResponse( String urlString, String responseText )
{
Matcher m = UNSMASHED.matcher( responseText );
unsmashedSquares = 1L; // make value non-zero, even if no barrels remain unsmashed
unsmashedUser = KoLCharacter.getUserId();
while ( m.find() )
{
int square = StringUtilities.parseInt( m.group(1) );
int quad = barrelToQuad( square );
if ( quad < 0 || quad >= 9 )
{
continue; // shouldn't happen
}
unsmashedSquares |= 1L << square;
}
}
}