/**
* 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.maximizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.kolmafia.AdventureResult;
import net.sourceforge.kolmafia.FamiliarData;
import net.sourceforge.kolmafia.KoLCharacter;
import net.sourceforge.kolmafia.KoLConstants.WeaponType;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.Modifiers;
import net.sourceforge.kolmafia.RequestLogger;
import net.sourceforge.kolmafia.SpecialOutfit;
import net.sourceforge.kolmafia.KoLConstants.MafiaState;
import net.sourceforge.kolmafia.textui.command.EdPieceCommand;
import net.sourceforge.kolmafia.textui.command.SnowsuitCommand;
import net.sourceforge.kolmafia.objectpool.EffectPool;
import net.sourceforge.kolmafia.objectpool.FamiliarPool;
import net.sourceforge.kolmafia.objectpool.ItemPool;
import net.sourceforge.kolmafia.persistence.EquipmentDatabase;
import net.sourceforge.kolmafia.persistence.FamiliarDatabase;
import net.sourceforge.kolmafia.persistence.ItemDatabase;
import net.sourceforge.kolmafia.persistence.ItemFinder;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.request.EquipmentRequest;
import net.sourceforge.kolmafia.request.StandardRequest;
import net.sourceforge.kolmafia.session.EquipmentManager;
import net.sourceforge.kolmafia.utilities.BooleanArray;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public class Evaluator
{
public boolean failed;
boolean exceeded;
private Evaluator tiebreaker;
private double[] weight, min, max;
private double totalMin, totalMax;
private int dump = 0;
private int clownosity = 0;
private int raveosity = 0;
private int beeosity = 2;
private int booleanMask, booleanValue;
private ArrayList<FamiliarData> familiars;
private ArrayList<FamiliarData> carriedFamiliars;
private int carriedFamiliarsNeeded = 0;
private boolean cardNeeded = false;
private boolean edPieceNeeded = false;
private String edPieceDecided = null;
private boolean snowsuitNeeded = false;
private final int[] slots = new int[ EquipmentManager.ALL_SLOTS ];
private String weaponType = null;
private int hands = 0;
int melee = 0; // +/-2 or higher: require, +/-1: disallow other type
private boolean effective = false;
private boolean requireClub = false;
private boolean requireShield = false;
private boolean requireUtensil = false;
private boolean requireKnife = false;
private boolean requireAccordion = false;
private boolean noTiebreaker = false;
private boolean current = !KoLCharacter.canInteract();
private HashSet<String> posOutfits, negOutfits;
private TreeSet<AdventureResult> posEquip, negEquip;
private static final String TIEBREAKER = "1 familiar weight, 1 familiar experience, 1 initiative, 5 exp, 1 item, 1 meat, 0.1 DA 1000 max, 1 DR, 0.5 all res, -10 mana cost, 1.0 mus, 0.5 mys, 1.0 mox, 1.5 mainstat, 1 HP, 1 MP, 1 weapon damage, 1 ranged damage, 1 spell damage, 1 cold damage, 1 hot damage, 1 sleaze damage, 1 spooky damage, 1 stench damage, 1 cold spell damage, 1 hot spell damage, 1 sleaze spell damage, 1 spooky spell damage, 1 stench spell damage, -1 fumble, 1 HP regen max, 3 MP regen max, 1 critical hit percent, 0.1 food drop, 0.1 booze drop, 0.1 hat drop, 0.1 weapon drop, 0.1 offhand drop, 0.1 shirt drop, 0.1 pants drop, 0.1 accessory drop, 1 DB combat damage, 0.1 sixgun damage";
private static final Pattern KEYWORD_PATTERN = Pattern.compile( "\\G\\s*(\\+|-|)([\\d.]*)\\s*(\"[^\"]+\"|(?:[^-+,0-9]|(?<! )[-+0-9])+),?\\s*" );
// Groups: 1=sign 2=weight 3=keyword
// Equipment slots, that aren't the primary slot of any item type,
// that are repurposed here (rather than making the array bigger).
// Watches have to be handled specially because only one can be
// used - otherwise, they'd fill up the list, leaving no room for
// any non-watches to put in the other two acc slots.
// 1-handed weapons have to be ranked separately due to the following
// possibility: all of your best weapons are 2-hand, but you've got
// a really good off-hand, better than any weapon. There would
// otherwise be no suitable weapons to go with that off-hand.
static final int OFFHAND_MELEE = EquipmentManager.ACCESSORY2;
static final int OFFHAND_RANGED = EquipmentManager.ACCESSORY3;
static final int WATCHES = EquipmentManager.STICKER2;
static final int WEAPON_1H = EquipmentManager.STICKER3;
// Slots starting with EquipmentManager.ALL_SLOTS are equipment
// for other familiars being considered.
private static int relevantSkill( String skill )
{
return KoLCharacter.hasSkill( skill ) ? 1 : 0;
}
private int relevantFamiliar( int id )
{
if ( KoLCharacter.getFamiliar().getId() == id )
{
return 1;
}
for ( int i = 0; i < this.familiars.size(); ++i )
{
if ( this.familiars.get( i ).getId() == id )
{
return 1;
}
}
return 0;
}
private int maxUseful( int slot )
{
switch ( slot )
{
case Evaluator.WEAPON_1H:
return 1 + relevantSkill( "Double-Fisted Skull Smashing" ) +
this.relevantFamiliar( FamiliarPool.HAND );
case EquipmentManager.ACCESSORY1:
return 3;
case EquipmentManager.FAMILIAR:
// Familiar items include weapons, hats and pants, make sure we have enough to consider for other slots
return 1 + this.relevantFamiliar( FamiliarPool.SCARECROW ) +
this.relevantFamiliar( FamiliarPool.HAND ) + this.relevantFamiliar( FamiliarPool.HATRACK );
}
return 1;
}
private static int toUseSlot( int slot )
{
int useSlot = slot;
switch ( slot )
{
case Evaluator.OFFHAND_MELEE:
case Evaluator.OFFHAND_RANGED:
useSlot = EquipmentManager.OFFHAND;
break;
case Evaluator.WATCHES:
useSlot = EquipmentManager.ACCESSORY1;
break;
case Evaluator.WEAPON_1H:
useSlot = EquipmentManager.WEAPON;
break;
}
return useSlot;
}
private Evaluator()
{
this.totalMin = Double.NEGATIVE_INFINITY;
this.totalMax = Double.POSITIVE_INFINITY;
}
Evaluator( String expr )
{
this();
Evaluator tiebreaker = new Evaluator();
this.tiebreaker = tiebreaker;
this.posOutfits = tiebreaker.posOutfits = new HashSet<String>();
this.negOutfits = tiebreaker.negOutfits = new HashSet<String>();
this.posEquip = tiebreaker.posEquip = new TreeSet<AdventureResult>();
this.negEquip = tiebreaker.negEquip = new TreeSet<AdventureResult>();
this.familiars = tiebreaker.familiars = new ArrayList<FamiliarData>();
this.carriedFamiliars = tiebreaker.carriedFamiliars = new ArrayList<FamiliarData>();
this.weight = new double[ Modifiers.DOUBLE_MODIFIERS ];
tiebreaker.weight = new double[ Modifiers.DOUBLE_MODIFIERS ];
tiebreaker.min = new double[ Modifiers.DOUBLE_MODIFIERS ];
tiebreaker.max = new double[ Modifiers.DOUBLE_MODIFIERS ];
Arrays.fill( tiebreaker.min, Double.NEGATIVE_INFINITY );
Arrays.fill( tiebreaker.max, Double.POSITIVE_INFINITY );
tiebreaker.parse( Evaluator.TIEBREAKER );
this.min = (double[]) tiebreaker.min.clone();
this.max = (double[]) tiebreaker.max.clone();
this.parse( expr );
}
private void parse( String expr )
{
expr = expr.trim().toLowerCase();
Matcher m = KEYWORD_PATTERN.matcher( expr );
boolean hadFamiliar = false;
boolean forceCurrent = false;
int pos = 0;
int index = -1;
int equipBeeosity = 0;
int outfitBeeosity = 0;
while ( pos < expr.length() )
{
if ( !m.find() )
{
KoLmafia.updateDisplay( MafiaState.ERROR,
"Unable to interpret: " + expr.substring( pos ) );
return;
}
pos = m.end();
double weight = StringUtilities.parseDouble(
m.end( 2 ) == m.start( 2 ) ? m.group( 1 ) + "1"
: m.group( 1 ) + m.group( 2 ) );
String keyword = m.group( 3 ).trim();
if ( keyword.startsWith( "\"" ) && keyword.endsWith( "\"" ) )
{
keyword = keyword.substring( 1, keyword.length() - 1 ).trim();
}
if ( keyword.equals( "min" ) )
{
if ( index >= 0 )
{
this.min[ index ] = weight;
}
else
{
this.totalMin = weight;
}
continue;
}
else if ( keyword.equals( "max" ) )
{
if ( index >= 0 )
{
this.max[ index ] = weight;
}
else
{
this.totalMax = weight;
}
continue;
}
else if ( keyword.equals( "dump" ) )
{
this.dump = (int) weight;
continue;
}
else if ( keyword.startsWith( "hand" ) )
{
this.hands = (int) weight;
if ( this.hands >= 2 )
{
//this.slots[ EquipmentManager.OFFHAND ] = -1;
}
continue;
}
else if ( keyword.startsWith( "tie" ) )
{
this.noTiebreaker = weight < 0.0;
continue;
}
else if ( keyword.startsWith( "current" ) )
{
this.current = weight > 0.0;
forceCurrent = true;
continue;
}
else if ( keyword.startsWith( "type " ) )
{
this.weaponType = keyword.substring( 5 ).trim();
continue;
}
else if ( keyword.equals( "club" ) )
{
this.requireClub = weight > 0.0;
continue;
}
else if ( keyword.equals( "shield" ) )
{
this.requireShield = weight > 0.0;
this.hands = 1;
continue;
}
else if ( keyword.equals( "utensil" ) )
{
this.requireUtensil = weight > 0.0;
continue;
}
else if ( keyword.equals( "knife" ) )
{
this.requireKnife = weight > 0.0;
continue;
}
else if ( keyword.equals( "accordion" ) )
{
this.requireAccordion = weight > 0.0;
continue;
}
else if ( keyword.equals( "melee" ) )
{
this.melee = (int) (weight * 2.0);
continue;
}
else if ( keyword.equals( "effective" ) )
{
this.effective = weight > 0.0;
continue;
}
else if ( keyword.equals( "empty" ) )
{
for ( int i = 0; i < EquipmentManager.ALL_SLOTS; ++i )
{
this.slots[ i ] += ((int) weight) *
( EquipmentManager.getEquipment( i ).equals( EquipmentRequest.UNEQUIP ) ? 1 : -1 );
}
continue;
}
else if ( keyword.equals( "clownosity" ) )
{
if ( m.end( 2 ) == m.start( 2 ) )
{
// No weight specified, so assume 4
this.clownosity = 4;
}
else
{
this.clownosity = (int) weight;
}
continue;
}
else if ( keyword.equals( "raveosity" ) )
{
if ( m.end( 2 ) == m.start( 2 ) )
{
// No weight specified, so assume 7
this.raveosity = 7;
}
else
{
this.raveosity = (int) weight;
}
continue;
}
else if ( keyword.equals( "beeosity" ) )
{
this.beeosity = (int) weight;
continue;
}
else if ( keyword.equals( "sea" ) )
{
this.booleanMask |= (1 << Modifiers.ADVENTURE_UNDERWATER) | (1 << Modifiers.UNDERWATER_FAMILIAR);
this.booleanValue |= (1 << Modifiers.ADVENTURE_UNDERWATER) | (1 << Modifiers.UNDERWATER_FAMILIAR);
index = -1;
// Force Crown of Ed to Fish
this.edPieceDecided = "fish";
continue;
}
else if ( keyword.startsWith( "equip " ) )
{
AdventureResult match = ItemFinder.getFirstMatchingItem(
keyword.substring( 6 ).trim(), ItemFinder.EQUIP_MATCH );
if ( match == null )
{
return;
}
if ( weight > 0.0 )
{
this.posEquip.add( match );
equipBeeosity += KoLCharacter.getBeeosity(
match.getName() );
}
else
{
this.negEquip.add( match );
}
continue;
}
else if ( keyword.startsWith( "outfit" ) )
{
keyword = keyword.substring( 6 ).trim();
if ( keyword.equals( "" ) )
{ // allow "+outfit" to mean "keep the current outfit on"
keyword = KoLCharacter.currentStringModifier( Modifiers.OUTFIT );
}
SpecialOutfit outfit = EquipmentManager.getMatchingOutfit( keyword );
if ( outfit == null || outfit.getOutfitId() <= 0 )
{
KoLmafia.updateDisplay( MafiaState.ERROR,
"Unknown or custom outfit: " + keyword );
return;
}
if ( weight > 0.0 )
{
this.posOutfits.add( outfit.getName() );
int bees = 0;
AdventureResult[] pieces = outfit.getPieces();
for ( int i = 0; i < pieces.length; ++i )
{
bees += KoLCharacter.getBeeosity( pieces[ i ].getName() );
}
outfitBeeosity = Math.max( outfitBeeosity, bees );
}
else
{
this.negOutfits.add( outfit.getName() );
}
continue;
}
else if ( keyword.startsWith( "switch " ) )
{
keyword = keyword.substring( 7 ).trim();
int id = FamiliarDatabase.getFamiliarId( keyword );
if ( id == -1 )
{
KoLmafia.updateDisplay( MafiaState.ERROR,
"Unknown familiar: " + keyword );
return;
}
if ( hadFamiliar && weight < 0.0 ) continue;
FamiliarData fam = KoLCharacter.findFamiliar( id );
if ( fam == null && weight > 1.0 )
{ // Allow a familiar to be faked for testing
fam = new FamiliarData( id );
fam.setWeight( (int) weight );
}
hadFamiliar = fam != null;
if ( fam != null && !fam.equals( KoLCharacter.getFamiliar() )
&& fam.canEquip() && !this.familiars.contains( fam ) )
{
this.familiars.add( fam );
}
continue;
}
int slot = EquipmentRequest.slotNumber( keyword );
if ( slot >= 0 && slot < EquipmentManager.ALL_SLOTS )
{
this.slots[ slot ] += (int) weight;
continue;
}
index = Modifiers.findName( keyword );
if ( index < 0 )
{ // try generic abbreviations
if ( keyword.endsWith( " res" ) )
{
keyword += "istance";
}
else if ( keyword.endsWith( " dmg" ) )
{
keyword = keyword.substring( 0, keyword.length() - 3 ) + "damage";
}
else if ( keyword.endsWith( " dmg percent" ) )
{
keyword = keyword.substring( 0, keyword.length() - 11 ) + "damage percent";
}
else if ( keyword.endsWith( " exp" ) )
{
keyword = keyword.substring( 0, keyword.length() - 3 ) + "experience";
}
index = Modifiers.findName( keyword );
}
if ( index >= 0 )
{ // exact match
}
else if ( keyword.equals( "all resistance" ) )
{
this.weight[ Modifiers.COLD_RESISTANCE ] = weight;
this.weight[ Modifiers.HOT_RESISTANCE ] = weight;
this.weight[ Modifiers.SLEAZE_RESISTANCE ] = weight;
this.weight[ Modifiers.SPOOKY_RESISTANCE ] = weight;
this.weight[ Modifiers.STENCH_RESISTANCE ] = weight;
continue;
}
else if ( keyword.equals( "elemental damage" ) )
{
this.weight[ Modifiers.COLD_DAMAGE ] = weight;
this.weight[ Modifiers.HOT_DAMAGE ] = weight;
this.weight[ Modifiers.SLEAZE_DAMAGE ] = weight;
this.weight[ Modifiers.SPOOKY_DAMAGE ] = weight;
this.weight[ Modifiers.STENCH_DAMAGE ] = weight;
continue;
}
else if ( keyword.equals( "hp regen" ) )
{
this.weight[ Modifiers.HP_REGEN_MIN ] = weight / 2;
this.weight[ Modifiers.HP_REGEN_MAX ] = weight / 2;
continue;
}
else if ( keyword.equals( "mp regen" ) )
{
this.weight[ Modifiers.MP_REGEN_MIN ] = weight / 2;
this.weight[ Modifiers.MP_REGEN_MAX ] = weight / 2;
continue;
}
else if ( keyword.equals( "init" ) )
{
index = Modifiers.INITIATIVE;
}
else if ( keyword.equals( "hp" ) )
{
index = Modifiers.HP;
}
else if ( keyword.equals( "mp" ) )
{
index = Modifiers.MP;
}
else if ( keyword.equals( "da" ) )
{
index = Modifiers.DAMAGE_ABSORPTION;
}
else if ( keyword.equals( "dr" ) )
{
index = Modifiers.DAMAGE_REDUCTION;
}
else if ( keyword.equals( "ml" ) )
{
index = Modifiers.MONSTER_LEVEL;
}
else if ( keyword.startsWith( "mus" ) )
{
index = Modifiers.MUS;
}
else if ( keyword.startsWith( "mys" ) )
{
index = Modifiers.MYS;
}
else if ( keyword.startsWith( "mox" ) )
{
index = Modifiers.MOX;
}
else if ( keyword.startsWith( "main" ) )
{
switch ( KoLCharacter.getPrimeIndex() )
{
case 0:
index = Modifiers.MUS;
break;
case 1:
index = Modifiers.MYS;
break;
case 2:
index = Modifiers.MOX;
break;
}
}
else if ( keyword.startsWith( "com" ) )
{
index = Modifiers.COMBAT_RATE;
if ( Modifiers.currentZone.equals( "The Sea" ) || Modifiers.currentLocation.equals( "The Sunken Party Yacht" ) )
{
this.weight[ Modifiers.UNDERWATER_COMBAT_RATE ] = weight;
}
}
else if ( keyword.startsWith( "item" ) )
{
index = Modifiers.ITEMDROP;
}
else if ( keyword.startsWith( "meat" ) )
{
index = Modifiers.MEATDROP;
}
else if ( keyword.startsWith( "adv" ) )
{
this.beeosity = 999;
index = Modifiers.ADVENTURES;
}
else if ( keyword.startsWith( "fites" ) )
{
this.beeosity = 999;
index = Modifiers.PVP_FIGHTS;
}
else if ( keyword.startsWith( "exp" ) )
{
index = Modifiers.EXPERIENCE;
}
else if ( keyword.startsWith( "crit" ) )
{
index = Modifiers.CRITICAL_PCT;
}
else if ( keyword.startsWith( "spell crit" ) )
{
index = Modifiers.SPELL_CRITICAL_PCT;
}
else if ( keyword.startsWith( "sprinkle" ) )
{
index = Modifiers.SPRINKLES;
}
else if ( keyword.equals( "ocrs" ) )
{
this.noTiebreaker = true;
this.beeosity = 999;
index = Modifiers.RANDOM_MONSTER_MODIFIERS;
}
if ( index >= 0 )
{
this.weight[ index ] = weight;
continue;
}
int boolIndex = Modifiers.findBooleanName( keyword );
if ( boolIndex >= 0 )
{
this.booleanMask |= 1 << boolIndex;
if ( weight > 0.0 )
{
this.booleanValue |= 1 << boolIndex;
}
index = -1; // min/max not valid at this point
continue;
}
KoLmafia.updateDisplay( MafiaState.ERROR,
"Unrecognized keyword: " + keyword );
return;
}
// If no tiebreaker, consider current unless -current specified
if ( !forceCurrent && this.noTiebreaker )
{
this.current = true;
}
this.beeosity = Math.max( Math.max( this.beeosity,
equipBeeosity ), outfitBeeosity );
// Make sure indirect sources have at least a little weight;
double fudge = this.weight[ Modifiers.EXPERIENCE ] * 0.0001f;
this.weight[ Modifiers.MONSTER_LEVEL ] += fudge;
this.weight[ Modifiers.MUS_EXPERIENCE ] += fudge;
this.weight[ Modifiers.MYS_EXPERIENCE ] += fudge;
this.weight[ Modifiers.MOX_EXPERIENCE ] += fudge;
this.weight[ Modifiers.MUS_EXPERIENCE_PCT ] += fudge;
this.weight[ Modifiers.MYS_EXPERIENCE_PCT ] += fudge;
this.weight[ Modifiers.MOX_EXPERIENCE_PCT ] += fudge;
this.weight[ Modifiers.VOLLEYBALL_WEIGHT ] += fudge;
this.weight[ Modifiers.SOMBRERO_WEIGHT ] += fudge;
this.weight[ Modifiers.VOLLEYBALL_EFFECTIVENESS ] += fudge;
this.weight[ Modifiers.SOMBRERO_EFFECTIVENESS ] += fudge;
this.weight[ Modifiers.SOMBRERO_BONUS ] += fudge;
fudge = this.weight[ Modifiers.ITEMDROP ] * 0.0001f;
this.weight[ Modifiers.FOODDROP ] += fudge;
this.weight[ Modifiers.BOOZEDROP ] += fudge;
this.weight[ Modifiers.HATDROP ] += fudge;
this.weight[ Modifiers.WEAPONDROP ] += fudge;
this.weight[ Modifiers.OFFHANDDROP ] += fudge;
this.weight[ Modifiers.SHIRTDROP ] += fudge;
this.weight[ Modifiers.PANTSDROP ] += fudge;
this.weight[ Modifiers.ACCESSORYDROP ] += fudge;
this.weight[ Modifiers.CANDYDROP ] += fudge;
this.weight[ Modifiers.GEARDROP ] += fudge;
this.weight[ Modifiers.FAIRY_WEIGHT ] += fudge;
this.weight[ Modifiers.FAIRY_EFFECTIVENESS ] += fudge;
this.weight[ Modifiers.SPORADIC_ITEMDROP ] += fudge;
this.weight[ Modifiers.PICKPOCKET_CHANCE ] += fudge;
fudge = this.weight[ Modifiers.MEATDROP ] * 0.0001f;
this.weight[ Modifiers.LEPRECHAUN_WEIGHT ] += fudge;
this.weight[ Modifiers.LEPRECHAUN_EFFECTIVENESS ] += fudge;
this.weight[ Modifiers.SPORADIC_MEATDROP ] += fudge;
this.weight[ Modifiers.MEAT_BONUS ] += fudge;
}
public double getScore( Modifiers mods )
{
this.failed = false;
this.exceeded = false;
int[] predicted = mods.predict();
double score = 0.0;
for ( int i = 0; i < Modifiers.DOUBLE_MODIFIERS; ++i )
{
double weight = this.weight[ i ];
double min = this.min[ i ];
if ( weight == 0.0 && min == Double.NEGATIVE_INFINITY ) continue;
double val = mods.get( i );
double max = this.max[ i ];
switch ( i )
{
case Modifiers.MUS:
val = predicted[ Modifiers.BUFFED_MUS ];
break;
case Modifiers.MYS:
val = predicted[ Modifiers.BUFFED_MYS ];
break;
case Modifiers.MOX:
val = predicted[ Modifiers.BUFFED_MOX ];
break;
case Modifiers.FAMILIAR_WEIGHT:
val += mods.get( Modifiers.HIDDEN_FAMILIAR_WEIGHT );
if ( mods.get( Modifiers.FAMILIAR_WEIGHT_PCT ) < 0.0 )
{
val *= 0.5f;
}
break;
case Modifiers.MANA_COST:
val += mods.get( Modifiers.STACKABLE_MANA_COST );
break;
case Modifiers.INITIATIVE:
val += Math.min( 0.0, mods.get( Modifiers.INITIATIVE_PENALTY ) );
break;
case Modifiers.MEATDROP:
val += 100.0 + Math.min( 0.0, mods.get( Modifiers.MEATDROP_PENALTY ) ) + mods.get( Modifiers.SPORADIC_MEATDROP ) + mods.get( Modifiers.MEAT_BONUS ) / 10000.0;
break;
case Modifiers.ITEMDROP:
val += 100.0 + Math.min( 0.0, mods.get( Modifiers.ITEMDROP_PENALTY ) ) + mods.get( Modifiers.SPORADIC_ITEMDROP );
break;
case Modifiers.HP:
val = predicted[ Modifiers.BUFFED_HP ];
break;
case Modifiers.MP:
val = predicted[ Modifiers.BUFFED_MP ];
break;
case Modifiers.WEAPON_DAMAGE:
// Incorrect - needs to estimate base damage
val += mods.get( Modifiers.WEAPON_DAMAGE_PCT );
break;
case Modifiers.RANGED_DAMAGE:
// Incorrect - needs to estimate base damage
val += mods.get( Modifiers.RANGED_DAMAGE_PCT );
break;
case Modifiers.SPELL_DAMAGE:
// Incorrect - base damage depends on spell used
val += mods.get( Modifiers.SPELL_DAMAGE_PCT );
break;
case Modifiers.COLD_RESISTANCE:
case Modifiers.HOT_RESISTANCE:
case Modifiers.SLEAZE_RESISTANCE:
case Modifiers.SPOOKY_RESISTANCE:
case Modifiers.STENCH_RESISTANCE:
if ( mods.getBoolean( i - Modifiers.COLD_RESISTANCE + Modifiers.COLD_IMMUNITY ) )
{
val = 100.0;
}
else if ( mods.getBoolean( i - Modifiers.COLD_RESISTANCE + Modifiers.COLD_VULNERABILITY ) )
{
val -= 100.0;
}
break;
case Modifiers.EXPERIENCE:
val = mods.get( Modifiers.MUS_EXPERIENCE + KoLCharacter.getPrimeIndex() );
break;
}
if ( val < min ) this.failed = true;
score += weight * Math.min( val, max );
}
// Add fudge factor for Rollover Effect
if ( mods.getString( Modifiers.ROLLOVER_EFFECT ).length() > 0 )
{
score += 0.01f;
}
if ( score < this.totalMin ) this.failed = true;
if ( score >= this.totalMax ) this.exceeded = true;
// special handling for -osity:
// The "weight" specified is actually the desired -osity.
// Allow partials to contribute to the score (1:1 ratio) up to the desired value.
// Similar to setting a max.
if ( this.clownosity > 0 )
{
int osity = mods.getBitmap( Modifiers.CLOWNOSITY );
score += Math.min( osity, this.clownosity );
if ( osity < this.clownosity )
this.failed = true;
}
if ( this.raveosity > 0 )
{
int osity = mods.getBitmap( Modifiers.RAVEOSITY );
score += Math.min( osity, this.raveosity );
if ( osity < this.raveosity )
this.failed = true;
}
if ( !this.failed && this.booleanMask != 0 &&
(mods.getRawBitmap( 0 ) & this.booleanMask) != this.booleanValue )
{
this.failed = true;
}
return score;
}
void checkEquipment( Modifiers mods, AdventureResult[] equipment,
int beeosity )
{
boolean outfitSatisfied = this.posOutfits.isEmpty();
boolean equipSatisfied = this.posEquip.isEmpty();
if ( !this.failed && !this.posEquip.isEmpty() )
{
equipSatisfied = true;
Iterator<AdventureResult> i = this.posEquip.iterator();
while ( i.hasNext() )
{
AdventureResult item = i.next();
if ( !KoLCharacter.hasEquipped( equipment, item ) )
{
equipSatisfied = false;
break;
}
}
}
if ( !this.failed )
{
String outfit = mods.getString( Modifiers.OUTFIT );
if ( this.negOutfits.contains( outfit ) )
{
this.failed = true;
}
else
{
outfitSatisfied = this.posOutfits.contains( outfit ) || this.posOutfits.isEmpty();
}
}
// negEquip is not checked, since enumerateEquipment should make it
// impossible for such items to be chosen.
if ( !outfitSatisfied || !equipSatisfied )
{
this.failed = true;
}
if ( beeosity > this.beeosity )
{
this.failed = true;
}
}
double getTiebreaker( Modifiers mods )
{
if ( this.noTiebreaker ) return 0.0;
return this.tiebreaker.getScore( mods );
}
int checkConstraints( Modifiers mods )
{
// Return value:
// -1: item violates a constraint, don't use it
// 0: item not relevant to any constraints
// 1: item meets a constraint, give it special handling
if ( mods == null ) return 0;
int bools = mods.getRawBitmap( 0 ) & this.booleanMask;
if ( (bools & ~this.booleanValue) != 0 ) return -1;
if ( bools != 0 ) return 1;
return 0;
}
public static boolean checkEffectConstraints( int effectId )
{
// Return true if effect cannot be gained due to current other effects or class
switch ( effectId )
{
case EffectPool.BARREL_CHESTED:
return KoLCharacter.getClassType() != KoLCharacter.SEAL_CLUBBER;
case EffectPool.BOON_OF_SHE_WHO_WAS:
return KoLCharacter.getBlessingType() != KoLCharacter.SHE_WHO_WAS_BLESSING || KoLCharacter.getBlessingLevel() == 4;
case EffectPool.BOON_OF_THE_STORM_TORTOISE:
return KoLCharacter.getBlessingType() != KoLCharacter.STORM_BLESSING || KoLCharacter.getBlessingLevel() == 4;
case EffectPool.BOON_OF_THE_WAR_SNAPPER:
return KoLCharacter.getBlessingType() != KoLCharacter.WAR_BLESSING || KoLCharacter.getBlessingLevel() == 4;
case EffectPool.AVATAR_OF_SHE_WHO_WAS:
return KoLCharacter.getBlessingType() != KoLCharacter.SHE_WHO_WAS_BLESSING || KoLCharacter.getBlessingLevel() != 3;
case EffectPool.AVATAR_OF_THE_STORM_TORTOISE:
return KoLCharacter.getBlessingType() != KoLCharacter.STORM_BLESSING || KoLCharacter.getBlessingLevel() != 3;
case EffectPool.AVATAR_OF_THE_WAR_SNAPPER:
return KoLCharacter.getBlessingType() != KoLCharacter.WAR_BLESSING || KoLCharacter.getBlessingLevel() != 3;
case EffectPool.BLESSING_OF_SHE_WHO_WAS:
return KoLCharacter.getClassType() != KoLCharacter.TURTLE_TAMER ||
KoLCharacter.getBlessingType() == KoLCharacter.SHE_WHO_WAS_BLESSING ||
KoLCharacter.getBlessingLevel() == -1 ||
KoLCharacter.getBlessingLevel() == 4;
case EffectPool.BLESSING_OF_THE_STORM_TORTOISE:
return KoLCharacter.getClassType() != KoLCharacter.TURTLE_TAMER ||
KoLCharacter.getBlessingType() == KoLCharacter.STORM_BLESSING ||
KoLCharacter.getBlessingLevel() == -1 ||
KoLCharacter.getBlessingLevel() == 4;
case EffectPool.BLESSING_OF_THE_WAR_SNAPPER:
return KoLCharacter.getClassType() != KoLCharacter.TURTLE_TAMER ||
KoLCharacter.getBlessingType() == KoLCharacter.WAR_BLESSING ||
KoLCharacter.getBlessingLevel() == -1 ||
KoLCharacter.getBlessingLevel() == 4;
case EffectPool.DISDAIN_OF_SHE_WHO_WAS:
case EffectPool.DISDAIN_OF_THE_STORM_TORTOISE:
case EffectPool.DISDAIN_OF_THE_WAR_SNAPPER:
return KoLCharacter.getClassType() == KoLCharacter.TURTLE_TAMER;
case EffectPool.BARREL_OF_LAUGHS:
return KoLCharacter.getClassType() != KoLCharacter.TURTLE_TAMER;
case EffectPool.FLIMSY_SHIELD_OF_THE_PASTALORD:
case EffectPool.BLOODY_POTATO_BITS:
case EffectPool.SLINKING_NOODLE_GLOB:
case EffectPool.WHISPERING_STRANDS:
case EffectPool.MACARONI_COATING:
case EffectPool.PENNE_FEDORA:
case EffectPool.PASTA_EYEBALL:
case EffectPool.SPICE_HAZE:
return KoLCharacter.getClassType() == KoLCharacter.PASTAMANCER;
case EffectPool.SHIELD_OF_THE_PASTALORD:
case EffectPool.PORK_BARREL:
return KoLCharacter.getClassType() != KoLCharacter.PASTAMANCER;
case EffectPool.BLOOD_SUGAR_SAUCE_MAGIC:
case EffectPool.SOULERSKATES:
case EffectPool.WARLOCK_WARSTOCK_WARBARREL:
return KoLCharacter.getClassType() != KoLCharacter.SAUCEROR;
case EffectPool.BLOOD_SUGAR_SAUCE_MAGIC_LITE:
return KoLCharacter.getClassType() == KoLCharacter.SAUCEROR;
case EffectPool.DOUBLE_BARRELED:
return KoLCharacter.getClassType() != KoLCharacter.DISCO_BANDIT;
case EffectPool.BEER_BARREL_POLKA:
return KoLCharacter.getClassType() != KoLCharacter.ACCORDION_THIEF;
case EffectPool.UNMUFFLED:
return !Preferences.getString( "peteMotorbikeMuffler" ).equals( "Extra-Loud Muffler" );
case EffectPool.MUFFLED:
return !Preferences.getString( "peteMotorbikeMuffler" ).equals( "Extra-Quiet Muffler" );
}
return false;
}
void enumerateEquipment( int equipLevel, int maxPrice, int priceLevel )
throws MaximizerInterruptedException
{
// Items automatically considered regardless of their score -
// synergies, hobo power, brimstone, etc.
ArrayList<CheckedItem>[] automatic = new ArrayList[ EquipmentManager.ALL_SLOTS + this.familiars.size() ];
// Items to be considered based on their score
ArrayList<CheckedItem>[] ranked = new ArrayList[ EquipmentManager.ALL_SLOTS + this.familiars.size() ];
for ( int i = ranked.length - 1; i >= 0; --i )
{
automatic[ i ] = new ArrayList<CheckedItem>();
ranked[ i ] = new ArrayList<CheckedItem>();
}
double nullScore = this.getScore( new Modifiers() );
BooleanArray usefulOutfits = new BooleanArray();
TreeMap<AdventureResult, AdventureResult> outfitPieces = new TreeMap<AdventureResult, AdventureResult>();
for ( int i = 1; i < EquipmentDatabase.normalOutfits.size(); ++i )
{
SpecialOutfit outfit = EquipmentDatabase.normalOutfits.get( i );
if ( outfit == null ) continue;
if ( this.negOutfits.contains( outfit.getName() ) ) continue;
if ( this.posOutfits.contains( outfit.getName() ) )
{
usefulOutfits.set( i, true );
continue;
}
Modifiers mods = Modifiers.getModifiers( "Outfit", outfit.getName() );
if ( mods == null ) continue;
switch ( this.checkConstraints( mods ) )
{
case -1:
continue;
case 0:
double delta = this.getScore( mods ) - nullScore;
if ( delta <= 0.0 ) continue;
break;
}
usefulOutfits.set( i, true );
}
int usefulSynergies = 0;
Iterator syn = Modifiers.getSynergies();
while ( syn.hasNext() )
{
Modifiers mods = Modifiers.getModifiers( "Synergy", (String) syn.next() );
int value = ((Integer) syn.next()).intValue();
if ( mods == null ) continue;
double delta = this.getScore( mods ) - nullScore;
if ( delta > 0.0 ) usefulSynergies |= value;
}
boolean hoboPowerUseful = false;
{
Modifiers mods = Modifiers.getModifiers( "MaxCat", "_hoboPower" );
if ( mods != null &&
this.getScore( mods ) - nullScore > 0.0 )
{
hoboPowerUseful = true;
}
}
boolean smithsnessUseful = false;
{
Modifiers mods = Modifiers.getModifiers( "MaxCat", "_smithsness" );
if ( mods != null &&
this.getScore( mods ) - nullScore > 0.0 )
{
smithsnessUseful = true;
}
}
boolean brimstoneUseful = false;
{
Modifiers mods = Modifiers.getModifiers( "MaxCat", "_brimstone" );
if ( mods != null &&
this.getScore( mods ) - nullScore > 0.0 )
{
brimstoneUseful = true;
}
}
boolean cloathingUseful = false;
{
Modifiers mods = Modifiers.getModifiers( "MaxCat", "_cloathing" );
if ( mods != null &&
this.getScore( mods ) - nullScore > 0.0 )
{
cloathingUseful = true;
}
}
boolean slimeHateUseful = false;
{
Modifiers mods = Modifiers.getModifiers( "MaxCat", "_slimeHate" );
if ( mods != null &&
this.getScore( mods ) - nullScore > 0.0 )
{
slimeHateUseful = true;
}
}
// This relies on the special sauce glove having a lower ID
// than any chefstaff.
boolean gloveAvailable = false;
int id = 0;
while ( (id = EquipmentDatabase.nextEquipmentItemId( id )) != -1 )
{
int slot = EquipmentManager.itemIdToEquipmentType( id );
if ( slot < 0 || slot >= EquipmentManager.ALL_SLOTS ) continue;
AdventureResult preItem = ItemPool.get( id, 1 );
String name = preItem.getName();
CheckedItem item = null;
if ( this.negEquip.contains( preItem ) ) continue;
if ( KoLCharacter.inBeecore() &&
KoLCharacter.getBeeosity( name ) > this.beeosity )
{ // too beechin' all by itself!
continue;
}
boolean famCanEquip = KoLCharacter.getFamiliar().canEquip( preItem );
if ( famCanEquip && slot != EquipmentManager.FAMILIAR )
{
// Modifiers when worn by Hatrack or Scarecrow
Modifiers familiarMods = new Modifiers();
int familiarId = KoLCharacter.getFamiliar().getId();
if ( ( familiarId == FamiliarPool.HATRACK && slot == EquipmentManager.HAT ) ||
( familiarId == FamiliarPool.SCARECROW && slot == EquipmentManager.PANTS ) )
{
familiarMods.applyFamiliarModifiers( KoLCharacter.getFamiliar(), preItem );
}
else
// Normal item modifiers when used by Disembodied Hand
{
familiarMods = Modifiers.getItemModifiers( id );
if ( familiarMods == null ) // no enchantments
{
familiarMods = new Modifiers();
}
}
item = new CheckedItem( id, equipLevel, maxPrice, priceLevel );
switch ( this.checkConstraints( familiarMods ) )
{
case -1:
continue;
case 1:
item.automaticFlag = true;
}
if ( item.getCount() != 0 &&
( this.getScore( familiarMods ) - nullScore > 0.0 || item.automaticFlag == true ) )
{
ranked[ EquipmentManager.FAMILIAR ].add( item );
}
}
for ( int f = this.familiars.size() - 1; f >= 0; --f )
{
FamiliarData fam = this.familiars.get( f );
if ( !fam.canEquip( preItem ) ) continue;
// Modifiers when worn by Hatrack or Scarecrow
Modifiers familiarMods = new Modifiers();
int familiarId = fam.getId();
if ( ( familiarId == FamiliarPool.HATRACK && slot == EquipmentManager.HAT ) ||
( familiarId == FamiliarPool.SCARECROW && slot == EquipmentManager.PANTS ) )
{
familiarMods.applyFamiliarModifiers( fam, preItem );
}
else
// Normal item modifiers when used by Disembodied Hand
{
familiarMods = Modifiers.getItemModifiers( id );
if ( familiarMods == null ) // no enchantments
{
familiarMods = new Modifiers();
}
}
if ( item == null )
{
item = new CheckedItem( id, equipLevel, maxPrice, priceLevel );
}
switch ( this.checkConstraints( familiarMods ) )
{
case -1:
continue;
case 1:
item.automaticFlag = true;
}
if ( item.getCount() != 0 &&
( this.getScore( familiarMods ) - nullScore > 0.0 || item.automaticFlag == true ) )
{
ranked[ EquipmentManager.ALL_SLOTS + f ].add( item );
}
}
if ( !EquipmentManager.canEquip( id ) ) continue;
if ( item == null )
{
item = new CheckedItem( id, equipLevel, maxPrice, priceLevel );
}
if ( item.getCount() == 0 )
{
continue;
}
int auxSlot = -1;
gotItem:
{
switch ( slot )
{
case EquipmentManager.FAMILIAR:
if ( !famCanEquip ) continue;
break;
case EquipmentManager.WEAPON:
int hands = EquipmentDatabase.getHands( id );
if ( this.hands == 1 && hands != 1 )
{
continue;
}
if ( this.hands > 1 && hands < this.hands )
{
continue;
}
WeaponType weaponType = EquipmentDatabase.getWeaponType( id );
if ( this.melee > 0 && weaponType != WeaponType.MELEE )
{
continue;
}
if ( this.melee < 0 && weaponType != WeaponType.RANGED )
{
continue;
}
if ( this.requireClub && !EquipmentDatabase.isClub( id ) )
{
continue;
}
if ( this.requireUtensil && !EquipmentDatabase.isUtensil( id ) )
{
continue;
}
if ( this.requireKnife && !EquipmentDatabase.isKnife( id ) )
{
continue;
}
if ( this.requireAccordion && !EquipmentDatabase.isAccordion( id ) )
{
continue;
}
if ( this.effective )
{
if ( KoLCharacter.getAdjustedMoxie() >= KoLCharacter.getAdjustedMuscle() &&
weaponType != WeaponType.RANGED &&
( !EquipmentDatabase.isKnife( id ) || !KoLCharacter.hasSkill( "Tricky Knifework" ) ) )
{
continue;
}
if ( KoLCharacter.getAdjustedMoxie() < KoLCharacter.getAdjustedMuscle() &&
weaponType != WeaponType.MELEE )
{
continue;
}
}
String type = EquipmentDatabase.getItemType( id );
if ( this.weaponType != null && type.indexOf( this.weaponType ) == -1 )
{
continue;
}
if ( hands == 1 )
{
slot = Evaluator.WEAPON_1H;
if ( type.equals( "chefstaff" ) )
{ // Don't allow chefstaves to displace other
// 1H weapons from the shortlist if you can't
// equip them anyway.
if ( !KoLCharacter.hasSkill( "Spirit of Rigatoni" ) &&
!KoLCharacter.isJarlsberg() &&
!( KoLCharacter.getClassType().equals( KoLCharacter.SAUCEROR ) && gloveAvailable ) )
{
continue;
}
// In any case, don't put this in an aux slot.
}
else if ( !this.requireShield && !EquipmentDatabase.isMainhandOnly( id ) )
{
switch ( weaponType )
{
case MELEE:
auxSlot = Evaluator.OFFHAND_MELEE;
break;
case RANGED:
auxSlot = Evaluator.OFFHAND_RANGED;
break;
case NONE:
default:
break;
}
}
}
break;
case EquipmentManager.OFFHAND:
if ( this.requireShield && !EquipmentDatabase.isShield( id ) )
{
continue;
}
if ( hoboPowerUseful && name.startsWith( "Hodgman's" ) )
{
Modifiers.hoboPower = 100.0;
item.automaticFlag = true;
}
break;
case EquipmentManager.ACCESSORY1:
if ( id == ItemPool.SPECIAL_SAUCE_GLOVE &&
KoLCharacter.getClassType().equals( KoLCharacter.SAUCEROR )
&& !KoLCharacter.hasSkill( "Spirit of Rigatoni" ) )
{
item.validate( maxPrice, priceLevel );
if ( item.getCount() == 0 )
{
continue;
}
item.automaticFlag = true;
gloveAvailable = true;
break gotItem;
}
break;
}
// Some items can only be equipped in certain paths in hardcore
// Will only affect characters who buy items for other paths whilst in run
if ( KoLCharacter.isHardcore() )
{
switch ( id )
{
case ItemPool.BORIS_HELM:
case ItemPool.BORIS_HELM_ASKEW:
if ( !KoLCharacter.isAvatarOfBoris() )
{
continue;
}
break;
case ItemPool.RIGHT_BEAR_ARM:
case ItemPool.LEFT_BEAR_ARM:
if ( !KoLCharacter.isZombieMaster() )
{
continue;
}
break;
case ItemPool.JARLS_PAN:
case ItemPool.JARLS_COSMIC_PAN:
if ( !KoLCharacter.isJarlsberg() )
{
continue;
}
break;
case ItemPool.FOLDER_HOLDER:
if ( !KoLCharacter.inHighschool() )
{
continue;
}
break;
case ItemPool.PETE_JACKET:
case ItemPool.PETE_JACKET_COLLAR:
if ( !KoLCharacter.isSneakyPete() )
{
continue;
}
break;
case ItemPool.THORS_PLIERS:
if ( !KoLCharacter.inRaincore() )
{
continue;
}
break;
case ItemPool.CROWN_OF_ED:
if ( !KoLCharacter.isEd() )
{
continue;
}
break;
default:
break;
}
}
if ( usefulOutfits.get( EquipmentDatabase.getOutfitWithItem( id ) ) )
{
item.validate( maxPrice, priceLevel );
if ( item.getCount() == 0 )
{
continue;
}
outfitPieces.put( item, item );
}
if ( KoLCharacter.hasEquipped( item ) && this.current )
{ // Make sure the current item in each slot is considered
// for keeping, unless it's actively harmful, unless -current
// option is used
item.automaticFlag = true;
}
Modifiers mods = Modifiers.getItemModifiers( id );
if ( mods == null ) // no enchantments
{
mods = new Modifiers();
}
boolean wrongClass = false;
String classType = mods.getString( Modifiers.CLASS );
if ( classType != "" && !classType.equals( KoLCharacter.getClassType() ) )
{
wrongClass = true;
}
if ( mods.getBoolean( Modifiers.SINGLE ) )
{
item.singleFlag = true;
}
// If you have a familiar carrier, we'll need to check 1 or 2 Familiars best carried
// unless you specified not to change them
if ( ( ( id == ItemPool.HATSEAT && this.slots[ EquipmentManager.CROWNOFTHRONES ] >= 0 ) ||
( id == ItemPool.BUDDY_BJORN && this.slots[ EquipmentManager.BUDDYBJORN ] >= 0 ) ) &&
!KoLCharacter.isSneakyPete() && !KoLCharacter.inAxecore() && !KoLCharacter.isJarlsberg() )
{
this.carriedFamiliarsNeeded++;
}
if ( id == ItemPool.CARD_SLEEVE && this.slots[ EquipmentManager.CARDSLEEVE ] >= 0 )
{
this.cardNeeded = true;
}
if ( id == ItemPool.CROWN_OF_ED && this.slots[ EquipmentManager.HAT ] >= 0 )
{
this.edPieceNeeded = true;
}
if ( id == ItemPool.SNOW_SUIT && this.slots[ EquipmentManager.FAMILIAR ] >= 0 )
{
this.snowsuitNeeded = true;
}
if ( mods.getBoolean( Modifiers.NONSTACKABLE_WATCH ) )
{
slot = Evaluator.WATCHES;
}
if ( this.posEquip.contains( item ) )
{
item.automaticFlag = true;
item.requiredFlag = true;
break gotItem;
}
switch ( this.checkConstraints( mods ) )
{
case -1:
continue;
case 1:
item.automaticFlag = true;
break gotItem;
}
if ( ( hoboPowerUseful &&
mods.get( Modifiers.HOBO_POWER ) > 0.0 ) ||
( smithsnessUseful && !wrongClass &&
mods.get( Modifiers.SMITHSNESS ) > 0.0 ) ||
( brimstoneUseful &&
mods.getRawBitmap( Modifiers.BRIMSTONE ) != 0 ) ||
( cloathingUseful &&
mods.getRawBitmap( Modifiers.CLOATHING ) != 0 ) ||
( slimeHateUseful &&
mods.get( Modifiers.SLIME_HATES_IT ) > 0.0 ) ||
( this.clownosity > 0 &&
mods.getRawBitmap( Modifiers.CLOWNOSITY ) != 0 ) ||
( this.raveosity > 0 &&
mods.getRawBitmap( Modifiers.RAVEOSITY ) != 0 ) ||
( (mods.getRawBitmap( Modifiers.SYNERGETIC )
& usefulSynergies) != 0 ) )
{
item.automaticFlag = true;
break gotItem;
}
// Always carry through items with changeable contents to speculation, but don't force them to go further
if ( ( id == ItemPool.HATSEAT || id == ItemPool.BUDDY_BJORN ) &&
!KoLCharacter.isSneakyPete() && !KoLCharacter.inAxecore() && !KoLCharacter.isJarlsberg() )
{
break gotItem;
}
if ( id == ItemPool.CARD_SLEEVE )
{
break gotItem;
}
if ( id == ItemPool.CROWN_OF_ED )
{
if ( this.edPieceDecided != null )
{
// Currently this means +sea specified, so meets that requirement
item.automaticFlag = true;
}
break gotItem;
}
String intrinsic = mods.getString( Modifiers.INTRINSIC_EFFECT );
if ( intrinsic.length() > 0 )
{
Modifiers newMods = new Modifiers();
newMods.add( mods );
newMods.add( Modifiers.getModifiers( "Effect", intrinsic ) );
mods = newMods;
}
double delta = this.getScore( mods ) - nullScore;
if ( delta < 0.0 ) continue;
if ( delta == 0.0 )
{
if ( KoLCharacter.hasEquipped( item ) && this.current ) break gotItem;
if ( item.initial == 0 ) continue;
if ( item.automaticFlag ) continue;
}
if ( mods.getBoolean( Modifiers.UNARMED ) ||
mods.getRawBitmap( Modifiers.MUTEX ) != 0 )
{ // This item may turn out to be unequippable, so don't
// count it towards the shortlist length.
item.conditionalFlag = true;
}
}
// "break gotItem" goes here
ranked[ slot ].add( item );
if ( auxSlot != -1 ) ranked[ auxSlot ].add( item );
}
// Get best Familiars for Crown of Thrones and Buddy Bjorn
// Assume current ones are best if in use
FamiliarData bestCarriedFamiliar = FamiliarData.NO_FAMILIAR;
FamiliarData secondBestCarriedFamiliar = FamiliarData.NO_FAMILIAR;
FamiliarData useBjornFamiliar = FamiliarData.NO_FAMILIAR;
FamiliarData useCrownFamiliar = FamiliarData.NO_FAMILIAR;
if ( KoLCharacter.hasEquipped( ItemPool.BUDDY_BJORN, EquipmentManager.CONTAINER ) )
{
// If we're not allowed to change the current familiar, blacklist it
if ( this.slots[ EquipmentManager.BUDDYBJORN ] < 0 )
{
useBjornFamiliar = KoLCharacter.getBjorned();
}
else
{
bestCarriedFamiliar = KoLCharacter.getBjorned();
}
}
if ( KoLCharacter.hasEquipped( ItemPool.HATSEAT, EquipmentManager.HAT ) )
{
// If we're not allowed to change the current familiar, add it
if ( this.slots[ EquipmentManager.CROWNOFTHRONES ] < 0 )
{
useCrownFamiliar = KoLCharacter.getEnthroned();
}
else
{
secondBestCarriedFamiliar = KoLCharacter.getEnthroned();
}
}
if ( bestCarriedFamiliar == FamiliarData.NO_FAMILIAR && !(secondBestCarriedFamiliar == FamiliarData.NO_FAMILIAR ) )
{
bestCarriedFamiliar = secondBestCarriedFamiliar;
secondBestCarriedFamiliar = FamiliarData.NO_FAMILIAR;
}
if ( secondBestCarriedFamiliar != FamiliarData.NO_FAMILIAR )
{
// Make sure best is better than secondBest !
MaximizerSpeculation best = new MaximizerSpeculation();
MaximizerSpeculation secondBest = new MaximizerSpeculation();
CheckedItem item = new CheckedItem( ItemPool.HATSEAT, equipLevel, maxPrice, priceLevel );
best.attachment = secondBest.attachment = item;
Arrays.fill( best.equipment, EquipmentRequest.UNEQUIP );
Arrays.fill( secondBest.equipment, EquipmentRequest.UNEQUIP );
best.equipment[ EquipmentManager.HAT ] = secondBest.equipment[ EquipmentManager.HAT ] = item;
best.setEnthroned( bestCarriedFamiliar );
secondBest.setEnthroned( secondBestCarriedFamiliar );
if ( secondBest.compareTo( best ) > 0 )
{
FamiliarData temp = bestCarriedFamiliar;
bestCarriedFamiliar = secondBestCarriedFamiliar;
secondBestCarriedFamiliar = temp;
}
}
if ( this.carriedFamiliarsNeeded > 0 )
{
MaximizerSpeculation best = new MaximizerSpeculation();
MaximizerSpeculation secondBest = new MaximizerSpeculation();
CheckedItem item = new CheckedItem( ItemPool.HATSEAT, equipLevel, maxPrice, priceLevel );
best.attachment = secondBest.attachment = item;
Arrays.fill( best.equipment, EquipmentRequest.UNEQUIP );
Arrays.fill( secondBest.equipment, EquipmentRequest.UNEQUIP );
best.equipment[ EquipmentManager.HAT ] = secondBest.equipment[ EquipmentManager.HAT ] = item;
best.setEnthroned( bestCarriedFamiliar );
secondBest.setEnthroned( secondBestCarriedFamiliar );
// Check each familiar in hat to see if they are worthwhile
List familiarList = KoLCharacter.getFamiliarList();
for ( int f = 0; f < familiarList.size(); ++f )
{
FamiliarData familiar = (FamiliarData) familiarList.get( f );
if ( familiar != null && familiar != FamiliarData.NO_FAMILIAR && familiar.canCarry() && StandardRequest.isAllowed( "Familiars", familiar.getRace() ) &&
!familiar.equals( KoLCharacter.getFamiliar() ) && !this.carriedFamiliars.contains( familiar ) &&
!familiar.equals( useCrownFamiliar ) && !familiar.equals( useBjornFamiliar ) && !familiar.equals( bestCarriedFamiliar ) &&
!( KoLCharacter.inBeecore() && KoLCharacter.getBeeosity( familiar.getRace() ) > 0 ) )
{
MaximizerSpeculation spec = new MaximizerSpeculation();
spec.attachment = item;
Arrays.fill( spec.equipment, EquipmentRequest.UNEQUIP );
spec.equipment[ EquipmentManager.HAT ] = item;
spec.setEnthroned( familiar );
spec.setUnscored();
if ( spec.compareTo( best ) > 0 )
{
secondBest = (MaximizerSpeculation) best.clone();
best = (MaximizerSpeculation) spec.clone();
secondBestCarriedFamiliar = bestCarriedFamiliar;
bestCarriedFamiliar = familiar;
}
else if ( spec.compareTo( secondBest ) > 0 )
{
secondBest = (MaximizerSpeculation) spec.clone();
secondBestCarriedFamiliar = familiar;
}
}
}
this.carriedFamiliars.add( bestCarriedFamiliar );
if ( this.carriedFamiliarsNeeded > 1 )
{
this.carriedFamiliars.add( secondBestCarriedFamiliar );
}
}
// Get best Card for Card Sleeve
CheckedItem bestCard = null;
AdventureResult useCard = null;
if ( this.cardNeeded )
{
MaximizerSpeculation best = new MaximizerSpeculation();
Arrays.fill( best.equipment, EquipmentRequest.UNEQUIP );
// Check each card in sleeve to see if they are worthwhile
for ( int c = 4967; c <= 5007; c++ )
{
CheckedItem card = new CheckedItem( c, equipLevel, maxPrice, priceLevel );
AdventureResult equippedCard = EquipmentManager.getEquipment( EquipmentManager.CARDSLEEVE );
if ( card.getCount() > 0 || ( equippedCard != null && c == equippedCard.getItemId() ) )
{
MaximizerSpeculation spec = new MaximizerSpeculation();
CheckedItem sleeve = new CheckedItem( ItemPool.CARD_SLEEVE, equipLevel, maxPrice, priceLevel );
spec.attachment = sleeve;
Arrays.fill( spec.equipment, EquipmentRequest.UNEQUIP );
spec.equipment[ EquipmentManager.OFFHAND ] = sleeve;
spec.equipment[ EquipmentManager.CARDSLEEVE ] = card;
if ( spec.compareTo( best ) > 0 )
{
best = (MaximizerSpeculation) spec.clone();
bestCard = card;
}
}
}
}
String bestEdPiece = null;
if ( this.edPieceNeeded )
{
// Is Crown of Ed forced to a particular choice ?
if ( this.edPieceDecided != null )
{
bestEdPiece = this.edPieceDecided;
}
else
{
// Assume best is current edPiece
MaximizerSpeculation best = new MaximizerSpeculation();
CheckedItem edPiece = new CheckedItem( ItemPool.CROWN_OF_ED, equipLevel, maxPrice, priceLevel );
best.attachment = edPiece;
Arrays.fill( best.equipment, EquipmentRequest.UNEQUIP );
bestEdPiece = Preferences.getString( "edPiece" );
best.equipment[ EquipmentManager.HAT ] = edPiece;
best.setEdPiece( bestEdPiece );
// Check each animal in Crown of Ed to see if they are worthwhile
for ( String[] ANIMAL : EdPieceCommand.ANIMAL )
{
String animal = ANIMAL[0];
if ( animal.equals( bestEdPiece ) )
{
// Don't bother if we've already done it for best
continue;
}
MaximizerSpeculation spec = new MaximizerSpeculation();
spec.attachment = edPiece;
Arrays.fill( spec.equipment, EquipmentRequest.UNEQUIP );
spec.equipment[ EquipmentManager.HAT ] = edPiece;
spec.setEdPiece( animal );
if ( spec.compareTo( best ) > 0 )
{
best = (MaximizerSpeculation) spec.clone();
bestEdPiece = animal;
}
}
}
}
String bestSnowsuit = null;
if ( this.snowsuitNeeded )
{
// Assume best is current Snowsuit
MaximizerSpeculation best = new MaximizerSpeculation();
CheckedItem snowsuit = new CheckedItem( ItemPool.SNOW_SUIT, equipLevel, maxPrice, priceLevel );
best.attachment = snowsuit;
Arrays.fill( best.equipment, EquipmentRequest.UNEQUIP );
bestSnowsuit = Preferences.getString( "snowsuit" );
best.equipment[ EquipmentManager.FAMILIAR ] = snowsuit;
best.setSnowsuit( bestSnowsuit );
// Check each decoration in Snowsuit to see if they are worthwhile
for ( int i = 0; i < SnowsuitCommand.DECORATION.length; i++ )
{
String decoration = SnowsuitCommand.DECORATION[ i ][ 0 ];
if ( decoration.equals( bestSnowsuit ) )
{
// Don't bother if we've already done it for best
continue;
}
MaximizerSpeculation spec = new MaximizerSpeculation();
spec.attachment = snowsuit;
Arrays.fill( spec.equipment, EquipmentRequest.UNEQUIP );
spec.equipment[ EquipmentManager.FAMILIAR ] = snowsuit;
spec.setSnowsuit( decoration );
if ( spec.compareTo( best ) > 0 )
{
best = (MaximizerSpeculation) spec.clone();
bestSnowsuit = decoration;
}
}
}
ArrayList<MaximizerSpeculation>[] speculationList = new ArrayList[ ranked.length ];
for ( int i = ranked.length - 1; i >= 0; --i )
{
speculationList[ i ] = new ArrayList<MaximizerSpeculation>();
}
for ( int slot = 0; slot < ranked.length; ++slot )
{
ArrayList<CheckedItem> checkedItemList = ranked[ slot ];
// If we currently have nothing equipped, also consider leaving nothing equipped
if ( EquipmentManager.getEquipment( Evaluator.toUseSlot( slot ) ) == EquipmentRequest.UNEQUIP )
{
ranked[ slot ].add( new CheckedItem( 0, equipLevel, maxPrice, priceLevel ) );
}
for ( CheckedItem item : checkedItemList )
{
MaximizerSpeculation spec = new MaximizerSpeculation();
spec.attachment = item;
int useSlot = Evaluator.toUseSlot( slot );
if ( slot >= EquipmentManager.ALL_SLOTS )
{
spec.setFamiliar( this.familiars.get(
slot - EquipmentManager.ALL_SLOTS ) );
useSlot = EquipmentManager.FAMILIAR;
}
Arrays.fill( spec.equipment, EquipmentRequest.UNEQUIP );
spec.equipment[ useSlot ] = item;
int itemId = item.getItemId();
if ( itemId == ItemPool.HATSEAT )
{
if ( this.slots[ EquipmentManager.CROWNOFTHRONES ] < 0 )
{
spec.setEnthroned( useCrownFamiliar );
}
else if ( this.carriedFamiliarsNeeded > 1 )
{
item.automaticFlag = true;
spec.setEnthroned( secondBestCarriedFamiliar );
}
else
{
spec.setEnthroned( bestCarriedFamiliar );
}
}
else if ( itemId == ItemPool.BUDDY_BJORN )
{
if ( this.slots[ EquipmentManager.BUDDYBJORN ] < 0 )
{
spec.setBjorned( useBjornFamiliar );
}
else if ( this.carriedFamiliarsNeeded > 1 )
{
item.automaticFlag = true;
spec.setBjorned( secondBestCarriedFamiliar );
}
else
{
spec.setBjorned( bestCarriedFamiliar );
}
}
else if ( EquipmentManager.isStickerWeapon( item ) )
{
MaximizerSpeculation current = new MaximizerSpeculation();
spec.equipment[ EquipmentManager.STICKER1 ] = current.equipment[ EquipmentManager.STICKER1 ];
spec.equipment[ EquipmentManager.STICKER2 ] = current.equipment[ EquipmentManager.STICKER2 ];
spec.equipment[ EquipmentManager.STICKER3 ] = current.equipment[ EquipmentManager.STICKER3 ];
}
else if ( itemId == ItemPool.CARD_SLEEVE )
{
MaximizerSpeculation current = new MaximizerSpeculation();
if ( bestCard != null )
{
spec.equipment[ EquipmentManager.CARDSLEEVE ] = bestCard;
useCard = (AdventureResult) bestCard;
}
else
{
spec.equipment[ EquipmentManager.CARDSLEEVE ] = current.equipment[ EquipmentManager.CARDSLEEVE ];
useCard = (AdventureResult) current.equipment[ EquipmentManager.CARDSLEEVE ];
}
}
else if ( itemId == ItemPool.FOLDER_HOLDER )
{
MaximizerSpeculation current = new MaximizerSpeculation();
spec.equipment[ EquipmentManager.FOLDER1 ] = current.equipment[ EquipmentManager.FOLDER1 ];
spec.equipment[ EquipmentManager.FOLDER2 ] = current.equipment[ EquipmentManager.FOLDER2 ];
spec.equipment[ EquipmentManager.FOLDER3 ] = current.equipment[ EquipmentManager.FOLDER3 ];
spec.equipment[ EquipmentManager.FOLDER4 ] = current.equipment[ EquipmentManager.FOLDER4 ];
spec.equipment[ EquipmentManager.FOLDER5 ] = current.equipment[ EquipmentManager.FOLDER5 ];
}
else if ( itemId == ItemPool.CROWN_OF_ED )
{
if ( bestEdPiece != null )
{
spec.setEdPiece( bestEdPiece );
}
}
else if ( itemId == ItemPool.SNOW_SUIT )
{
if ( bestSnowsuit != null )
{
spec.setSnowsuit( bestSnowsuit );
}
}
else if ( itemId == ItemPool.COWBOY_BOOTS )
{
MaximizerSpeculation current = new MaximizerSpeculation();
spec.equipment[ EquipmentManager.BOOTSKIN ] = current.equipment[ EquipmentManager.BOOTSKIN ];
spec.equipment[ EquipmentManager.BOOTSPUR ] = current.equipment[ EquipmentManager.BOOTSPUR ];
}
spec.getScore(); // force evaluation
spec.failed = false; // individual items are not expected
// to fulfill all requirements
speculationList[ slot ].add( spec );
}
Collections.sort( speculationList[ slot ] );
}
// Compare sets which improve with the number of items equipped with the best items in the same spots
// Compare synergies with best items in the same spots, and remove automatic flag if not better
Iterator it = Modifiers.getSynergies();
while ( it.hasNext() )
{
String synergy = (String) it.next();
int mask = ((Integer) it.next()).intValue();
int index = synergy.indexOf( "/");
String itemName1 = synergy.substring( 0, index );
String itemName2 = synergy.substring( index + 1 );
int itemId1 = ItemDatabase.getItemId( itemName1 );
int itemId2 = ItemDatabase.getItemId( itemName2 );
int slot1 = EquipmentManager.itemIdToEquipmentType( itemId1 );
int slot2 = EquipmentManager.itemIdToEquipmentType( itemId2 );
CheckedItem item1 = null;
CheckedItem item2 = null;
// The only times the slots will be wrong for looking at speculation lists for current synergies are 1 handed swords
// They are always item 1
int hands = EquipmentDatabase.getHands( itemId1 );
WeaponType weaponType = EquipmentDatabase.getWeaponType( itemId1 );
int slot1SpecLookup = slot1;
if ( hands == 1 && weaponType == WeaponType.MELEE )
{
slot1SpecLookup = Evaluator.WEAPON_1H;
}
if ( slot1 == -1 || slot2 == -1 )
{
continue;
}
ListIterator<MaximizerSpeculation> sI = speculationList[ slot1SpecLookup ].listIterator( speculationList[ slot1SpecLookup ].size() );
while ( sI.hasPrevious() && item1 == null )
{
CheckedItem checkItem = sI.previous().attachment;
checkItem.validate( maxPrice, priceLevel );
if ( checkItem.getName().equals( itemName1 ) )
{
item1 = checkItem;
}
}
sI = speculationList[ slot2 ].listIterator( speculationList[ slot2 ].size() );
while ( sI.hasPrevious() && item2 == null )
{
CheckedItem checkItem = sI.previous().attachment;
checkItem.validate( maxPrice, priceLevel );
if ( checkItem.getName().equals( itemName2 ) )
{
item2 = checkItem;
}
}
if ( item1 == null || item2 == null )
{
continue;
}
// Found a synergy in our speculationList, so compare it with the best individual items
int accCompared = 0;
MaximizerSpeculation synergySpec = new MaximizerSpeculation();
MaximizerSpeculation compareSpec = new MaximizerSpeculation();
int newSlot1 = slot1;
int compareItemNo = slot1 == EquipmentManager.ACCESSORY1 ? speculationList[ slot1SpecLookup ].size() - 3 : speculationList[ slot1SpecLookup ].size() - 1;
do
{
CheckedItem compareItem = speculationList[ slot1SpecLookup ].get( compareItemNo ).attachment;
if ( compareItem.conditionalFlag )
{
compareItemNo--;
}
else
{
compareSpec.equipment[ newSlot1 ] = speculationList[ slot1SpecLookup ].get( compareItemNo ).attachment;
break;
}
if ( compareItemNo < 0 )
{
compareSpec.equipment[ newSlot1 ] = EquipmentRequest.UNEQUIP;
break;
}
}
while ( compareItemNo >= 0 );
if ( slot1 == EquipmentManager.ACCESSORY1 )
{
accCompared++;
}
synergySpec.equipment[ newSlot1 ] = item1;
int newSlot2 = slot2 + ( slot2 == EquipmentManager.ACCESSORY1 ? accCompared : 0 );
compareItemNo = slot2 == EquipmentManager.ACCESSORY1 ? speculationList[ slot2 ].size() - 2 : speculationList[ slot2 ].size() - 1;
do
{
CheckedItem compareItem = speculationList[ slot2 ].get( compareItemNo ).attachment;
if ( compareItem.conditionalFlag || compareItem.getName().equals( compareSpec.equipment[ newSlot1 ].getName() ) )
{
compareItemNo--;
}
else
{
compareSpec.equipment[ newSlot2 ] = speculationList[ slot2 ].get( compareItemNo ).attachment;
break;
}
if ( compareItemNo < 0 )
{
compareSpec.equipment[ newSlot2 ] = EquipmentRequest.UNEQUIP;
break;
}
}
while ( compareItemNo >= 0 );
synergySpec.equipment[ newSlot2 ] = item2;
if ( synergySpec.compareTo( compareSpec ) <= 0 || synergySpec.failed )
{
// Not useful, so remove it's automatic flag so it won't be put forward unless it's good enough in it's own right
sI = speculationList[ slot1SpecLookup ].listIterator( speculationList[ slot1SpecLookup ].size() );
while ( sI.hasPrevious() )
{
MaximizerSpeculation spec = sI.previous();
CheckedItem checkItem = spec.attachment;
checkItem.validate( maxPrice, priceLevel );
if ( checkItem.getName().equals( itemName1 ) )
{
spec.attachment.automaticFlag = false;
break;
}
}
sI = speculationList[ slot2 ].listIterator( speculationList[ slot2 ].size() );
while ( sI.hasPrevious() )
{
MaximizerSpeculation spec = sI.previous();
CheckedItem checkItem = spec.attachment;
checkItem.validate( maxPrice, priceLevel );
if ( checkItem.getName().equals( itemName2 ) )
{
spec.attachment.automaticFlag = false;
break;
}
}
}
}
// However, that's only two item Synergies, and there are two three item synergies effectively.
// Ugly hack to reinstate them if necessary. They are always accessories, which simplifies things.
int count = 0;
while ( count < 2 )
{
int itemId1 = -1;
int itemId2 = -1;
int itemId3 = -1;
CheckedItem item1 = null;
CheckedItem item2 = null;
CheckedItem item3 = null;
int slot = EquipmentManager.ACCESSORY1;
if ( count == 0 )
{
itemId1 = ItemPool.MONSTROUS_MONOCLE;
itemId2 = ItemPool.MUSTY_MOCCASINS;
itemId3 = ItemPool.MOLTEN_MEDALLION;
count++;
}
else
{
itemId1 = ItemPool.BRAZEN_BRACELET;
itemId2 = ItemPool.BITTER_BOWTIE;
itemId3 = ItemPool.BEWITCHING_BOOTS;
count++;
}
ListIterator<MaximizerSpeculation> sI = speculationList[ slot ].listIterator( speculationList[ slot ].size() );
while ( sI.hasPrevious() )
{
CheckedItem checkItem = sI.previous().attachment;
checkItem.validate( maxPrice, priceLevel );
if ( checkItem.getItemId() == itemId1 )
{
item1 = checkItem;
}
else if ( checkItem.getItemId() == itemId2 )
{
item2 = checkItem;
}
else if ( checkItem.getItemId() == itemId3 )
{
item3 = checkItem;
}
if ( item1 != null && item2 != null && item3 != null )
{
break;
}
}
if ( item1 == null || item2 == null || item3 == null )
{
continue;
}
// All three in our speculationList, so compare it with the best 3 accessories items
MaximizerSpeculation synergySpec = new MaximizerSpeculation();
MaximizerSpeculation compareSpec = new MaximizerSpeculation();
int compareItemNo = speculationList[ slot ].size() - 1;
compareSpec.equipment[ slot ] = EquipmentRequest.UNEQUIP;
compareSpec.equipment[ slot + 1 ] = EquipmentRequest.UNEQUIP;
compareSpec.equipment[ slot + 2 ] = EquipmentRequest.UNEQUIP;
int newSlot = slot;
do
{
CheckedItem compareItem = speculationList[ slot ].get( compareItemNo ).attachment;
if ( !compareItem.conditionalFlag )
{
compareSpec.equipment[ newSlot ] = speculationList[ slot ].get( compareItemNo ).attachment;
newSlot++;
}
compareItemNo--;
}
while ( compareItemNo >= 0 && newSlot < slot + 3 );
synergySpec.equipment[ slot ] = item1;
synergySpec.equipment[ slot + 1 ] = item2;
synergySpec.equipment[ slot + 2 ] = item3;
if ( synergySpec.compareTo( compareSpec ) > 0 && !synergySpec.failed )
{
// Useful, so automatic flag it again
sI = speculationList[ slot ].listIterator( speculationList[ slot ].size() );
int found = 0;
while ( sI.hasPrevious() && found < 3 )
{
MaximizerSpeculation spec = sI.previous();
CheckedItem checkItem = spec.attachment;
checkItem.validate( maxPrice, priceLevel );
if ( checkItem.getItemId() == itemId1 )
{
spec.attachment.automaticFlag = true;
found++;
}
else if ( checkItem.getItemId() == itemId2 )
{
spec.attachment.automaticFlag = true;
found++;
}
else if ( checkItem.getItemId() == itemId3 )
{
spec.attachment.automaticFlag = true;
found++;
}
}
}
}
// Compare outfits with best item in the same spot, and remove if not better
// Compare the accessories to the worst ones, not the best
StringBuilder outfitSummary = new StringBuilder();
outfitSummary.append( "Outfits [" );
int outfitCount = 0;
for ( int i = 0 ; i < usefulOutfits.size() ; i++ )
{
if ( usefulOutfits.get( i ) )
{
int accCount = 0;
MaximizerSpeculation outfitSpec = new MaximizerSpeculation();
MaximizerSpeculation compareSpec = new MaximizerSpeculation();
// Get pieces of outfit
SpecialOutfit outfit = EquipmentDatabase.getOutfit( i );
AdventureResult[] pieces = outfit.getPieces();
for ( int j = 0; j < pieces.length; ++j )
{
int outfitItemId = pieces[j].getItemId();
int slot = EquipmentManager.itemIdToEquipmentType( outfitItemId );
// For some items, Evaluator uses a different slot
// I don't think any outfits use an offhand weapon or watch though?
int hands = EquipmentDatabase.getHands( outfitItemId );
if ( hands == 1 )
{
slot = Evaluator.WEAPON_1H;
}
// Compare outfit with best individual non conditional item that hasn't previously been used
// For accessories compare with 3rd best for first accessory, 2nd best for second accessory, best for third
int newSlot = slot + ( slot == EquipmentManager.ACCESSORY1 ? accCount : 0 );
int compareItemNo = speculationList[ slot ].size() - 1;
int accSkip = slot == EquipmentManager.ACCESSORY1 ? 2 - accCount : 0;
while ( compareItemNo >= 0 )
{
CheckedItem compareItem = speculationList[ slot ].get( compareItemNo ).attachment;
if ( compareItem.conditionalFlag )
{
compareItemNo--;
}
else if ( accSkip > 0 )
{
// Valid item, but we're looking for 2nd or 3rd best non-conditional
compareItemNo--;
accSkip--;
}
else
{
compareSpec.equipment[ newSlot ] = compareItem;
break;
}
if ( compareItemNo < 0 )
{
compareSpec.equipment[ newSlot ] = EquipmentRequest.UNEQUIP;
break;
}
}
CheckedItem outfitItem = new CheckedItem( outfitItemId, equipLevel, maxPrice, priceLevel );
outfitSpec.equipment[ newSlot ] = outfitItem;
}
if ( outfitSpec.compareTo( compareSpec ) <= 0 && !this.posOutfits.contains( outfit.getName() ) )
{
usefulOutfits.set( i, false );
}
else
{
if ( outfitCount > 0 )
{
outfitSummary.append( ", " );
}
outfitSummary.append( outfit.toString() );
outfitCount++;
}
}
}
if ( this.dump > 0 )
{
outfitSummary.append( "]" );
RequestLogger.printLine( outfitSummary.toString() );
}
for ( int slot = 0; slot < ranked.length; ++slot )
{
ArrayList<CheckedItem> checkedItemList = ranked[ slot ];
if ( this.dump > 0 )
{
RequestLogger.printLine( "SLOT " + slot );
}
if ( this.dump > 1 )
{
RequestLogger.printLine( speculationList[ slot ].toString() );
}
// Do we have any required items for the slot?
int total = 0;
for ( CheckedItem item : checkedItemList )
{
if ( item.requiredFlag )
{
automatic[ slot ].add( item );
++total;
}
}
int useful = this.maxUseful( slot );
// If slots already handled by required items, we're done with the slot
if ( useful > total )
{
ListIterator<MaximizerSpeculation> speculationIterator = speculationList[ slot ].listIterator( speculationList[ slot ].size() );
int beeotches = 0;
int beeosity = 0;
int b;
while ( speculationIterator.hasPrevious() )
{
CheckedItem item = speculationIterator.previous().attachment;
item.validate( maxPrice, priceLevel );
// If we only need as many fold items as we have, then we can
// count them against the items we need to pass through
ArrayList group = ItemDatabase.getFoldGroup( item.getName() );
int foldItemsNeeded = 0;
if ( group != null && Preferences.getBoolean( "maximizerFoldables" ) )
{
// How many times have we already used this fold item?
for ( int checkSlot = 0; checkSlot < slot; ++checkSlot )
{
ArrayList<CheckedItem> checkItemList = automatic[ checkSlot ];
if ( checkItemList != null )
{
for ( CheckedItem checkItem : checkItemList )
{
ArrayList checkGroup = ItemDatabase.getFoldGroup( checkItem.getName() );
if ( checkGroup != null && group.get( 1 ).equals( checkGroup.get( 1 ) ) )
{
foldItemsNeeded += checkItem.getCount();
}
}
}
}
// And how many times do we expect to use them for the rest of the slots?
for ( int checkSlot = slot + 1; checkSlot < ranked.length; checkSlot++ )
{
ListIterator<MaximizerSpeculation> checkIterator = speculationList[ checkSlot ].listIterator( speculationList[ checkSlot ].size() );
int usefulCheckCount = this.maxUseful( checkSlot );
while ( checkIterator.hasPrevious() && usefulCheckCount > 0 )
{
CheckedItem checkItem = checkIterator.previous().attachment;
ArrayList checkGroup = ItemDatabase.getFoldGroup( checkItem.getName() );
if ( checkGroup != null && group.get( 1 ).equals( checkGroup.get( 1 ) ) )
{
foldItemsNeeded += checkItem.getCount();
}
else if ( checkItem.automaticFlag || !checkItem.conditionalFlag )
{
usefulCheckCount--;
}
}
}
}
if ( item.getCount() == 0 )
{
continue;
}
if ( KoLCharacter.inBeecore() &&
(b = KoLCharacter.getBeeosity( item.getName() )) > 0 )
{ // This item is a beeotch!
// Don't count it towards the number of items desired
// in this slot's shortlist, since it may turn out to be
// advantageous to use up all our allowed beeosity on
// other slots.
if ( item.automaticFlag )
{
if ( !automatic[ slot ].contains( item ) )
{
automatic[ slot ].add( item );
}
beeotches += item.getCount();
beeosity += b * item.getCount();
}
else if ( total < useful && beeotches < useful &&
beeosity < this.beeosity )
{
if ( !automatic[ slot ].contains( item ) )
{
automatic[ slot ].add( item );
}
beeotches += item.getCount();
beeosity += b * item.getCount();
}
}
else if ( item.automaticFlag )
{
if ( !automatic[ slot ].contains( item ) )
{
automatic[ slot ].add( item );
if ( !item.conditionalFlag && item.getCount() >= foldItemsNeeded )
{
total += item.getCount();
}
}
}
else if ( total < useful )
{
if ( !automatic[ slot ].contains( item ) )
{
automatic[ slot ].add( item );
if ( !item.conditionalFlag && item.getCount() >= foldItemsNeeded )
{
total += item.getCount();
}
}
}
}
}
if ( this.dump > 0 )
{
RequestLogger.printLine( automatic[ slot ].toString() );
}
}
automatic[ EquipmentManager.ACCESSORY1 ].addAll( automatic[ Evaluator.WATCHES ] );
automatic[ EquipmentManager.WEAPON ].addAll( automatic[ Evaluator.WEAPON_1H ] );
automatic[ Evaluator.OFFHAND_MELEE ].addAll( automatic[ EquipmentManager.OFFHAND ] );
automatic[ Evaluator.OFFHAND_RANGED ].addAll( automatic[ EquipmentManager.OFFHAND ] );
MaximizerSpeculation spec = new MaximizerSpeculation();
// The threshold in the slots array that indicates that a slot
// should be considered will be either >= 1 or >= 0, depending
// on whether inclusive or exclusive slot specs were used.
for ( int thresh = 1; ; --thresh )
{
if ( thresh < 0 ) return; // no slots enabled
boolean anySlots = false;
for ( int i = 0; i <= EquipmentManager.FAMILIAR; ++i )
{
if ( this.slots[ i ] >= thresh )
{
spec.equipment[ i ] = null;
anySlots = true;
}
}
if ( anySlots ) break;
}
if ( spec.equipment[ EquipmentManager.OFFHAND ] != null )
{
this.hands = 1;
automatic[ EquipmentManager.WEAPON ] = automatic[ Evaluator.WEAPON_1H ];
Iterator<AdventureResult> i = outfitPieces.keySet().iterator();
while ( i.hasNext() )
{
id = i.next().getItemId();
if ( EquipmentManager.itemIdToEquipmentType( id ) == EquipmentManager.WEAPON &&
EquipmentDatabase.getHands( id ) > 1 )
{
i.remove();
}
}
}
if ( spec.equipment[ EquipmentManager.HAT ] == null )
{
spec.setEdPiece( bestEdPiece );
}
if ( spec.equipment[ EquipmentManager.FAMILIAR ] == null )
{
spec.setSnowsuit( bestSnowsuit );
}
spec.tryAll( this.familiars, this.carriedFamiliars, usefulOutfits, outfitPieces, automatic, useCard, useCrownFamiliar, useBjornFamiliar );
}
}