/** * 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.util.ArrayList; import java.util.Collections; import java.util.List; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants.Stat; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.objectpool.FamiliarPool; import net.sourceforge.kolmafia.objectpool.IntegerPool; import net.sourceforge.kolmafia.persistence.AdventureDatabase; import net.sourceforge.kolmafia.persistence.AdventureQueueDatabase; import net.sourceforge.kolmafia.persistence.AdventureSpentDatabase; import net.sourceforge.kolmafia.persistence.BountyDatabase; import net.sourceforge.kolmafia.persistence.ItemDatabase; import net.sourceforge.kolmafia.persistence.MonsterDatabase; import net.sourceforge.kolmafia.persistence.MonsterDatabase.Element; import net.sourceforge.kolmafia.persistence.MonsterDatabase.Phylum; import net.sourceforge.kolmafia.persistence.QuestDatabase; import net.sourceforge.kolmafia.persistence.QuestDatabase.Quest; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.FightRequest; import net.sourceforge.kolmafia.session.BanishManager; import net.sourceforge.kolmafia.session.EncounterManager; import net.sourceforge.kolmafia.session.EncounterManager.EncounterType; import net.sourceforge.kolmafia.session.EquipmentManager; import net.sourceforge.kolmafia.utilities.StringUtilities; public class AreaCombatData { private static double lastDropModifier = 0.0; private static double lastDropMultiplier = 0.0; private int minHit; private int maxHit; private int minEvade; private int maxEvade; private int poison; private int jumpChance; private final int combats; private double weights; // Parallel lists: monsters and encounter weighting private final List<MonsterData> monsters; private final List<MonsterData> superlikelyMonsters; private final List<Integer> baseWeightings; private final List<Integer> currentWeightings; private final List<Integer> rejection; private final String zone; // Flags in low-order bits of weightings private static final int ASCENSION_ODD = 0x01; private static final int ASCENSION_EVEN = 0x02; private static final int WEIGHT_SHIFT = 2; public AreaCombatData( String zone, final int combats ) { this.zone = zone; this.monsters = new ArrayList<MonsterData>(); this.superlikelyMonsters = new ArrayList<MonsterData>(); this.baseWeightings = new ArrayList<Integer>(); this.currentWeightings = new ArrayList<Integer>(); this.rejection = new ArrayList<Integer>(); this.combats = combats; this.weights = 0.0; this.minHit = Integer.MAX_VALUE; this.maxHit = 0; this.minEvade = Integer.MAX_VALUE; this.maxEvade = 0; this.poison = Integer.MAX_VALUE; this.jumpChance = Integer.MAX_VALUE; } public void recalculate() { this.minHit = Integer.MAX_VALUE; this.maxHit = 0; this.minEvade = Integer.MAX_VALUE; this.maxEvade = 0; this.jumpChance = 100; double weights = 0.0; List<Integer> currentWeightings = new ArrayList<Integer>(); for ( int i = 0; i < this.monsters.size(); ++i ) { // Weighting has two low bits which represent odd or even ascension restriction // Strip them out now and restore them at the end int baseWeighting = this.baseWeightings.get( i ); int flags = baseWeighting & 3; baseWeighting = baseWeighting >> WEIGHT_SHIFT; MonsterData monster = this.getMonster( i ); String monsterName = monster.getName(); baseWeighting = AreaCombatData.adjustConditionalWeighting( zone, monsterName, baseWeighting ); int currentWeighting = baseWeighting; // If olfacted, add three to encounter pool if ( Preferences.getString( "olfactedMonster" ).equals( monsterName ) && KoLConstants.activeEffects.contains( FightRequest.ONTHETRAIL ) ) { currentWeighting += 3 * baseWeighting; } // If Nosy Nose sniffed, and is current familiar, add one to encounter pool if ( Preferences.getString( "nosyNoseMonster" ).equals( monsterName ) && KoLCharacter.getFamiliar().getId() == FamiliarPool.NOSY_NOSE ) { currentWeighting += baseWeighting; } // If Staff of the Cream of the Cream jiggle, add two to encounter pool if ( Preferences.getString( "_jiggleCreamedMonster" ).equals( monsterName ) ) { currentWeighting += 2 * baseWeighting; } // If Make Friends used, add three to encounter pool if ( Preferences.getString( "makeFriendsMonster" ).equals( monsterName ) ) { currentWeighting += 3 * baseWeighting; } // If Curse of Stench used, add three to encounter pool if ( Preferences.getString( "stenchCursedMonster" ).equals( monsterName ) ) { currentWeighting += 3 * baseWeighting; } // If Long Con used, add three to encounter pool if ( Preferences.getString( "longConMonster" ).equals( monsterName ) ) { currentWeighting += 3 * baseWeighting; } if ( BanishManager.isBanished( monsterName ) ) { // Banishing reduces number of copies currentWeighting -= baseWeighting; // If this takes it to zero chance, it's properly banished if ( currentWeighting == 0 ) { currentWeighting = -3; } } // Not available in current if ( ( flags == ASCENSION_ODD && KoLCharacter.getAscensions() % 2 == 1 ) || ( flags == ASCENSION_EVEN && KoLCharacter.getAscensions() % 2 == 0 ) ) { currentWeighting = -2; // impossible this ascension } // Temporarily set to 0% chance if ( baseWeighting == -4 ) { currentWeighting = -4; } currentWeightings.add( (currentWeighting << WEIGHT_SHIFT) | flags ); // Omit currently 0% chance, banished (-3), impossible (-2) and ultra-rare (-1) monsters if ( currentWeighting < 0 ) { continue; } weights += currentWeighting * ( 1 - (double) this.getRejection( i ) / 100 ); this.addMonsterStats( monster ); } this.weights = weights; Collections.copy( this.currentWeightings, currentWeightings ); // Take into account superlikely monsters if they have a non zero chance to appear for ( int i = 0; i < this.superlikelyMonsters.size(); ++i ) { MonsterData monster = this.getMonster( i ); String monsterName = monster.getName(); if ( AreaCombatData.superlikelyChance( monsterName ) > 0 ) { this.addMonsterStats( monster ); } } } private void addMonsterStats( MonsterData monster ) { // These include current monster level and Beeosity int attack = monster.getAttack(); if ( attack < this.minEvade ) { this.minEvade = attack; } if ( attack > this.maxEvade ) { this.maxEvade = attack; } int defense = monster.getDefense(); if ( defense < this.minHit ) { this.minHit = defense; } if ( defense > this.maxHit ) { this.maxHit = defense; } int jumpChance = monster.getJumpChance(); if ( jumpChance < this.jumpChance ) { this.jumpChance = jumpChance; } } public boolean addMonster( String name ) { int weighting = 1; int flags = ASCENSION_EVEN | ASCENSION_ODD; int rejection = 0; int colon = name.indexOf( ":" ); if ( colon > 0 ) { String weight = name.substring( colon + 1 ).trim(); name = name.substring( 0, colon ); String flag = null; if ( weight.length() == 0 ) { KoLmafia.updateDisplay( "Missing entry after colon for " + name + " in combats.txt." ); return false; } for ( int i = 0; i < weight.length(); i++ ) { char ch = weight.charAt( i ); if ( i == 0 && ch == '-' ) { continue; } if ( !Character.isDigit( ch ) ) { flag = weight.substring( i ); weight = weight.substring( 0, i ); break; } } if ( !StringUtilities.isNumeric( weight ) ) { KoLmafia.updateDisplay( "First entry after colon for " + name + " in combats.txt is not numeric." ); return false; } weighting = Integer.parseInt( weight ); // Only one flag per monster is is supported if ( flag != null ) { switch ( flag.charAt( 0 ) ) { case 'e': flags = ASCENSION_EVEN; break; case 'o': flags = ASCENSION_ODD; break; case 'r': if ( flag.length() > 1 ) { if ( !StringUtilities.isNumeric( flag.substring( 1 ) ) ) { KoLmafia.updateDisplay( "Rejection percentage specified for " + name + " in combats.txt is not numeric." ); return false; } rejection = StringUtilities.parseInt( flag.substring( 1 ) ); } else { KoLmafia.updateDisplay( "No rejection percentage specified for " + name + " in combats.txt." ); return false; } break; default: KoLmafia.updateDisplay( "Unknown flag " + flag.charAt( 0 ) + " specified for " + name + " in combats.txt." ); return false; } } } MonsterData monster = MonsterDatabase.findMonster( name, false ); if ( monster == null ) { return false; } if ( EncounterManager.isSuperlikelyMonster( monster.getName() ) ) { this.superlikelyMonsters.add( monster ); } else { this.monsters.add( monster ); this.baseWeightings.add( IntegerPool.get( (weighting << WEIGHT_SHIFT) | flags ) ); this.currentWeightings.add( IntegerPool.get( (weighting << WEIGHT_SHIFT) | flags ) ); this.rejection.add( IntegerPool.get( rejection ) ); } this.poison = Math.min( this.poison, monster.getPoison() ); // Don't let ultra-rare monsters skew hit and evade numbers - // or anything else. if ( weighting < 0 ) { return true; } // Don't let special monsters skew combat percentage numbers // or things derived from them, like area-wide item and meat // drops. Do include them in hit and evade ("safety") numbers. // Assume that the number and total weights of even- and // odd-ascension-only monsters are equal. if ( weighting > 0 && flags != ASCENSION_ODD ) { this.weights += weighting * ( 1 - (double) rejection / 100 ); } this.addMonsterStats( monster ); return true; } /** * Counts the number of monsters in this area that drop the item with the * given ID. * * @param itemId * @return the number of monsters in this area dropping the item */ public int countMonstersDroppingItem( final int itemId ) { int total = 0; for ( MonsterData monster : this.monsters ) { for ( AdventureResult item : monster.getItems() ) { if ( item.getItemId() == itemId ) { total++; break; } } } for ( MonsterData monster : this.superlikelyMonsters ) { for ( AdventureResult item : monster.getItems() ) { if ( item.getItemId() == itemId ) { total++; break; } } } return total; } public int getMonsterCount() { return this.monsters.size(); } public int getSuperlikelyMonsterCount() { return this.superlikelyMonsters.size(); } public int getAvailableMonsterCount() { int count = 0; int size = this.monsters.size(); for ( int i = 0; i < size; ++i ) { int weighting = this.getWeighting( i ); if ( weighting > 0 ) { count++; } } size = this.superlikelyMonsters.size(); for ( int i = 0; i < size; ++i ) { MonsterData monster = this.superlikelyMonsters.get( i ); String monsterName = monster.getName(); if ( AreaCombatData.superlikelyChance( monsterName ) > 0 ) { count++; } } return count; } public MonsterData getMonster( final int i ) { return (MonsterData) this.monsters.get( i ); } public MonsterData getSuperlikelyMonster( final int i ) { return (MonsterData) this.superlikelyMonsters.get( i ); } public boolean hasMonster( final MonsterData m ) { if ( m == null ) { return false; } return this.monsters.contains( m ) || this.superlikelyMonsters.contains( m ); } public int getMonsterIndex( MonsterData monster ) { return this.monsters.indexOf( monster ); } public int getSuperlikelyMonsterIndex( MonsterData monster ) { return this.superlikelyMonsters.indexOf( monster ); } public int getWeighting( final int i ) { int raw = ( this.currentWeightings.get( i ) ).intValue(); if ( ((raw >> (KoLCharacter.getAscensions() & 1)) & 1) == 0 ) { return -2; // impossible this ascension } return raw >> WEIGHT_SHIFT; } public int getRejection( final int i ) { return this.rejection.get( i ); } public double totalWeighting() { return this.weights; } public int combats() { return this.combats; } public int minHit() { return this.minHit == Integer.MAX_VALUE ? 0 : this.minHit; } public int maxHit() { return this.maxHit; } public int minEvade() { return this.minEvade == Integer.MAX_VALUE ? 0 : this.minEvade; } public int maxEvade() { return this.maxEvade; } public int poison() { return this.poison; } public boolean willHitSomething() { int hitStat = EquipmentManager.getAdjustedHitStat(); return AreaCombatData.hitPercent( hitStat, this.minHit() ) > 0.0; } public double getAverageML() { double averageML = 0.0; for ( int i = 0; i < this.monsters.size(); ++i ) { int weighting = this.getWeighting( i ); // Omit impossible (-2), ultra-rare (-1) and special/banished (0) monsters if ( weighting < 1 ) { continue; } MonsterData monster = this.getMonster( i ); double weight = (double) weighting * ( 1 - (double) this.getRejection( i ) / 100 ) / this.totalWeighting(); averageML += weight * monster.getAttack(); } double averageSuperlikelyML = 0.0; double superlikelyChance = 0.0; for ( int i = 0; i < this.superlikelyMonsters.size(); ++i ) { MonsterData monster = this.superlikelyMonsters.get( i ); String monsterName = monster.getName(); double chance = AreaCombatData.superlikelyChance( monsterName ); if ( chance > 0 ) { averageSuperlikelyML += chance * monster.getAttack(); superlikelyChance += chance; } } return averageML * ( 1 - superlikelyChance / 100 ) + averageSuperlikelyML; } @Override public String toString() { return this.toString( false ); } public String toString( final boolean fullString ) { StringBuffer buffer = new StringBuffer(); buffer.append( "<html><head>" ); buffer.append( "<style>" ); buffer.append( "body { font-family: sans-serif; font-size: " ); buffer.append( Preferences.getString( "chatFontSize" ) ); buffer.append( "; }" ); buffer.append( "</style>" ); buffer.append( "</head><body>" ); this.getSummary( buffer, fullString ); this.getEncounterData( buffer ); this.getMonsterData( buffer, fullString ); buffer.append( "</body></html>" ); return buffer.toString(); } public void getSummary( final StringBuffer buffer, final boolean fullString ) { // Get up-to-date monster stats in area summary this.recalculate(); int moxie = KoLCharacter.getAdjustedMoxie(); String statName = EquipmentManager.getHitStatType() == Stat.MOXIE ? "Mox" : "Mus"; int hitstat = EquipmentManager.getAdjustedHitStat(); double minHitPercent = AreaCombatData.hitPercent( hitstat, this.minHit() ); double maxHitPercent = AreaCombatData.hitPercent( hitstat, this.maxHit ); int minPerfectHit = AreaCombatData.perfectHit( hitstat, this.minHit() ); int maxPerfectHit = AreaCombatData.perfectHit( hitstat, this.maxHit ); double minEvadePercent = AreaCombatData.hitPercent( moxie, this.minEvade() ); double maxEvadePercent = AreaCombatData.hitPercent( moxie, this.maxEvade ); int minPerfectEvade = AreaCombatData.perfectHit( moxie, this.minEvade() ); int maxPerfectEvade = AreaCombatData.perfectHit( moxie, this.maxEvade ); int jumpChance = this.jumpChance; // statGain constants double experienceAdjustment = KoLCharacter.getExperienceAdjustment(); // Area Combat percentage double combatFactor = this.areaCombatPercent() / 100.0; // Iterate once through monsters to calculate average statGain double averageExperience = 0.0; for ( int i = 0; i < this.monsters.size(); ++i ) { int weighting = this.getWeighting( i ); // Omit impossible (-2), ultra-rare (-1) and special/banished (0) monsters if ( weighting < 1 ) { continue; } MonsterData monster = this.getMonster( i ); double weight = (double) weighting * ( 1 - (double) this.getRejection( i ) / 100 ) / this.totalWeighting(); int ml = monster.ML(); averageExperience += weight * ( monster.getExperience() + experienceAdjustment - ml / ( ml > 0 ? 6.0 : 8.0 ) ); } double averageSuperlikelyExperience = 0.0; double superlikelyChance = 0.0; for ( int i = 0; i < this.superlikelyMonsters.size(); ++i ) { MonsterData monster = this.superlikelyMonsters.get( i ); String monsterName = monster.getName(); double chance = AreaCombatData.superlikelyChance( monsterName ); if ( chance > 0 ) { averageSuperlikelyExperience += chance / 100 * (monster.getExperience() + experienceAdjustment); superlikelyChance += chance; } } buffer.append( "<b>Hit</b>: " ); buffer.append( this.getRateString( minHitPercent, minPerfectHit, maxHitPercent, maxPerfectHit, statName, fullString ) ); buffer.append( "<br><b>Evade</b>: " ); buffer.append( this.getRateString( minEvadePercent, minPerfectEvade, maxEvadePercent, maxPerfectEvade, "Mox", fullString ) ); buffer.append( "<br><b>Jump Chance</b>: " ); buffer.append( jumpChance + "%" ); buffer.append( "<br><b>Combat Rate</b>: " ); double combatXP = averageSuperlikelyExperience + averageExperience * ( 1 - superlikelyChance / 100 ) * combatFactor; if ( this.combats > 0 ) { buffer.append( this.format( superlikelyChance + ( 1 - superlikelyChance / 100 ) * combatFactor * 100.0 ) + "%" ); buffer.append( "<br><b>Combat XP</b>: " + KoLConstants.FLOAT_FORMAT.format( combatXP ) ); } else if ( this.combats == 0 ) { buffer.append( "0%" ); } else { buffer.append( "No data" ); } } public void getMonsterData( final StringBuffer buffer, final boolean fullString ) { int moxie = KoLCharacter.getAdjustedMoxie(); int hitstat = EquipmentManager.getAdjustedHitStat(); double combatFactor = this.areaCombatPercent() / 100.0; double superlikelyChance = 0.0; for ( int i = 0; i < this.superlikelyMonsters.size(); ++i ) { MonsterData monster = this.superlikelyMonsters.get( i ); String monsterName = monster.getName(); double chance = AreaCombatData.superlikelyChance( monsterName ); buffer.append( "<br><br>" ); buffer.append( this.getMonsterString( this.getSuperlikelyMonster( i ), moxie, hitstat, 0, 0, combatFactor, chance, fullString ) ); superlikelyChance += chance; } for ( int i = 0; i < this.monsters.size(); ++i ) { int weighting = this.getWeighting( i ); int rejection = this.getRejection( i ); if ( weighting == -2 ) { continue; } buffer.append( "<br><br>" ); buffer.append( this.getMonsterString( this.getMonster( i ), moxie, hitstat, weighting, rejection, combatFactor, superlikelyChance, fullString ) ); } } public void getEncounterData( final StringBuffer buffer ) { String environment = AdventureDatabase.getEnvironment( this.zone ); if ( environment == null ) { buffer.append( "<br>" ); buffer.append( "<b>Environment:</b> unknown" ); } else { buffer.append( "<br>" ); buffer.append( "<b>Environment:</b> " ); buffer.append( environment ); } int recommendedStat = AdventureDatabase.getRecommendedStat( this.zone ); if ( recommendedStat == -1 ) { buffer.append( "<br>" ); buffer.append( "<b>Recommended Mainstat:</b> unknown" ); } else { buffer.append( "<br>" ); buffer.append( "<b>Recommended Mainstat:</b> " ); buffer.append( recommendedStat ); } if ( KoLCharacter.inRaincore() ) { int waterLevel = KoLCharacter.getWaterLevel(); Boolean fixed = AdventureDatabase.getWaterLevel( this.zone ) != -1; if ( environment == null ) { buffer.append( "<br>" ); buffer.append( "<b>Water Level:</b> unknown" ); } else if ( recommendedStat == -1 && !fixed ) { buffer.append( "<br>" ); buffer.append( "<b>Water Level:</b> " ); buffer.append( waterLevel ); buffer.append( " (at least)" ); } else { buffer.append( "<br>" ); buffer.append( "<b>Water Level:</b> " ); buffer.append( waterLevel ); } } String encounter = EncounterManager.findEncounterForLocation( this.zone, EncounterType.SEMIRARE ); if ( null != encounter ) { buffer.append( "<br>" ); buffer.append( "<b>Semi-Rare:</b> " ); buffer.append( encounter ); } encounter = EncounterManager.findEncounterForLocation( this.zone, EncounterType.CLOVER ); if ( null != encounter ) { buffer.append( "<br>" ); buffer.append( "<b>Clover:</b> " ); buffer.append( encounter ); } encounter = EncounterManager.findEncounterForLocation( this.zone, EncounterType.GLYPH ); if ( null != encounter ) { buffer.append( "<br>" ); buffer.append( "<b>Hobo Glyph:</b> " ); buffer.append( encounter ); } if ( KoLCharacter.inAxecore() ) { encounter = EncounterManager.findEncounterForLocation( this.zone, EncounterType.BORIS ); if ( null != encounter ) { buffer.append( "<br>" ); buffer.append( "<b>Clancy:</b> " ); buffer.append( encounter ); } } if ( KoLCharacter.inBadMoon() ) { encounter = EncounterManager.findEncounterForLocation( this.zone, EncounterType.BADMOON ); if ( null != encounter ) { buffer.append( "<br>" ); buffer.append( "<b>Badmoon:</b> " ); buffer.append( encounter ); } } } private String format( final double percentage ) { return String.valueOf( (int) percentage ); } public double areaCombatPercent() { // Some areas have fixed non-combats, if we're tracking this, handle them here. if ( this.zone.equals( "Barf Mountain" ) ) { return Preferences.getBoolean( "dinseyRollercoasterNext" ) ? 0 : 100; } if ( this.zone.equals( "The Defiled Alcove" ) && Preferences.getInteger( "cyrptAlcoveEvilness" ) <= 25 ) { return 100; } if ( this.zone.equals( "The Defiled Cranny" ) && Preferences.getInteger( "cyrptCrannyEvilness" ) <= 25 ) { return 100; } if ( this.zone.equals( "The Defiled Niche" ) && Preferences.getInteger( "cyrptNicheEvilness" ) <= 25 ) { return 100; } if ( this.zone.equals( "The Defiled Nook" ) && Preferences.getInteger( "cyrptNookEvilness" ) <= 25 ) { return 100; } if ( this.zone.equals( "Investigating a Plaintive Telegram" ) ) { return Preferences.getInteger( "lttQuestStageCount" ) == 9 || QuestDatabase.isQuestStep( Quest.TELEGRAM, QuestDatabase.STARTED ) ? 0 : 100; } // If we don't have the data, pretend it's all combat if ( this.combats < 0 ) { return 100.0; } // Some areas are inherently all combat or no combat if ( this.combats == 0 || this.combats == 100 ) { return this.combats; } double pct = this.combats + KoLCharacter.getCombatRateAdjustment(); return Math.max( 0.0, Math.min( 100.0, pct ) ); } private String getRateString( final double minPercent, final int minMargin, final double maxPercent, final int maxMargin, final String statName, boolean fullString ) { StringBuilder buffer = new StringBuilder(); buffer.append( this.format( minPercent ) ); buffer.append( "%/" ); buffer.append( this.format( maxPercent ) ); buffer.append( "%" ); if ( !fullString ) { return buffer.toString(); } buffer.append( " (" ); buffer.append( statName ); if ( minMargin < Integer.MAX_VALUE / 2 ) { if ( minMargin >= 0 ) { buffer.append( "+" ); } buffer.append( minMargin ); buffer.append( "/" ); } else { buffer.append( " always hit" ); } if ( minMargin < Integer.MAX_VALUE / 2 ) { if ( maxMargin >= 0 ) { buffer.append( "+" ); } buffer.append( maxMargin ); } buffer.append( ")" ); return buffer.toString(); } private String getMonsterString( final MonsterData monster, final int moxie, final int hitstat, final int weighting, final int rejection, final double combatFactor, final double superlikelyChance, final boolean fullString ) { // moxie and hitstat NOT adjusted for monster level, since monster stats now are int defense = monster.getDefense(); double hitPercent = AreaCombatData.hitPercent( hitstat, defense ); int attack = monster.getAttack(); double evadePercent = AreaCombatData.hitPercent( moxie, attack ); int health = monster.getHP(); double statGain = monster.getExperience(); StringBuffer buffer = new StringBuffer(); Element ed = monster.getDefenseElement(); Element ea = monster.getAttackElement(); Element element = ed == Element.NONE ? ea : ed; Phylum phylum = monster.getPhylum(); int init = monster.getInitiative(); int jumpChance = monster.getJumpChance(); // Color the monster name according to its element buffer.append( " <font color=" + AreaCombatData.elementColor( element ) + "><b>" ); if ( monster.getPoison() < Integer.MAX_VALUE ) { buffer.append( "\u2620 " ); } String name = monster.getName(); buffer.append( name ); buffer.append( "</b></font> (" ); if ( EncounterManager.isSuperlikelyMonster( name ) ) { buffer.append( this.format( superlikelyChance ) + "%" ); } else if ( weighting == -1 ) { buffer.append( "ultra-rare" ); } else if ( weighting == -3 ) { buffer.append( "banished" ); } else if ( weighting == -4 ) { buffer.append( "0%" ); } else if ( weighting == 0 ) { { buffer.append( "special" ); } } else { buffer.append( this.format( AdventureQueueDatabase.applyQueueEffects( 100.0 * combatFactor * ( 1 - superlikelyChance / 100 ) * weighting * ( 1 - (double) rejection / 100 ), monster, this ) ) + "%" ); } buffer.append( ")<br>Hit: <font color=" + AreaCombatData.elementColor( ed ) + ">" ); buffer.append( this.format( hitPercent ) ); buffer.append( "%</font>, Evade: <font color=" + AreaCombatData.elementColor( ea ) + ">" ); buffer.append( this.format( evadePercent ) ); buffer.append( "%</font>, Jump Chance: <font color=" + AreaCombatData.elementColor( ea ) + ">" ); buffer.append( this.format( jumpChance ) ); buffer.append( "%</font><br>Atk: " + attack + ", Def: " + defense ); buffer.append( ", HP: " + health + ", XP: " + KoLConstants.FLOAT_FORMAT.format( statGain ) ); buffer.append( "<br>Phylum: " + phylum ); if ( init == -10000 ) { buffer.append( ", Never wins initiative" ); } else if ( init == 10000 ) { buffer.append( ", Always wins initiative" ); } else { buffer.append( ", Init: " + init ); } if ( fullString ) { this.appendMeatDrop( buffer, monster ); this.appendSprinkleDrop( buffer, monster ); } this.appendItemList( buffer, monster.getItems(), monster.getPocketRates(), fullString ); String bounty = BountyDatabase.getNameByMonster( monster.getName() ); if ( bounty != null ) { buffer.append( "<br>" + bounty + " (bounty)" ); } return buffer.toString(); } private void appendMeatDrop( final StringBuffer buffer, final MonsterData monster ) { int minMeat = monster.getMinMeat(); int maxMeat = monster.getMaxMeat(); if ( maxMeat == 0 ) { return; } int avgMeat = monster.getBaseMeat(); double modifier = Math.max( 0.0, ( KoLCharacter.getMeatDropPercentAdjustment() + 100.0 ) / 100.0 ); buffer.append( "<br>Meat: " ); buffer.append( this.format( (int)Math.floor (minMeat * modifier ) ) ); buffer.append( "-" ); buffer.append( this.format( (int)Math.floor( maxMeat * modifier ) ) ) ; buffer.append( " (" ); buffer.append( this.format( (int)Math.floor( avgMeat * modifier ) ) ); buffer.append( " average)" ); } private void appendSprinkleDrop( final StringBuffer buffer, final MonsterData monster ) { int minSprinkles = monster.getMinSprinkles(); int maxSprinkles = monster.getMaxSprinkles(); if ( maxSprinkles == 0 ) { return; } double modifier = Math.max( 0.0, ( KoLCharacter.getSprinkleDropPercentAdjustment() + 100.0 ) / 100.0 ); buffer.append( "<br>Sprinkles: " ); buffer.append( this.format( (int)Math.floor( minSprinkles * modifier ) ) ); if ( maxSprinkles != minSprinkles ) { buffer.append( "-" ); buffer.append( this.format( (int)Math.ceil( maxSprinkles * modifier ) ) ); } } private void appendItemList( final StringBuffer buffer, final List items, final List pocketRates, boolean fullString ) { if ( items.size() == 0 ) { return; } double itemModifier = AreaCombatData.getDropRateModifier(); boolean stealing = KoLCharacter.canPickpocket(); double pocketModifier = ( 100.0 + KoLCharacter.currentNumericModifier( Modifiers.PICKPOCKET_CHANCE ) ) / 100.0; for ( int i = 0; i < items.size(); ++i ) { AdventureResult item = (AdventureResult) items.get( i ); if ( !fullString ) { if ( i == 0 ) { buffer.append( "<br>" ); } else { buffer.append( ", " ); } buffer.append( item.getName() ); continue; } buffer.append( "<br>" ); // Certain items can be increased by other bonuses than just item drop int itemId = item.getItemId(); double itemBonus = 0.0; if ( ItemDatabase.isFood( itemId ) ) { itemBonus += KoLCharacter.currentNumericModifier( Modifiers.FOODDROP ) / 100.0; } else if ( ItemDatabase.isBooze( itemId ) ) { itemBonus += KoLCharacter.currentNumericModifier( Modifiers.BOOZEDROP ) / 100.0; } else if ( ItemDatabase.isCandyItem( itemId ) ) { itemBonus += KoLCharacter.currentNumericModifier( Modifiers.CANDYDROP ) / 100.0; } else if ( ItemDatabase.isEquipment( itemId ) ) { itemBonus += KoLCharacter.currentNumericModifier( Modifiers.GEARDROP ) / 100.0; if ( ItemDatabase.isHat( itemId ) ) { itemBonus += KoLCharacter.currentNumericModifier( Modifiers.HATDROP ) / 100.0; } else if ( ItemDatabase.isWeapon( itemId ) ) { itemBonus += KoLCharacter.currentNumericModifier( Modifiers.WEAPONDROP ) / 100.0; } else if ( ItemDatabase.isOffHand( itemId ) ) { itemBonus += KoLCharacter.currentNumericModifier( Modifiers.OFFHANDDROP ) / 100.0; } else if ( ItemDatabase.isShirt( itemId ) ) { itemBonus += KoLCharacter.currentNumericModifier( Modifiers.SHIRTDROP ) / 100.0; } else if ( ItemDatabase.isPants( itemId ) ) { itemBonus += KoLCharacter.currentNumericModifier( Modifiers.PANTSDROP ) / 100.0; } else if ( ItemDatabase.isAccessory( itemId ) ) { itemBonus += KoLCharacter.currentNumericModifier( Modifiers.ACCESSORYDROP ) / 100.0; } } double stealRate = Math.min( ( (Double) pocketRates.get( i ) ).doubleValue() * pocketModifier, 1.0 ); int rawDropRate = item.getCount() >> 16; double dropRate = Math.min( rawDropRate * ( itemModifier + itemBonus ), 100.0 ); double effectiveDropRate = stealRate * 100.0 + ( 1.0 - stealRate ) * dropRate; String rateRaw = this.format( rawDropRate ); String rate1 = this.format( dropRate ); String rate2 = this.format( effectiveDropRate ); buffer.append( item.getName() ); switch ( (char) item.getCount() & 0xFFFF ) { case '0': buffer.append( " (unknown drop rate)" ); break; case 'n': if ( rawDropRate > 0 ) { buffer.append( " " ); buffer.append( rate1 ); buffer.append( "% (no pickpocket)" ); } else { buffer.append( " (no pickpocket, unknown drop rate)" ); } break; case 'c': if ( rawDropRate > 0 ) { buffer.append( " " ); buffer.append( rate1 ); buffer.append( "% (conditional)" ); } else { buffer.append( " (conditional, unknown drop rate)" ); } break; case 'f': buffer.append( " " ); buffer.append( rateRaw ); buffer.append( "% (no modifiers)" ); break; case 'p': if ( stealing && rawDropRate > 0 ) { buffer.append( " " ); buffer.append( Math.min( rawDropRate * pocketModifier, 100.0 ) ); buffer.append( "% (pickpocket only)" ); } else { buffer.append( " (pickpocket only, unknown rate)" ); } break; case 'a': buffer.append( " (stealable accordion)" ); break; default: if ( stealing ) { buffer.append( " " ); buffer.append( rate2 ); buffer.append( "% (" ); buffer.append( this.format( stealRate * 100.0 ) ); buffer.append( "% steal, " ); buffer.append( rate1 ); buffer.append( "% drop)" ); } else { buffer.append( " " ); buffer.append( rate1 ); buffer.append( "%" ); } } } } public static final double getDropRateModifier() { if ( AreaCombatData.lastDropMultiplier != 0.0 && KoLCharacter.getItemDropPercentAdjustment() == AreaCombatData.lastDropModifier ) { return AreaCombatData.lastDropMultiplier; } AreaCombatData.lastDropModifier = KoLCharacter.getItemDropPercentAdjustment(); AreaCombatData.lastDropMultiplier = Math.max( 0.0, ( 100.0 + AreaCombatData.lastDropModifier ) / 100.0 ); return AreaCombatData.lastDropMultiplier; } public static final String elementColor( final Element element ) { switch ( element ) { case HOT: return "#ff0000"; case COLD: return "#0000ff"; case STENCH: return "#008000"; case SPOOKY: return "#808080"; case SLEAZE: return "#8a2be2"; case SLIME: return "#006400"; default: return "#000000"; } } public static final double hitPercent( final int attack, final int defense ) { // ( (Attack - Defense) / 18 ) * 100 + 50 = Hit% double percent = 100.0 * ( attack - defense ) / 18 + 50.0; if ( percent < 0.0 ) { return 0.0; } if ( percent > 100.0 ) { return 100.0; } return percent; } public static final int perfectHit( final int attack, final int defense ) { return attack - defense - 9; } public String getZone() { return zone; } private static final int adjustConditionalWeighting( String zone, String monster, int weighting ) { // Bossbat can appear on 4th fight, and will always appear on the 8th fight if ( zone.equals( "The Boss Bat's Lair" ) ) { int bossTurns = AdventureSpentDatabase.getTurns( zone ); if ( monster.equals( "Boss Bat" ) ) { return bossTurns > 3 && !QuestDatabase.isQuestLaterThan( Quest.BAT, "step3" ) ? 1 : 0; } else { return bossTurns > 7 || QuestDatabase.isQuestLaterThan( Quest.BAT, "step3" ) ? -4 : 1; } } else if ( zone.equals( "The Hidden Park" ) ) { if ( monster.equals( "pygmy janitor" ) && Preferences.getInteger( "relocatePygmyJanitor" ) != KoLCharacter.getAscensions() ) { return -4; } if ( monster.equals( "pygmy witch lawyer" ) && Preferences.getInteger( "relocatePygmyLawyer" ) != KoLCharacter.getAscensions() ) { return -4; } } else if ( zone.equals( "The Hidden Apartment Building" ) || zone.equals( "The Hidden Hospital" ) || zone.equals( "The Hidden Office Building" ) || zone.equals( "The Hidden Bowling Alley" ) ) { if ( monster.equals( "pygmy janitor" ) && Preferences.getInteger( "relocatePygmyJanitor" ) == KoLCharacter.getAscensions() ) { return -4; } if ( monster.equals( "pygmy witch lawyer" ) && Preferences.getInteger( "relocatePygmyLawyer" ) == KoLCharacter.getAscensions() ) { return -4; } if ( monster.equals( "drunk pygmy" ) && Preferences.getInteger( "_drunkPygmyBanishes" ) >= 11 ) { return -4; } } else if ( zone.equals( "The Fungal Nethers" ) ) { if ( monster.equals( "muscular mushroom guy" ) ) { return KoLCharacter.getClassType() == KoLCharacter.SEAL_CLUBBER ? 1 : 0; } if ( monster.equals( "armored mushroom guy" ) ) { return KoLCharacter.getClassType() == KoLCharacter.TURTLE_TAMER ? 1 : 0; } if ( monster.equals( "wizardly mushroom guy" ) ) { return KoLCharacter.getClassType() == KoLCharacter.PASTAMANCER ? 1 : 0; } if ( monster.equals( "fiery mushroom guy" ) ) { return KoLCharacter.getClassType() == KoLCharacter.SAUCEROR ? 1 : 0; } if ( monster.equals( "dancing mushroom guy" ) ) { return KoLCharacter.getClassType() == KoLCharacter.DISCO_BANDIT ? 1 : 0; } if ( monster.equals( "wailing mushroom guy" ) ) { return KoLCharacter.getClassType() == KoLCharacter.ACCORDION_THIEF ? 1 : 0; } } else if ( zone.equals( "Pirates of the Garbage Barges" ) ) { if ( monster.equals( "flashy pirate" ) && !Preferences.getBoolean( "dinseyGarbagePirate" ) ) { return 0; } } else if ( zone.equals( "Uncle Gator's Country Fun-Time Liquid Waste Sluice" ) ) { if ( monster.equals( "nasty bear" ) && QuestDatabase.isQuestStep( Quest.NASTY_BEARS, "step1" ) ) { return 1; } } else if ( zone.equals( "Throne Room" ) ) { if ( monster.equals( "Knob Goblin King" ) && QuestDatabase.isQuestFinished( Quest.GOBLIN ) ) { return 0; } } else if ( zone.equals( "The Defiled Alcove" ) ) { int evilness = Preferences.getInteger( "cyrptAlcoveEvilness" ); if ( monster.equals( "conjoined zmombie" ) ) { return evilness > 0 && evilness <= 25 ? 1 : 0; } else if ( !monster.equals( "modern zmobie" ) ) { return evilness > 25 ? 1 : 0; } } else if ( zone.equals( "The Defiled Cranny" ) ) { int evilness = Preferences.getInteger( "cyrptCrannyEvilness" ); if ( monster.equals( "huge ghuol" ) ) { return evilness > 0 && evilness <= 25 ? 1 : 0; } else if ( monster.equals( "gaunt ghuol" ) || monster.equals( "gluttonous ghuol" ) ) { return evilness > 25 ? 1 : 0; } } else if ( zone.equals( "The Defiled Niche" ) ) { int evilness = Preferences.getInteger( "cyrptNicheEvilness" ); if ( monster.equals( "gargantulihc" ) ) { return evilness > 0 && evilness <= 25 ? 1 : 0; } else { return evilness > 25 ? 1 : 0; } } else if ( zone.equals( "The Defiled Nook" ) ) { int evilness = Preferences.getInteger( "cyrptNookEvilness" ); if ( monster.equals( "giant skeelton" ) ) { return evilness > 0 && evilness <= 25 ? 1 : 0; } else { return evilness > 25 ? 1 : 0; } } else if ( zone.equals( "Haert of the Cyrpt" ) ) { if ( monster.equals( "Bonerdagon" ) && QuestDatabase.isQuestLaterThan( Quest.CYRPT, QuestDatabase.STARTED ) ) { return 0; } } else if ( zone.equals( "The F'c'le" ) ) { if ( monster.equals( "clingy pirate (female)" ) ) { return KoLCharacter.getGender() == KoLCharacter.MALE ? 1 : 0; } else if ( monster.equals( "clingy pirate (male)" ) ) { return KoLCharacter.getGender() == KoLCharacter.FEMALE ? 1 : 0; } } else if ( zone.equals( "Summoning Chamber" ) ) { if ( monster.equals( "Lord Spookyraven" ) && QuestDatabase.isQuestFinished( Quest.MANOR ) ) { return 0; } } else if ( zone.equals( "An Overgrown Shrine (Northeast)" ) ) { // Assume lianas are dealt with once Apartment opened. Player may leave without doing so, but that's // abit niche for me to care! if ( monster.equals( "dense liana" ) && Preferences.getInteger( "hiddenApartmentProgress" ) > 0 ) { return 0; } } else if ( zone.equals( "An Overgrown Shrine (Northwest)" ) ) { // Assume lianas are dealt with once Office opened. Player may leave without doing so, but that's // abit niche for me to care! if ( monster.equals( "dense liana" ) && Preferences.getInteger( "hiddenOfficeProgress" ) > 0 ) { return 0; } } else if ( zone.equals( "An Overgrown Shrine (Southeast)" ) ) { // Assume lianas are dealt with once Hospital opened. Player may leave without doing so, but that's // abit niche for me to care! if ( monster.equals( "dense liana" ) && Preferences.getInteger( "hiddenHospitalProgress" ) > 0 ) { return 0; } } else if ( zone.equals( "An Overgrown Shrine (Southwest)" ) ) { // Assume lianas are dealt with once Bowling Alley opened. Player may leave without doing so, but that's // abit niche for me to care! if ( monster.equals( "dense liana" ) && Preferences.getInteger( "hiddenBowlingAlleyProgress" ) > 0 ) { return 0; } } else if ( zone.equals( "A Massive Ziggurat" ) ) { // Assume lianas dealt with after 3 turns, won't always be right, but this is a bit niche for special tracking int zoneTurns = AdventureSpentDatabase.getTurns( zone ); if ( monster.equals( "dense liana" ) && ( zoneTurns >= 3 || QuestDatabase.isQuestFinished( Quest.WORSHIP ) ) ) { return 0; } else if ( monster.equals( "Protector Spectre" ) && QuestDatabase.isQuestStep( Quest.WORSHIP, "step4" ) ) { return 1; } } else if ( zone.equals( "Oil Peak" ) ) { int monsterLevel = (int) KoLCharacter.currentNumericModifier( Modifiers.MONSTER_LEVEL ); if ( monster.equals( "oil slick" ) ) { return monsterLevel < 20 ? 1 : 0; } else if ( monster.equals( "oil tycoon" ) ) { return monsterLevel >= 20 && monsterLevel < 50 ? 1 : 0; } else if ( monster.equals( "oil baron" ) ) { return monsterLevel >= 50 && monsterLevel < 100 ? 1 : 0; } else if ( monster.equals( "oil cartel" ) ) { return monsterLevel >= 100 ? 1 : 0; } } else if ( zone.equals( "Fastest Adventurer Contest" ) ) { int opponentsLeft = Preferences.getInteger( "nsContestants1" ); if ( monster.equals( "Tasmanian Dervish" ) ) { return opponentsLeft == 1 ? 1 : 0; } else { return opponentsLeft > 1 ? 1 : 0; } } else if ( zone.equals( "Strongest Adventurer Contest" ) ) { int opponentsLeft = Preferences.getString( "nsChallenge1" ).equals( "Muscle" ) ? Preferences.getInteger( "nsContestants2" ) : 0; if ( monster.equals( "Mr. Loathing" ) ) { return opponentsLeft == 1 ? 1 : 0; } else { return opponentsLeft > 1 ? 1 : 0; } } else if ( zone.equals( "Smartest Adventurer Contest" ) ) { int opponentsLeft = Preferences.getString( "nsChallenge1" ).equals( "Mysticality" ) ? Preferences.getInteger( "nsContestants2" ) : 0; if ( monster.equals( "The Mastermind" ) ) { return opponentsLeft == 1 ? 1 : 0; } else { return opponentsLeft > 1 ? 1 : 0; } } else if ( zone.equals( "Smoothest Adventurer Contest" ) ) { int opponentsLeft = Preferences.getString( "nsChallenge1" ).equals( "Muscle" ) ? Preferences.getInteger( "nsContestants2" ) : 0; if ( monster.equals( "Seannery the Conman" ) ) { return opponentsLeft == 1 ? 1 : 0; } else { return opponentsLeft > 1 ? 1 : 0; } } else if ( zone.equals( "Coldest Adventurer Contest" ) ) { int opponentsLeft = Preferences.getString( "nsChallenge2" ).equals( "cold" ) ? Preferences.getInteger( "nsContestants3" ) : 0; if ( monster.equals( "Mrs. Freeze" ) ) { return opponentsLeft == 1 ? 1 : 0; } else { return opponentsLeft > 1 ? 1 : 0; } } else if ( zone.equals( "Hottest Adventurer Contest" ) ) { int opponentsLeft = Preferences.getString( "nsChallenge2" ).equals( "hot" ) ? Preferences.getInteger( "nsContestants3" ) : 0; if ( monster.equals( "Mrs. Freeze" ) ) { return opponentsLeft == 1 ? 1 : 0; } else { return opponentsLeft > 1 ? 1 : 0; } } else if ( zone.equals( "Sleaziest Adventurer Contest" ) ) { int opponentsLeft = Preferences.getString( "nsChallenge2" ).equals( "sleaze" ) ? Preferences.getInteger( "nsContestants3" ) : 0; if ( monster.equals( "Leonard" ) ) { return opponentsLeft == 1 ? 1 : 0; } else { return opponentsLeft > 1 ? 1 : 0; } } else if ( zone.equals( "Spookiest Adventurer Contest" ) ) { int opponentsLeft = Preferences.getString( "nsChallenge2" ).equals( "spooky" ) ? Preferences.getInteger( "nsContestants3" ) : 0; if ( monster.equals( "Arthur Frankenstein" ) ) { return opponentsLeft == 1 ? 1 : 0; } else { return opponentsLeft > 1 ? 1 : 0; } } else if ( zone.equals( "Stinkiest Adventurer Contest" ) ) { int opponentsLeft = Preferences.getString( "nsChallenge2" ).equals( "stinky" ) ? Preferences.getInteger( "nsContestants3" ) : 0; if ( monster.equals( "Odorous Humongous" ) ) { return opponentsLeft == 1 ? 1 : 0; } else { return opponentsLeft > 1 ? 1 : 0; } } else if ( zone.equals( "The Nemesis' Lair" ) ) { int lairTurns = AdventureSpentDatabase.getTurns( zone ); if ( monster.equals( "hellseal guardian" ) ) { return KoLCharacter.getClassType() == KoLCharacter.SEAL_CLUBBER ? 1 : 0; } else if ( monster.equals( "Gorgolok, the Infernal Seal (Inner Sanctum)" ) ) { return KoLCharacter.getClassType() == KoLCharacter.SEAL_CLUBBER && lairTurns >= 4 ? 1 : 0; } else if ( monster.equals( "warehouse worker" ) ) { return KoLCharacter.getClassType() == KoLCharacter.TURTLE_TAMER ? 1 : 0; } else if ( monster.equals( "Stella, the Turtle Poacher (Inner Sanctum)" ) ) { return KoLCharacter.getClassType() == KoLCharacter.TURTLE_TAMER && lairTurns >= 4 ? 1 : 0; } else if ( monster.equals( "evil spaghetti cult zealot" ) ) { return KoLCharacter.getClassType() == KoLCharacter.PASTAMANCER ? 1 : 0; } else if ( monster.equals( "Spaghetti Elemental (Inner Sanctum)" ) ) { return KoLCharacter.getClassType() == KoLCharacter.PASTAMANCER && lairTurns >= 4 ? 1 : 0; } else if ( monster.equals( "security slime" ) ) { return KoLCharacter.getClassType() == KoLCharacter.SAUCEROR ? 1 : 0; } else if ( monster.equals( "Lumpy, the Sinister Sauceblob (Inner Sanctum)" ) ) { return KoLCharacter.getClassType() == KoLCharacter.SAUCEROR && lairTurns >= 4 ? 1 : 0; } else if ( monster.equals( "daft punk" ) ) { return KoLCharacter.getClassType() == KoLCharacter.DISCO_BANDIT ? 1 : 0; } else if ( monster.equals( "Spirit of New Wave (Inner Sanctum)" ) ) { return KoLCharacter.getClassType() == KoLCharacter.DISCO_BANDIT && lairTurns >= 4 ? 1 : 0; } else if ( monster.equals( "mariachi bruiser" ) ) { return KoLCharacter.getClassType() == KoLCharacter.ACCORDION_THIEF ? 1 : 0; } else if ( monster.equals( "Somerset Lopez, Dread Mariachi (Inner Sanctum)" ) ) { return KoLCharacter.getClassType() == KoLCharacter.ACCORDION_THIEF && lairTurns >= 4 ? 1 : 0; } } else if ( zone.equals( "The Slime Tube" ) ) { int monsterLevel = (int) KoLCharacter.currentNumericModifier( Modifiers.MONSTER_LEVEL ); if ( monster.equals( "Slime" ) ) { return monsterLevel <= 100 ? 1 : 0; } else if ( monster.equals( "Slime Hand" ) ) { return monsterLevel > 100 && monsterLevel <= 300 ? 1 : 0; } else if ( monster.equals( "Slime Mouth" ) ) { return monsterLevel > 300 && monsterLevel <= 600 ? 1 : 0; } else if ( monster.equals( "Slime Construct" ) ) { return monsterLevel > 600 && monsterLevel <= 1000 ? 1 : 0; } else if ( monster.equals( "Slime Colossus" ) ) { return monsterLevel > 1000 ? 1 : 0; } } else if ( zone.equals( "The Post-Mall" ) ) { int mallTurns = AdventureSpentDatabase.getTurns( zone ); if ( monster.equals( "sentient ATM" ) ) { return mallTurns == 11 ? 1 : 0; } else { return mallTurns == 11 ? -4 : 1; } } else if ( zone.equals( "Investigating a Plaintive Telegram" ) ) { String quest = Preferences.getString( "lttQuestName" ); String questStep = Preferences.getString( "questLTTQuestByWire" ); if ( monster.equals( "drunk cowpoke" ) ) { return ( quest.equals( "Missing: Fancy Man" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Help! Desperados|" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Big Gambling Tournament Announced" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Sheriff Wanted" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Madness at the Mine" ) && questStep.equals( "step1" ) ) ? 1 : 0; } else if ( monster.equals( "surly gambler" ) ) { return ( quest.equals( "Missing: Fancy Man" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Big Gambling Tournament Announced" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Sheriff Wanted" ) && questStep.equals( "step1" ) ) ? 1 : 0; } else if ( monster.equals( "wannabe gunslinger" ) ) { return ( quest.equals( "Help! Desperados|" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Big Gambling Tournament Announced" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Sheriff Wanted" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Wagon Train Escort Wanted" ) && questStep.equals( "step3" ) ) ? 1 : 0; } else if ( monster.equals( "cow cultist" ) ) { return ( quest.equals( "Missing: Pioneer Daughter" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Haunted Boneyard" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Sheriff Wanted" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Missing: Many Children" ) && questStep.equals( "step1" ) ) ? 1 : 0; } else if ( monster.equals( "hired gun" ) ) { return ( quest.equals( "Missing: Fancy Man" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Help! Desperados|" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Missing: Pioneer Daughter" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Big Gambling Tournament Announced" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Sheriff Wanted" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Missing: Many Children" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Wagon Train Escort Wanted" ) && questStep.equals( "step3" ) ) ? 1 : 0; } else if ( monster.equals( "camp cook" ) ) { return ( quest.equals( "Missing: Fancy Man" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Sheriff Wanted" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Madness at the Mine" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Wagon Train Escort Wanted" ) && questStep.equals( "step3" ) ) ? 1 : 0; } else if ( monster.equals( "skeletal gunslinger" ) ) { return ( quest.equals( "Help! Desperados|" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Haunted Boneyard" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Madness at the Mine" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Wagon Train Escort Wanted" ) && questStep.equals( "step2" ) ) ? 1 : 0; } else if ( monster.equals( "restless ghost" ) ) { return ( quest.equals( "Missing: Fancy Man" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Missing: Pioneer Daughter" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Haunted Boneyard" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Madness at the Mine" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Missing: Many Children" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Wagon Train Escort Wanted" ) && questStep.equals( "step2" ) ) ? 1 : 0; } else if ( monster.equals( "buzzard" ) ) { return ( quest.equals( "Missing: Fancy Man" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Help! Desperados|" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Missing: Pioneer Daughter" ) && questStep.equals( "step1" ) ) || ( quest.equals( "Haunted Boneyard" ) && questStep.equals( "step1" ) ) ? 1 : 0; } else if ( monster.equals( "mountain lion" ) ) { return ( quest.equals( "Missing: Fancy Man" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Help! Desperados|" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Sheriff Wanted" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Madness at the Mine" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Wagon Train Escort Wanted" ) && questStep.equals( "step1" ) ) ? 1 : 0; } else if ( monster.equals( "grizzled bear" ) ) { return ( quest.equals( "Help! Desperados|" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Madness at the Mine" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Wagon Train Escort Wanted" ) && questStep.equals( "step1" ) ) ? 1 : 0; } else if ( monster.equals( "diamondback rattler" ) ) { return ( quest.equals( "Help! Desperados|" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Big Gambling Tournament Announced" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Madness at the Mine" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Wagon Train Escort Wanted" ) && questStep.equals( "step1" ) ) ? 1 : 0; } else if ( monster.equals( "coal snake" ) ) { return ( quest.equals( "Missing: Fancy Man" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Big Gambling Tournament Announced" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Madness at the Mine" ) && questStep.equals( "step1" ) ) ? 1 : 0; } else if ( monster.equals( "frontwinder" ) ) { return ( quest.equals( "Big Gambling Tournament Announced" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Sheriff Wanted" ) && questStep.equals( "step2" ) ) ? 1 : 0; } else if ( monster.equals( "caugr" ) ) { return ( quest.equals( "Missing: Pioneer Daughter" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Missing: Many Children" ) && questStep.equals( "step3" ) ) ? 1 : 0; } else if ( monster.equals( "pyrobove" ) ) { return ( quest.equals( "Missing: Pioneer Daughter" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Missing: Many Children" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Wagon Train Escort Wanted" ) && questStep.equals( "step2" ) ) ? 1 : 0; } else if ( monster.equals( "spidercow" ) ) { return ( quest.equals( "Missing: Pioneer Daughter" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Haunted Boneyard" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Missing: Many Children" ) && questStep.equals( "step1" ) ) ? 1 : 0; } else if ( monster.equals( "moomy" ) ) { return ( quest.equals( "Haunted Boneyard" ) && questStep.equals( "step3" ) ) || ( quest.equals( "Madness at the Mine" ) && questStep.equals( "step2" ) ) || ( quest.equals( "Missing: Many Children" ) && questStep.equals( "step3" ) ) ? 1 : 0; } else if ( monster.equals( "Jeff the Fancy Skeleton" ) ) { return ( quest.equals( "Missing: Fancy Man" ) && questStep.equals( "step4" ) ) ? 1 : 0; } else if ( monster.equals( "Daisy the Unclean" ) ) { return ( quest.equals( "Missing: Pioneer Daughter" ) && questStep.equals( "step4" ) ) ? 1 : 0; } else if ( monster.equals( "Pecos Dave" ) ) { return ( quest.equals( "Help! Desperados|" ) && questStep.equals( "step4" ) ) ? 1 : 0; } else if ( monster.equals( "Pharaoh Amoon-Ra Cowtep" ) ) { return ( quest.equals( "Haunted Boneyard" ) && questStep.equals( "step4" ) ) ? 1 : 0; } else if ( monster.equals( "Snake-Eyes Glenn" ) ) { return ( quest.equals( "Big Gambling Tournament Announced" ) && questStep.equals( "step4" ) ) ? 1 : 0; } else if ( monster.equals( "Former Sheriff Dan Driscoll" ) ) { return ( quest.equals( "Sheriff Wanted" ) && questStep.equals( "step4" ) ) ? 1 : 0; } else if ( monster.equals( "unusual construct" ) ) { return ( quest.equals( "Madness at the Mine" ) && questStep.equals( "step4" ) ) ? 1 : 0; } else if ( monster.equals( "Clara" ) ) { return ( quest.equals( "Missing: Many Children" ) && questStep.equals( "step4" ) ) ? 1 : 0; } else if ( monster.equals( "Granny Hackleton" ) ) { return ( quest.equals( "Wagon Train Escort Wanted" ) && questStep.equals( "step4" ) ) ? 1 : 0; } } else if ( zone.equals( "Gingerbread Civic Center" ) || zone.equals( "Gingerbread Train Station" ) || zone.equals( "Gingerbread Industrial Zone" ) || zone.equals( "Gingerbread Upscale Retail District" ) ) { if ( monster.equals( "gingerbread pigeon" ) || monster.equals( "gingerbread rat" ) ) { return Preferences.getBoolean( "gingerSewersUnlocked" ) ? 0 : 1; } } return weighting; } public static final double superlikelyChance( String monster ) { if ( monster.equals( "screambat" ) ) { int turns = AdventureSpentDatabase.getTurns( "Guano Junction" ) + AdventureSpentDatabase.getTurns( "The Batrat and Ratbat Burrow" ) + AdventureSpentDatabase.getTurns( "The Beanbat Chamber" ); // Appears every 8 turns in relevant zones return turns > 0 && ( turns % 8 ) == 0 ? 100.0 : 0.0; } if ( monster.equals( "modern zmobie" ) && Preferences.getInteger( "cyrptAlcoveEvilness" ) > 25 ) { // Chance based on initiative double chance = 15 + KoLCharacter.getInitiativeAdjustment() / 10; return chance < 0 ? 0.0 : chance > 100 ? 100.0 : chance; } if ( monster.equals( "ninja snowman assassin" ) ) { // Do not appear without positive combat rate double combatRate = KoLCharacter.getCombatRateAdjustment(); if ( combatRate <= 0 ) { return 0; } // Guaranteed on turns 11, 21, and 31 int snowmanTurns = AdventureSpentDatabase.getTurns( "Lair of the Ninja Snowmen" ); if ( snowmanTurns == 10 || snowmanTurns == 20 || snowmanTurns == 30 ) { return 100.0; } double chance = combatRate / 2 + (double) snowmanTurns * 1.5; return chance < 0 ? 0.0 : chance > 100 ? 100.0 : chance; } if ( monster.equals( "mother hellseal" ) ) { double chance = Preferences.getInteger( "_sealScreeches" ) * 10; return chance < 0 ? 0.0 : chance > 100 ? 100.0 : chance; } if ( monster.equals( "Brick Mulligan, the Bartender" ) ) { int kokomoTurns = AdventureSpentDatabase.getTurns( "Kokomo Resort" ); // Appears every 25 turns return kokomoTurns > 0 && ( kokomoTurns % 25 ) == 0 ? 100.0 : 0.0; } return 0; } }