/** * Copyright (c) 2005-2017, KoLmafia development team * http://kolmafia.sourceforge.net/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * [1] Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * [2] Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * [3] Neither the name "KoLmafia" nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.sourceforge.kolmafia; import java.awt.Component; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.DefaultListCellRenderer; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.SwingConstants; import net.java.dev.spellcast.utilities.JComponentUtilities; 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.preferences.Preferences; import net.sourceforge.kolmafia.request.EquipmentRequest; import net.sourceforge.kolmafia.request.GenericRequest; import net.sourceforge.kolmafia.request.StandardRequest; import net.sourceforge.kolmafia.session.EquipmentManager; import net.sourceforge.kolmafia.utilities.StringUtilities; public class FamiliarData implements Comparable<FamiliarData> { public static final FamiliarData NO_FAMILIAR = new FamiliarData( -1 ); // <center>Current Familiar:<br><img src="http://images.kingdomofloathing.com/itemimages/jungman.gif" class=hand onClick='fam(165)'><br><b>Jung Grrl</b><br>20-pound Angry Jung Man (500 experience, 29,380 kills)<table><tr><td valign=center>Equipment:</td><td><img src="http://images.kingdomofloathing.com/itemimages/antsickle.gif" class=hand onClick='descitem(235040244)'></td><td valign=center>ant sickle <font size=1><a href='inv_equip.php?pwd=4438585275374d322da30a77b73cb7d5&action=unequip&type=familiarequip&terrarium=1'>[unequip]</a></font></td><td><a href='familiar.php?action=lockequip&pwd=4438585275374d322da30a77b73cb7d5'><img src="http://images.kingdomofloathing.com/itemimages/openpadlock.gif" class=hand title='This Familiar Equipment is Unlocked'></a></td><td valign=top><font size=-1><b><a class=nounder href='javascript:doc("famequiplock");'>?</a></b></font></td></tr></table><p><form name=rename action=familiar.php method=post><input type=hidden name=action value="rename"><input type=hidden name=pwd value='4438585275374d322da30a77b73cb7d5'>Change your Familiar's Name:<br><input class=text type=text size=40 maxlength=40 name=newname value="Jung Grrl"> <input class=button type=submit value="Rename"></form> private static final Pattern CURRENT_PATTERN = Pattern.compile( "Current Familiar:.*?</form>" ); // <tr class="frow expired" data-stats="1" data-other="1"><td valign=center> </td><td valign=center><img src="http://images.kingdomofloathing.com/itemimages/crayongoth.gif" class="hand fam" onClick='fam(160)'></td><td valign=top style='padding-top: .45em;'><b>Raven 'Raven' Ravengrrl</b>, the 1-pound Artistic Goth Kid (0 exp, 32,443 kills) <font size="1"><br />    <a class="fave" href="familiar.php?group=0&action=fave&famid=160&pwd=4438585275374d322da30a77b73cb7d5">[unfavorite]</a></font></td></tr> private static final Pattern FROW_PATTERN = Pattern.compile( "<tr class=\"frow .*?</tr>" ); private static final Pattern FAMILIAR_PATTERN = Pattern.compile( ".*?<img src=[^>]*?(?:images.kingdomofloathing.com|/images)/itemimages/([^\"]*?)\" class=(?:\"hand fam\"|hand) onClick='fam\\((\\d+)\\)'>.*?<b>(.*?)</b>.*?\\d+-pound (.*?) \\(([\\d,]+) (?:exp|experience|candy|candies)?, .*? kills?\\)(.*?)<(?:/tr|form)" ); private static final Pattern DESCID_PATTERN = Pattern.compile( "descitem\\((.*?)\\)" ); private static final Pattern SHRUB_TOPPER_PATTERN = Pattern.compile( "span title=\"(.*?)-heavy" ); private static final Pattern SHRUB_LIGHT_PATTERN = Pattern.compile( "Deals (.*?) damage" ); public static final AdventureResult BATHYSPHERE = ItemPool.get( ItemPool.BATHYSPHERE, 1 ); public static final AdventureResult DAS_BOOT = ItemPool.get( ItemPool.DAS_BOOT, 1 ); public static final AdventureResult DOPPELGANGER = ItemPool.get( ItemPool.FAMILIAR_DOPPELGANGER, 1 ); public static final AdventureResult FIREWORKS = ItemPool.get( ItemPool.FIREWORKS, 1 ); public static final AdventureResult FLOWER_BOUQUET = ItemPool.get( ItemPool.MAYFLOWER_BOUQUET, 1 ); public static final AdventureResult ITTAH_BITTAH_HOOKAH = ItemPool.get( ItemPool.ITTAH_BITTAH_HOOKAH, 1 ); public static final AdventureResult LEAD_NECKLACE = ItemPool.get( ItemPool.LEAD_NECKLACE, 1 ); public static final AdventureResult LIFE_PRESERVER = ItemPool.get( ItemPool.MINI_LIFE_PRESERVER, 1 ); public static final AdventureResult MOVEABLE_FEAST = ItemPool.get( ItemPool.MOVEABLE_FEAST, 1 ); public static final AdventureResult PET_SWEATER = ItemPool.get( ItemPool.PET_SWEATER, 1 ); public static final AdventureResult PUMPKIN_BUCKET = ItemPool.get( ItemPool.PUMPKIN_BUCKET, 1 ); public static final AdventureResult RAT_HEAD_BALLOON = ItemPool.get( ItemPool.RAT_BALLOON, 1 ); public static final AdventureResult SUGAR_SHIELD = ItemPool.get( ItemPool.SUGAR_SHIELD, 1 ); public static final List<DropInfo> DROP_FAMILIARS = new ArrayList<DropInfo>(); public static final List<FightInfo> FIGHT_FAMILIARS = new ArrayList<FightInfo>(); private final int id; private final String race; private boolean beeware; private String name; private int experience; private int weight; private AdventureResult item; private boolean feasted; private boolean favorite; private int charges; public FamiliarData( final int id ) { this( id, "", 1, EquipmentRequest.UNEQUIP ); } public FamiliarData( final int id, final String name, final int weight, final AdventureResult item ) { this.id = id; this.name = name; String race = FamiliarDatabase.getFamiliarName( id ); this.race = ( id == -1 || race == null ) ? "(none)" : race; this.beeware = this.race.contains( "b" ) || this.race.contains( "B" ); this.weight = weight; this.item = item; this.feasted = false; this.charges = 0; } private FamiliarData( final Matcher dataMatcher ) { this.id = StringUtilities.parseInt( dataMatcher.group( 2 ) ); this.race = dataMatcher.group( 4 ); this.beeware = this.race.contains( "b" ) || this.race.contains( "B" ); String image = dataMatcher.group( 1 ); FamiliarDatabase.registerFamiliar( this.id, this.race, image ); this.update( dataMatcher ); } private final void update( final Matcher dataMatcher ) { this.name = dataMatcher.group( 3 ); this.experience = StringUtilities.parseInt( dataMatcher.group( 5 ) ); this.setWeight(); String itemData = dataMatcher.group( 6 ); this.item = FamiliarData.parseFamiliarItem( this.id, itemData ); this.favorite = itemData.contains( "[unfavorite]" ); } public static final void reset() { FamiliarData.loadDropFamiliars(); FamiliarData.loadFightFamiliars(); FamiliarData.checkShrub(); } public final boolean canEquip() { // Familiars cannot be equipped by most Avatar classes if ( KoLCharacter.inAxecore() || KoLCharacter.isJarlsberg() || KoLCharacter.isSneakyPete() || KoLCharacter.isEd() ) { return false; } // Familiars with a "B" in their race cannot be equipped in Beecore if ( KoLCharacter.inBeecore() && this.beeware ) { return false; } // Unallowed familiars cannot be equipped if ( !StandardRequest.isAllowed( "Familiars", this.race ) ) { return false; } return true; } public final int getTotalExperience() { return this.experience; } public final void addCombatExperience( String responseText ) { if ( this.id == FamiliarPool.STOCKING_MIMIC ) { // Doesn't automatically gain experience from winning a combat return; } double experienceModifier = KoLCharacter.currentNumericModifier( Modifiers.FAMILIAR_EXP ); int itemId = getItem().getItemId(); if ( itemId == ItemPool.MAYFLOWER_BOUQUET ) { String modifierName = Modifiers.getModifierName( Modifiers.FAMILIAR_EXP ); double itemModifier = Modifiers.getNumericModifier( "Item", itemId, modifierName ); experienceModifier -= itemModifier; if ( responseText.contains( "offer some words of encouragement and support" ) ) { experienceModifier += 3; } } this.experience += 1 + experienceModifier; if ( KoLCharacter.hasSkill( "Testudinal Teachings" ) ) { this.addTestTeachExperience(); } this.setWeight(); } public final void addNonCombatExperience( int exp ) { this.experience += exp; this.setWeight(); } public final void addTestTeachExperience() { String rawTTPref = Preferences.getString( "testudinalTeachings" ); String[] splitTTPref = rawTTPref.split( "\\|" ); // Check Familiar Testudinal Teachings experience for ( int i = 0; i < splitTTPref.length; ++i ) { String[] it = splitTTPref[ i ].split( ":" ); if ( it.length == 2 ) { if ( this.id == Integer.parseInt( it[ 0 ] ) ) { int newCount = Integer.parseInt( it[ 1 ] ) + 1; if ( newCount >= 6 ) { this.experience++ ; newCount = 0; } String newTTProperty = it[ 0 ] + ":" + String.valueOf( newCount ); String newTTPref = StringUtilities.globalStringReplace( rawTTPref, splitTTPref[ i ], newTTProperty ); Preferences.setString( "testudinalTeachings", newTTPref ); return; } } } // Familiar not found, so add it String delimiter = ""; if ( rawTTPref.length() > 0 ) { delimiter = "|"; } String newTTPref = rawTTPref + delimiter + String.valueOf( this.id ) + ":1"; Preferences.setString( "testudinalTeachings", newTTPref ); } public final void recognizeCombatUse() { int singleFamiliarRun = getSingleFamiliarRun(); if ( singleFamiliarRun == 0 ) { Preferences.setInteger( "singleFamiliarRun", this.id ); } else if ( this.id != singleFamiliarRun ) { Preferences.setInteger( "singleFamiliarRun", -1 ); } } public final boolean isUnexpectedFamiliar() { if ( this.id == -1 && KoLCharacter.getCurrentRun() == 0 ) { return true; } int singleFamiliarRun = getSingleFamiliarRun(); return singleFamiliarRun > 0 && this.id != singleFamiliarRun; } public static final int getSingleFamiliarRun() { int singleFamiliarRun = Preferences.getInteger( "singleFamiliarRun" ); if ( singleFamiliarRun == 0 ) { for ( FamiliarData familiar : KoLCharacter.getFamiliarList() ) { if ( familiar.getTotalExperience() != 0 ) { if ( singleFamiliarRun != 0 ) { singleFamiliarRun = -1; break; } singleFamiliarRun = familiar.getId(); } } Preferences.setInteger( "singleFamiliarRun", singleFamiliarRun ); } return singleFamiliarRun; } private final void setWeight() { int max = this.id == FamiliarPool.STOCKING_MIMIC ? 100 : 20; this.weight = Math.max( Math.min( max, (int) Math.sqrt( this.experience ) ), 1 ); } public final void checkWeight( final int weight, final boolean feasted ) { // Called from CharPaneRequest with KoL's idea of current familiar's weight and "well-fed" status. // This does NOT include "hidden" weight modifiers // Sanity check: don't adjust NO_FAMILIAR if ( this.id == -1 ) { return; } this.feasted = feasted; // Get modified weight excluding hidden weight modifiers int delta = weight - this.getModifiedWeight( false, true ); if ( delta != 0 ) { // The following is informational, not an error, but it confuses people, so don't print it. // RequestLogger.printLine( "Adjusting familiar weight by " + delta + " pound" + ( delta == 1 ? "" : "s" ) ); this.weight += delta; } } public final void setName( final String name ) { this.name = name; } private static final AdventureResult parseFamiliarItem( final int id, final String text ) { if ( !text.contains( "<img" ) ) { return EquipmentRequest.UNEQUIP; } Matcher itemMatcher = DESCID_PATTERN.matcher( text ); if ( !itemMatcher.find() ) { return EquipmentRequest.UNEQUIP; } String itemName = ItemDatabase.getItemName( itemMatcher.group( 1 ) ); if ( itemName == null ) { return EquipmentRequest.UNEQUIP; } return ItemPool.get( itemName, 1 ); } public static final void registerFamiliarData( final String responseText ) { // Assume he has no familiar FamiliarData current = FamiliarData.NO_FAMILIAR; if ( !responseText.contains( "You do not currently have a familiar" ) ) { Matcher currentMatcher = FamiliarData.CURRENT_PATTERN.matcher( responseText ); if ( currentMatcher.find() ) { Matcher familiarMatcher = FamiliarData.FAMILIAR_PATTERN.matcher( currentMatcher.group() ); if ( familiarMatcher.find() ) { current = FamiliarData.registerFamiliar( familiarMatcher ); // There's no indication of whether your current familiar is a // favorite or not. Safest to assume it is: current.setFavorite( true ); } } } Matcher frowMatcher = FamiliarData.FROW_PATTERN.matcher( responseText ); while ( frowMatcher.find() ) { String frow = frowMatcher.group(); if ( frow.contains( "\"frow expired\"" ) ) { continue; } Matcher familiarMatcher = FamiliarData.FAMILIAR_PATTERN.matcher( frow ); if ( !familiarMatcher.find() ) { continue; } FamiliarData familiar = FamiliarData.registerFamiliar( familiarMatcher ); if ( frow.contains( "kick out of Crown of Thrones" ) ) { KoLCharacter.setEnthroned( familiar ); } else if ( frow.contains( "kick out of Buddy Bjorn" ) ) { KoLCharacter.setBjorned( familiar ); } } int currentId = current.getId(); if ( currentId == FamiliarPool.REANIMATOR && currentId != KoLCharacter.getFamiliar().getId() ) { // Visit chat to familiar page to get current parts KoLmafia.updateDisplay( "Getting current parts information for " + current.getName() + " the " + current.getRace() + "." ); RequestThread.postRequest( new GenericRequest( "main.php?talktoreanimator=1" ) ); } KoLCharacter.setFamiliar( current ); EquipmentManager.setEquipment( EquipmentManager.FAMILIAR, current.getItem() ); FamiliarData.checkLockedItem( responseText ); } private static final FamiliarData registerFamiliar( final Matcher matcher ) { String race = matcher.group( 4 ); FamiliarData familiar = KoLCharacter.findFamiliar( race ); if ( familiar == null ) { // Add new familiar to list familiar = new FamiliarData( matcher ); KoLCharacter.addFamiliar( familiar ); } else { // Update existing familiar familiar.update( matcher ); } return familiar; } public static final FamiliarData registerFamiliar( final int id, final int experience ) { if ( id == 0 ) { return FamiliarData.NO_FAMILIAR; } FamiliarData familiar = KoLCharacter.findFamiliar( id ); if ( familiar == null ) { // Add new familiar to list familiar = new FamiliarData( id ); KoLCharacter.addFamiliar( familiar ); } familiar.experience = experience; familiar.setWeight(); return familiar; } private static final Pattern LOCK_PATTERN = Pattern.compile( "familiar.php\\?action=lockequip.*'This Familiar Equipment is (Locked|Unlocked)'" ); public static final void checkLockedItem( final String responseText ) { Matcher lockMatcher = FamiliarData.LOCK_PATTERN.matcher( responseText ); boolean locked = lockMatcher.find() && lockMatcher.group( 1 ).equals( "Locked" ); EquipmentManager.lockFamiliarItem( locked ); } public int getId() { return this.id; } public boolean getFeasted() { return this.feasted; } public void setItem( final AdventureResult item ) { if ( this.id < 1 ) { return; } if ( this.item != null && item != null && this.item.getItemId() == item.getItemId() ) { return; } if ( !KoLmafia.isRefreshing() && this.item != null && this.item != EquipmentRequest.UNEQUIP ) { AdventureResult.addResultToList( KoLConstants.inventory, this.item.getInstance( 1 ) ); } if ( item != null && item != EquipmentRequest.UNEQUIP ) { this.item = item.getInstance( 1 ); } else { this.item = item; } if ( !KoLmafia.isRefreshing() && item != null && item != EquipmentRequest.UNEQUIP ) { AdventureResult.addResultToList( KoLConstants.inventory, item.getInstance( -1 ) ); } if ( !KoLmafia.isRefreshing() ) { switch ( this.id ) { case FamiliarPool.HATRACK: // Mad Hatrack EquipmentManager.updateEquipmentList( EquipmentManager.HAT ); EquipmentManager.updateEquipmentList( EquipmentManager.FAMILIAR ); break; case FamiliarPool.HAND: // Disembodied Hand EquipmentManager.updateEquipmentList( EquipmentManager.WEAPON ); EquipmentManager.updateEquipmentList( EquipmentManager.OFFHAND ); EquipmentManager.updateEquipmentList( EquipmentManager.FAMILIAR ); break; case FamiliarPool.SCARECROW: // Fancypants Scarecrow EquipmentManager.updateEquipmentList( EquipmentManager.PANTS ); EquipmentManager.updateEquipmentList( EquipmentManager.FAMILIAR ); break; default: // Everything else EquipmentManager.updateEquipmentList( EquipmentManager.FAMILIAR ); break; } EquipmentManager.lockFamiliarItem(); } } public AdventureResult getItem() { return this.item == null ? EquipmentRequest.UNEQUIP : this.item; } public void setWeight( final int weight ) { this.weight = weight; } public int getWeight() { return this.weight; } public int getModifiedWeight() { return this.getModifiedWeight( true, true ); } public int getModifiedWeight( final boolean includeEquipment ) { return this.getModifiedWeight( true, includeEquipment ); } private int getModifiedWeight( final boolean includeHidden, final boolean includeEquipment ) { // Start with base weight of familiar int weight = this.weight; // Get current fixed and percent weight modifiers Modifiers current = KoLCharacter.getCurrentModifiers(); double fixed = current.get( Modifiers.FAMILIAR_WEIGHT ); double hidden = current.get( Modifiers.HIDDEN_FAMILIAR_WEIGHT ); double percent = current.get( Modifiers.FAMILIAR_WEIGHT_PCT ); FamiliarData familiar = KoLCharacter.getFamiliar(); // If this is not the current familiar or we are not // considering equipment, subtract weight granted by equipment if ( this != familiar || !includeEquipment ) { // Subtract modifiers for current familiar's equipment AdventureResult item = familiar.getItem(); if ( item != EquipmentRequest.UNEQUIP ) { Modifiers mods = Modifiers.getItemModifiers( item.getItemId() ); if ( mods != null ) { fixed -= mods.get( Modifiers.FAMILIAR_WEIGHT ); hidden -= mods.get( Modifiers.HIDDEN_FAMILIAR_WEIGHT ); percent -= mods.get( Modifiers.FAMILIAR_WEIGHT_PCT ); } } } // If this is not the current familiar and we are considering // equipment, add weight granted by equipment. if ( this != familiar && includeEquipment ) { // Add modifiers for this familiar's equipment item = this.getItem(); if ( item != EquipmentRequest.UNEQUIP ) { Modifiers mods = Modifiers.getItemModifiers( item.getItemId() ); if ( mods != null ) { fixed += mods.get( Modifiers.FAMILIAR_WEIGHT ); hidden += mods.get( Modifiers.HIDDEN_FAMILIAR_WEIGHT ); percent += mods.get( Modifiers.FAMILIAR_WEIGHT_PCT ); } } } // Add in fixed modifiers weight += (int) fixed; // If want to include hidden modifiers, do so now if ( includeHidden ) { weight += (int) hidden; } // Adjust by percent modifiers if ( percent != 0.0f ) { weight = (int) Math.floor( weight + weight * ( percent / 100.0f ) ); } // If the familiar is well-fed, it's 10 lbs. heavier if ( this.feasted ) { weight += 10; } // check if the familiar has a weight cap int cap = (int) current.get( Modifiers.FAMILIAR_WEIGHT_CAP ); int cappedWeight = ( cap == 0 ) ? weight : Math.min( weight, cap ); return Math.max( 1, cappedWeight ); } public static final int itemWeightModifier( final int itemId ) { Modifiers mods = Modifiers.getItemModifiers( itemId ); return mods == null ? 0 : (int) mods.get( Modifiers.FAMILIAR_WEIGHT ); } public final int getUncappedWeight() { if ( this.id == FamiliarPool.HATRACK || this.id == FamiliarPool.SCARECROW ) { return Math.max( Math.min( 20, (int) Math.sqrt( this.experience ) ), 1 ); } return this.weight; } public String getName() { return this.name; } public String getRace() { return this.race; } public boolean getFavorite() { return this.favorite; } public void setFavorite( boolean favor ) { this.favorite = favor; } public String getImageLocation() { String image = FamiliarDatabase.getFamiliarImageLocation( this.id ); int index = image.lastIndexOf( "/" ); return index == -1 ? image : image.substring( index + 1 ); } public void setCharges( int charges ) { this.charges = charges; } public int getCharges() { return this.charges; } public boolean trainable() { if ( this.id == -1 ) { return false; } int skills[] = FamiliarDatabase.getFamiliarSkills( this.id ); // If any skill is greater than 0, we can train in that event for ( int i = 0; i < skills.length; ++i ) { if ( skills[ i ] > 0 ) { return true; } } return false; } public boolean waterBreathing() { return FamiliarDatabase.isUnderwaterType( this.id ); } public boolean canCarry() { switch ( this.id ) { case FamiliarPool.DOPPEL: case FamiliarPool.CHAMELEON: case FamiliarPool.HATRACK: case FamiliarPool.HAND: case FamiliarPool.SCARECROW: return false; } return true; } public static class DropInfo { public final int id; public final AdventureResult dropItem; public final String dropName; public final String dropTracker; public final int dailyCap; public DropInfo( int id, int dropId, String dropName, String dropTracker, int dailyCap ) { this.id = id; this.dropItem = dropId < 0 ? null : ItemPool.get( dropId ); this.dropName = dropName; this.dropTracker = dropTracker; this.dailyCap = dailyCap; } public int dropsToday() { return Preferences.getInteger( this.dropTracker ); } public boolean hasDropsLeft() { return this.dropsToday() < this.dailyCap; } } // TODO: (philosophical) Decide whether free fights count as // meta-drops, or if these should both extend from a base abstract // class for familiar counters. public static class FightInfo extends DropInfo { public FightInfo( int id, String dropTracker, int dailyCap ) { super( id, -1, "combats", dropTracker, dailyCap ); } public int fightsToday() { return this.dropsToday(); } public int hasFightsLeft() { return this.dropsToday(); } } private static final void loadFightFamiliars() { FIGHT_FAMILIARS.clear(); FIGHT_FAMILIARS.add( new FightInfo( FamiliarPool.HIPSTER, "_hipsterAdv", 7 ) ); FIGHT_FAMILIARS.add( new FightInfo( FamiliarPool.ARTISTIC_GOTH_KID, "_hipsterAdv", 7 ) ); FIGHT_FAMILIARS.add( new FightInfo( FamiliarPool.MACHINE_ELF, "_machineTunnelsAdv", 5 ) ); } private static final void loadDropFamiliars() { DROP_FAMILIARS.clear(); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.PIXIE, ItemPool.ABSINTHE, "absinthe", "_absintheDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.SANDWORM, ItemPool.AGUA_DE_VIDA, "agua", "_aguaDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.BADGER, ItemPool.ASTRAL_MUSHROOM, "astral", "_astralDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.KLOOP, ItemPool.DEVILISH_FOLIO, "folio", "_kloopDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.LLAMA, ItemPool.GONG, "gong", "_gongDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.GROOSE, ItemPool.GROOSE_GREASE, "grease", "_grooseDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.TRON, ItemPool.GG_TOKEN, "token", "_tokenDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.ALIEN, ItemPool.TRANSPORTER_TRANSPONDER, "transponder", "_transponderDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.UNCONSCIOUS_COLLECTIVE, ItemPool.UNCONSCIOUS_COLLECTIVE_DREAM_JAR, "dream jar", "_dreamJarDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.ANGRY_JUNG_MAN, ItemPool.PSYCHOANALYTIC_JAR, "psycho jar", "_jungDrops", 1 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.GRIM_BROTHER, ItemPool.GRIM_FAIRY_TALE, "fairy tale", "_grimFairyTaleDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.GRIMSTONE_GOLEM, ItemPool.GRIMSTONE_MASK, "grim mask", "_grimstoneMaskDrops", 1 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.GALLOPING_GRILL, ItemPool.HOT_ASHES, "hot ashes", "_hotAshesDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.FIST_TURKEY, -1, "turkey booze", "_turkeyBooze", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.GOLDEN_MONKEY, ItemPool.POWDERED_GOLD, "powdered gold", "_powderedGoldDrops", 5 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.ADVENTUROUS_SPELUNKER, ItemPool.TALES_OF_SPELUNKING, "tales", "_spelunkingTalesDrops", 1 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.CARNIE, -1, "cotton candy", "_carnieCandyDrops", 10 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.BOOTS, -1, "pastes", "_bootStomps", 7 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.SWORD_AND_MARTINI_GUY, ItemPool.MINI_MARTINI, "mini-martini", "_miniMartiniDrops", 6 ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.PUCK_MAN, ItemPool.POWER_PILL, "power pill", "_powerPillDrops", Math.min( 1 + KoLCharacter.getCurrentDays(), 11 ) ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.MS_PUCK_MAN, ItemPool.POWER_PILL, "power pill", "_powerPillDrops", Math.min( 1 + KoLCharacter.getCurrentDays(), 11 ) ) ); DROP_FAMILIARS.add( new DropInfo( FamiliarPool.MACHINE_ELF, ItemPool.MACHINE_SNOWGLOBE, "snowglobe", "_snowglobeDrops", 1 ) ); } public static DropInfo getDropInfo( int id ) { for ( DropInfo info : DROP_FAMILIARS ) { if ( info.id == id ) return info; } return null; } public DropInfo getDropInfo() { return FamiliarData.getDropInfo( this.id ); } public static String dropName( int id ) { DropInfo drops = FamiliarData.getDropInfo( id ); return drops == null ? null : drops.dropName; } public String dropName( ) { return FamiliarData.dropName( this.id ); } public static AdventureResult dropItem( int id ) { DropInfo drops = FamiliarData.getDropInfo( id ); return drops == null ? null : drops.dropItem; } public AdventureResult dropItem() { return FamiliarData.dropItem( this.id ); } public static int dropsToday( int id ) { DropInfo drops = FamiliarData.getDropInfo( id ); return drops == null ? 0 : drops.dropsToday(); } public int dropsToday() { return FamiliarData.dropsToday( this.id ); } public static int dropDailyCap( int id ) { DropInfo drops = FamiliarData.getDropInfo( id ); return drops == null ? 0 : drops.dailyCap; } public int dropDailyCap() { return FamiliarData.dropDailyCap( this.id ); } public static boolean hasDrop( int id ) { return FamiliarData.getDropInfo( id ) != null; } public boolean hasDrop() { return FamiliarData.hasDrop( this.id ); } public static boolean hasFights( int id ) { return FamiliarData.getFightInfo( id ) != null; } public boolean hasFights() { return FamiliarData.hasFights( this.id ); } public static FightInfo getFightInfo( int id ) { for ( FightInfo info : FIGHT_FAMILIARS ) { if ( info.id == id ) return info; } return null; } public FightInfo getFightInfo() { return FamiliarData.getFightInfo( this.id ); } public static int fightsToday( int id ) { FightInfo fights = FamiliarData.getFightInfo( id ); return fights == null ? 0 : fights.fightsToday(); } public int fightsToday() { return FamiliarData.fightsToday( this.id ); } public static int fightDailyCap( int id ) { FightInfo fights = FamiliarData.getFightInfo( id ); return fights == null ? 0 : fights.dailyCap; } public int fightDailyCap() { return FamiliarData.fightDailyCap( this.id ); } @Override public String toString() { return this.id == -1 ? "(none)" : this.race + " (" + this.getModifiedWeight() + " lbs)"; } @Override public boolean equals( final Object o ) { return o != null && o instanceof FamiliarData && this.id == ( (FamiliarData) o ).id; } @Override public int hashCode() { return this.id; } @Override public int compareTo( final FamiliarData fd ) { return this.race.compareToIgnoreCase( fd.race ); } /** * Returns whether or not the familiar can equip the given familiar * item. */ public boolean canEquip( final AdventureResult item ) { if ( item == null ) { return false; } if ( item == EquipmentRequest.UNEQUIP ) { return true; } int itemId = item.getItemId(); if ( itemId <= 0 ) { return false; } String name = item.getName(); switch ( this.id ) { case -1: return false; case FamiliarPool.CHAMELEON: return false; case FamiliarPool.HATRACK: // Hatrack can wear Hats as well as familiar items, but not Crown of Thrones if ( itemId != ItemPool.HATSEAT && ItemDatabase.getConsumptionType( itemId ) == KoLConstants.EQUIP_HAT ) { return true; } break; case FamiliarPool.HAND: // Disembodied Hand can't equip Mainhand only items or Single Equip items if ( !EquipmentDatabase.isMainhandOnly( itemId ) && !Modifiers.getBooleanModifier( "Item", name, "Single Equip" ) ) { return true; } break; case FamiliarPool.SCARECROW: // Scarecrow can wear Pants as well as familiar items if ( ItemDatabase.getConsumptionType( itemId ) == KoLConstants.EQUIP_PANTS ) { return true; } break; } if ( itemId == FamiliarDatabase.getFamiliarItemId( this.id ) ) { return true; } Modifiers mods = Modifiers.getItemModifiers( itemId ); if ( mods == null ) { return false; } if ( mods.getBoolean( Modifiers.GENERIC ) ) { return true; } String others = mods.getString( Modifiers.EQUIPS_ON ); if ( others == null || others.equals( "" ) ) { return false; } String[] pieces = others.split( "\\s*\\|\\s*" ); for ( int i = pieces.length - 1; i >= 0; --i ) { if ( pieces[ i ].equals( this.getRace() ) ) { return true; } } return false; } public static boolean lockableItem( final AdventureResult item ) { if ( item == null || item == EquipmentRequest.UNEQUIP ) { return false; } Modifiers mods = Modifiers.getItemModifiers( item.getItemId() ); return mods != null && mods.getBoolean( Modifiers.GENERIC ); } public boolean isCombatFamiliar() { if ( FamiliarDatabase.isCombatType( this.id ) ) { return true; } if ( this.id == FamiliarPool.DANDY_LION ) { return EquipmentManager.getEquipment( EquipmentManager.WEAPON ).getName().endsWith( "whip" ) || EquipmentManager.getEquipment( EquipmentManager.OFFHAND ).getName().endsWith( "whip" ); } return false; } public final void findAndWearItem( boolean steal ) { AdventureResult use = this.findGoodItem( steal ); if ( use != null ) { RequestThread.postRequest( new EquipmentRequest( use, EquipmentManager.FAMILIAR ) ); } } public final AdventureResult findGoodItem( boolean steal ) { if ( KoLCharacter.inRaincore() && FamiliarData.availableItem( FamiliarData.LIFE_PRESERVER, steal ) ) { // The miniature life preserver is only useful in a Heavy Rains run return FamiliarData.LIFE_PRESERVER; } if ( FamiliarData.availableItem( FamiliarData.PET_SWEATER, steal ) ) { return FamiliarData.PET_SWEATER; } if ( FamiliarData.availableItem( FamiliarData.PUMPKIN_BUCKET, steal ) ) { return FamiliarData.PUMPKIN_BUCKET; } if ( FamiliarData.availableItem( FamiliarData.FIREWORKS, steal ) ) { return FamiliarData.FIREWORKS; } if ( FamiliarData.availableItem( FamiliarData.FLOWER_BOUQUET, steal ) ) { return FamiliarData.FLOWER_BOUQUET; } if ( FamiliarData.availableItem( FamiliarData.MOVEABLE_FEAST, steal ) ) { return FamiliarData.MOVEABLE_FEAST; } int itemId = FamiliarDatabase.getFamiliarItemId( this.id ); AdventureResult item = itemId > 0 ? ItemPool.get( itemId, 1 ) : null; if ( item != null && FamiliarData.availableItem( item, false ) ) { return item; } if ( FamiliarData.availableItem( FamiliarData.ITTAH_BITTAH_HOOKAH, steal ) ) { return FamiliarData.ITTAH_BITTAH_HOOKAH; } if ( FamiliarData.availableItem( FamiliarData.LEAD_NECKLACE, steal ) ) { return FamiliarData.LEAD_NECKLACE; } return null; } private static final boolean availableItem( AdventureResult item, boolean steal ) { if ( item.getCount( KoLConstants.inventory ) > 0 ) { return true; } if ( !steal ) { return false; } FamiliarData current = KoLCharacter.getFamiliar(); List familiars = KoLCharacter.getFamiliarList(); int count = familiars.size(); for ( int i = 0; i < count; ++i ) { FamiliarData familiar = (FamiliarData) familiars.get( i ); if ( !familiar.equals( current ) ) { AdventureResult equipped = familiar.getItem(); if ( equipped != null && equipped.equals( item ) ) { return true; } } } return false; } /** * Calculates the number of combats with a Slimeling required for the * nth slime stack in an ascension to drop. * * @param n the number of the slime stack (reset to zero on ascension) * @return the number of combats */ public static int getSlimeStackTurns( final int n ) { return n * ( n + 1 ) / 2; } public static final DefaultListCellRenderer getRenderer() { return new FamiliarRenderer(); } public static final void checkShrub() { if ( KoLCharacter.findFamiliar( FamiliarPool.CRIMBO_SHRUB ) == null ) { return; } GenericRequest request = new GenericRequest( "desc_familiar.php?which=189" ); RequestThread.postRequest( request ); String response = request.responseText; Matcher topperMatcher = SHRUB_TOPPER_PATTERN.matcher( response ); if ( topperMatcher.find() ) { Preferences.setString( "shrubTopper", topperMatcher.group( 1 ) ); } else { Preferences.setString( "shrubTopper", KoLCharacter.mainStat().toString() ); // If we didn't find this pattern, we won't find anything else either // The remaining values are either random or nothing Preferences.setString( "shrubLights", "" ); Preferences.setString( "shrubGarland", "" ); Preferences.setString( "shrubGifts", "" ); return; } Matcher lightsMatcher = SHRUB_LIGHT_PATTERN.matcher( response ); if ( lightsMatcher.find() ) { Preferences.setString( "shrubLights", lightsMatcher.group( 1 ) ); } if ( response.contains( "Restores Hit Points" ) ) { Preferences.setString( "shrubGarland", "HP" ); } else if ( response.contains( "PvP fights" ) ) { Preferences.setString( "shrubGarland", "PvP" ); } else if ( response.contains( "Prevents monsters" ) ) { Preferences.setString( "shrubGarland", "blocking" ); } if ( response.contains( "Blast foes" ) ) { Preferences.setString( "shrubGifts", "yellow" ); } else if ( response.contains( "Filled with Meat" ) ) { Preferences.setString( "shrubGifts", "meat" ); } else if ( response.contains( "Exchange random gifts" ) ) { Preferences.setString( "shrubGifts", "gifts" ); } } private static class FamiliarRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent( final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus ) { JLabel defaultComponent = (JLabel) super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus ); if ( value == null || !( value instanceof FamiliarData ) || ( (FamiliarData) value ).id == -1 ) { defaultComponent.setIcon( JComponentUtilities.getImage( "debug.gif" ) ); defaultComponent.setText( KoLConstants.VERSION_NAME + ", the 0 lb. \"No Familiar Plz\" Placeholder" ); defaultComponent.setVerticalTextPosition( SwingConstants.CENTER ); defaultComponent.setHorizontalTextPosition( SwingConstants.RIGHT ); return defaultComponent; } FamiliarData familiar = (FamiliarData) value; defaultComponent.setIcon( FamiliarDatabase.getFamiliarImage( familiar.id ) ); defaultComponent.setText( familiar.getName() + ", the " + familiar.getWeight() + " lb. " + familiar.getRace() ); defaultComponent.setVerticalTextPosition( SwingConstants.CENTER ); defaultComponent.setHorizontalTextPosition( SwingConstants.RIGHT ); return defaultComponent; } } }