/** * 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.swingui; import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; import net.java.dev.spellcast.utilities.ChatBuffer; import net.java.dev.spellcast.utilities.JComponentUtilities; import net.java.dev.spellcast.utilities.LockableListModel; import net.java.dev.spellcast.utilities.SortedListModel; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.CakeArenaManager; import net.sourceforge.kolmafia.CakeArenaManager.ArenaOpponent; import net.sourceforge.kolmafia.FamiliarData; import net.sourceforge.kolmafia.FamiliarTool; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLConstants.MafiaState; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.KoLmafiaCLI; import net.sourceforge.kolmafia.RequestThread; import net.sourceforge.kolmafia.SpecialOutfit; import net.sourceforge.kolmafia.StaticEntity; import net.sourceforge.kolmafia.chat.StyledChatBuffer; import net.sourceforge.kolmafia.listener.CharacterListener; import net.sourceforge.kolmafia.listener.CharacterListenerRegistry; import net.sourceforge.kolmafia.objectpool.EffectPool; import net.sourceforge.kolmafia.objectpool.FamiliarPool; import net.sourceforge.kolmafia.objectpool.IntegerPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.persistence.FamiliarDatabase; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.CakeArenaRequest; import net.sourceforge.kolmafia.request.EquipmentRequest; import net.sourceforge.kolmafia.request.FamiliarRequest; import net.sourceforge.kolmafia.request.UseItemRequest; import net.sourceforge.kolmafia.request.UseSkillRequest; import net.sourceforge.kolmafia.session.EquipmentManager; import net.sourceforge.kolmafia.session.FamiliarManager; import net.sourceforge.kolmafia.session.InventoryManager; import net.sourceforge.kolmafia.swingui.button.DisplayFrameButton; import net.sourceforge.kolmafia.swingui.listener.ThreadedListener; import net.sourceforge.kolmafia.swingui.panel.StatusPanel; import net.sourceforge.kolmafia.swingui.widget.RequestPane; import net.sourceforge.kolmafia.utilities.InputFieldUtilities; import net.sourceforge.kolmafia.utilities.LogStream; public class FamiliarTrainingFrame extends GenericFrame { private static final Pattern PRIZE_PATTERN = Pattern.compile( "You've earned a prize from the Arena Goodies Sack!.*You acquire an item: <b>(.*?)</b>" ); private static final Pattern STEAL_PATTERN = Pattern.compile( "She also drops an item from her mouth.*You acquire an item: <b>(.*?)</b>" ); private static final StyledChatBuffer results = new StyledChatBuffer( "", "blue", false ); private static int losses = 0; private static boolean stop = false; private final FamiliarTrainingPanel training; private CharacterListener weightListener; public static final int BASE = 1; public static final int BUFFED = 2; public static final int TURNS = 3; public static final int LEARN = 4; // Familiar buffing skills and effects public static final AdventureResult EMPATHY = EffectPool.get( EffectPool.EMPATHY ); private static final AdventureResult LEASH = EffectPool.get( EffectPool.LEASH_OF_LINGUINI ); private static final AdventureResult BESTIAL_SYMPATHY = EffectPool.get( EffectPool.BESTIAL_SYMPATHY ); private static final AdventureResult BLACK_TONGUE = EffectPool.get( EffectPool.BLACK_TONGUE ); private static final AdventureResult GREEN_GLOW = EffectPool.get( EffectPool.HEALTHY_GREEN_GLOW ); private static final AdventureResult GREEN_HEART = EffectPool.get( EffectPool.HEART_OF_GREEN ); private static final AdventureResult GREEN_TONGUE = EffectPool.get( EffectPool.GREEN_TONGUE ); private static final AdventureResult HEAVY_PETTING = EffectPool.get( EffectPool.HEAVY_PETTING ); private static final AdventureResult WORST_ENEMY = EffectPool.get( EffectPool.MANS_WORST_ENEMY ); // Familiar buffing items private static final AdventureResult PITH_HELMET = ItemPool.get( ItemPool.PLEXIGLASS_PITH_HELMET ); private static final AdventureResult CRUMPLED_FEDORA = ItemPool.get( ItemPool.CRUMPLED_FELT_FEDORA ); private static final AdventureResult BUFFING_SPRAY = ItemPool.get( ItemPool.PET_BUFFING_SPRAY ); private static final AdventureResult GREEN_SNOWCONE = ItemPool.get( ItemPool.GREEN_SNOWCONE ); private static final AdventureResult BLACK_SNOWCONE = ItemPool.get( ItemPool.BLACK_SNOWCONE ); private static final AdventureResult GREEN_CANDY = ItemPool.get( ItemPool.GREEN_CANDY ); private static final AdventureResult HALF_ORCHID = ItemPool.get( ItemPool.HALF_ORCHID ); private static final AdventureResult SPIKY_COLLAR = ItemPool.get( ItemPool.SPIKY_COLLAR ); private static final AdventureResult BAR_WHIP = ItemPool.get( ItemPool.BAR_WHIP ); private static final int[] tinyPlasticNormal = new int[] { 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988 }; private static final int[] tinyPlasticCrimbo = new int[] { 1377, 1378, 2201, 2202 }; // Available skills which affect weight private static boolean sympathyAvailable; private static boolean leashAvailable; private static boolean empathyAvailable; // Available effects which affect weight private static boolean bestialAvailable; private static boolean blackConeAvailable; private static boolean greenConeAvailable; private static boolean greenHeartAvailable; private static boolean heavyPettingAvailable; private static boolean worstEnemyAvailable; // Active effects which affect weight private static int leashActive; private static int empathyActive; private static int bestialActive; private static int blackTongueActive; private static int greenGlowActive; private static int greenHeartActive; private static int greenTongueActive; private static int heavyPettingActive; private static int worstEnemyActive; public FamiliarTrainingFrame() { super( "Familiar Trainer" ); this.training = new FamiliarTrainingPanel(); this.setCenterComponent( this.training ); // Clear left over results from the buffer FamiliarTrainingFrame.results.clear(); } @Override public void updateDisplayState( final MafiaState displayState ) { this.training.setEnabled( displayState != MafiaState.CONTINUE ); } @Override public void dispose() { FamiliarTrainingFrame.stop = true; CharacterListenerRegistry.removeCharacterListener( this.weightListener ); super.dispose(); } public static final ChatBuffer getResults() { return FamiliarTrainingFrame.results; } private class FamiliarTrainingPanel extends JPanel { private FamiliarData familiar; private final JComboBox familiars; private final JLabel winCount; private final JLabel prizeCounter; private final JLabel totalWeight; private final OpponentsPanel opponentsPanel; private final ButtonPanel buttonPanel; private final ResultsPanel resultsPanel; public FamiliarTrainingPanel() { super( new BorderLayout( 10, 10 ) ); JPanel container = new JPanel( new BorderLayout( 10, 10 ) ); // Get current familiar this.familiar = KoLCharacter.getFamiliar(); // Put familiar changer on top this.familiars = new ChangeComboBox( (LockableListModel) KoLCharacter.getFamiliarList() ); this.familiars.setRenderer( FamiliarData.getRenderer() ); container.add( this.familiars, BorderLayout.NORTH ); // Put results in center this.resultsPanel = new ResultsPanel(); container.add( this.resultsPanel, BorderLayout.CENTER ); this.add( container, BorderLayout.CENTER ); // Put opponents on left this.opponentsPanel = new OpponentsPanel(); this.add( this.opponentsPanel, BorderLayout.WEST ); // Put buttons on right JPanel buttonContainer = new JPanel( new BorderLayout() ); this.buttonPanel = new ButtonPanel(); buttonContainer.add( this.buttonPanel, BorderLayout.NORTH ); // List of counters at bottom JPanel counterPanel = new JPanel(); counterPanel.setLayout( new GridLayout( 3, 1 ) ); // First the win counter this.winCount = new JLabel( "", JLabel.CENTER ); counterPanel.add( this.winCount ); // Next the prize counter this.prizeCounter = new JLabel( "", JLabel.CENTER ); counterPanel.add( this.prizeCounter ); // Finally the total familiar weight this.totalWeight = new JLabel( "", JLabel.CENTER ); counterPanel.add( this.totalWeight ); // Make a refresher for the counters FamiliarTrainingFrame.this.weightListener = new CharacterListener( new TotalWeightRefresher() ); CharacterListenerRegistry.addCharacterListener( FamiliarTrainingFrame.this.weightListener ); // Show the counters buttonContainer.add( counterPanel, BorderLayout.SOUTH ); this.add( buttonContainer, BorderLayout.EAST ); this.add( new StatusPanel(), BorderLayout.SOUTH ); } @Override public void setEnabled( final boolean isEnabled ) { if ( this.buttonPanel == null || this.familiars == null ) { return; } super.setEnabled( isEnabled ); this.buttonPanel.setEnabled( isEnabled ); this.familiars.setEnabled( isEnabled ); } private class TotalWeightRefresher implements Runnable { public TotalWeightRefresher() { this.run(); } public void run() { // Arena wins int arenaWins = KoLCharacter.getArenaWins(); FamiliarTrainingPanel.this.winCount.setText( arenaWins + " arena wins" ); // Wins to next prize int nextPrize = 5 - arenaWins % 5; FamiliarTrainingPanel.this.prizeCounter.setText( nextPrize + " wins to next prize" ); // Terrarium weight int totalTerrariumWeight = 0; FamiliarData[] familiarArray = new FamiliarData[ KoLCharacter.getFamiliarList().size() ]; KoLCharacter.getFamiliarList().toArray( familiarArray ); for ( int i = 0; i < familiarArray.length; ++i ) { if ( familiarArray[ i ].getWeight() != 1 ) { totalTerrariumWeight += familiarArray[ i ].getWeight(); } } FamiliarTrainingPanel.this.totalWeight.setText( totalTerrariumWeight + " lb. terrarium" ); } } private class OpponentsPanel extends JPanel { public OpponentsPanel() { // Get current opponents LockableListModel opponents = CakeArenaManager.getOpponentList(); int opponentCount = opponents.size(); this.setLayout( new GridLayout( opponentCount, 1, 0, 20 ) ); for ( int i = 0; i < opponentCount; ++i ) { this.add( new OpponentLabel( (ArenaOpponent) opponents.get( i ) ) ); } } private class OpponentLabel extends JLabel { public OpponentLabel( final ArenaOpponent opponent ) { super( "<html><center>" + opponent.getName() + "<br>" + "(" + opponent.getWeight() + " lbs)</center></html>", FamiliarDatabase.getFamiliarImage( opponent.getRace() ), JLabel.CENTER ); this.setVerticalTextPosition( JLabel.BOTTOM ); this.setHorizontalTextPosition( JLabel.CENTER ); } } } private class ButtonPanel extends JPanel { private final JButton matchup, base, buffed, turns, stop, save; // private JButton debug; final JButton learn, equip; public ButtonPanel() { JPanel containerPanel = new JPanel( new GridLayout( 11, 1, 5, 5 ) ); this.matchup = new DisplayFrameButton( "View Matchup", "CakeArenaFrame" ); containerPanel.add( this.matchup ); containerPanel.add( new JLabel() ); this.base = new JButton( "Train Base Weight" ); this.base.addActionListener( new BaseListener() ); containerPanel.add( this.base ); this.buffed = new JButton( "Train Buffed Weight" ); this.buffed.addActionListener( new BuffedListener() ); containerPanel.add( this.buffed ); this.turns = new JButton( "Train for Set Turns" ); this.turns.addActionListener( new TurnsListener() ); containerPanel.add( this.turns ); containerPanel.add( new JLabel() ); this.stop = new JButton( "Stop Training" ); this.stop.addActionListener( new StopListener() ); containerPanel.add( this.stop ); this.save = new JButton( "Save Transcript" ); this.save.addActionListener( new SaveListener() ); containerPanel.add( this.save ); containerPanel.add( new JLabel() ); this.learn = new JButton( "Learn Familiar Strengths" ); this.learn.addActionListener( new LearnListener() ); containerPanel.add( this.learn ); this.equip = new JButton( "Equip All Familiars" ); this.equip.addActionListener( new EquipAllListener() ); containerPanel.add( this.equip ); // debug = new JButton( "Debug" ); // debug.addActionListener( new DebugListener() ); // this.add( debug ); this.add( containerPanel ); } @Override public void setEnabled( final boolean isEnabled ) { super.setEnabled( isEnabled ); if ( this.matchup != null ) { this.matchup.setEnabled( isEnabled ); } if ( this.base != null ) { this.base.setEnabled( isEnabled ); } if ( this.buffed != null ) { this.buffed.setEnabled( isEnabled ); } if ( this.turns != null ) { this.turns.setEnabled( isEnabled ); } // this.stop is always enabled if ( this.save != null ) { this.save.setEnabled( isEnabled ); } if ( this.learn != null ) { this.learn.setEnabled( isEnabled ); } if ( this.equip != null ) { this.equip.setEnabled( isEnabled ); } } private class BaseListener extends ThreadedListener { @Override protected void execute() { // Prompt for goal Integer value = InputFieldUtilities.getQuantity( "Train up to what base weight?", 20, 20 ); int goal = ( value == null ) ? 0 : value.intValue(); // Quit if canceled if ( goal == 0 ) { return; } // Level the familiar FamiliarTrainingFrame.levelFamiliar( goal, FamiliarTrainingFrame.BASE ); } } private class BuffedListener extends ThreadedListener { @Override protected void execute() { // Prompt for goal Integer value = InputFieldUtilities.getQuantity( "Train up to what buffed weight?", 48, 20 ); int goal = ( value == null ) ? 0 : value.intValue(); // Quit if canceled if ( goal == 0 ) { return; } // Level the familiar FamiliarTrainingFrame.levelFamiliar( goal, FamiliarTrainingFrame.BUFFED ); } } private class TurnsListener extends ThreadedListener { @Override protected void execute() { // Prompt for goal Integer value = InputFieldUtilities.getQuantity( "Train for how many turns?", Integer.MAX_VALUE, 1 ); int goal = ( value == null ) ? 0 : value.intValue(); // Quit if canceled if ( goal == 0 ) { return; } // Level the familiar FamiliarTrainingFrame.levelFamiliar( goal, FamiliarTrainingFrame.TURNS ); } } private class StopListener extends ThreadedListener { @Override protected void execute() { FamiliarTrainingFrame.stop = true; } } private class SaveListener extends ThreadedListener { File output = null; @Override protected void execute() { this.output = null; try { SwingUtilities.invokeAndWait( new Runnable() { public void run() { SaveListener.this.output = InputFieldUtilities.chooseOutputFile( KoLConstants.DATA_LOCATION, FamiliarTrainingFrame.this ); } } ); } catch ( Exception ie ) { } if ( output == null ) { return; } try { PrintStream ostream = LogStream.openStream( output, false ); ostream.println( FamiliarTrainingFrame.results.getHTMLContent().replaceAll( "<br>", KoLConstants.LINE_BREAK ) ); ostream.close(); } catch ( Exception ex ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( ex ); } } } private class LearnListener extends ThreadedListener { @Override protected void execute() { if ( FamiliarTrainingPanel.this.familiar == FamiliarData.NO_FAMILIAR ) { return; } // Prompt for trials Integer value = InputFieldUtilities.getQuantity( "How many trials per event per rank?", 20, 3 ); int trials = ( value == null ) ? 0 : value.intValue(); // Quit if canceled if ( trials == 0 ) { return; } // Nag dialog int turns = trials * 12; if ( !InputFieldUtilities.confirm( "This will take up to " + turns + " adventures and cost up to " + KoLConstants.COMMA_FORMAT.format( turns * 100 ) + " meat. Are you sure?" ) ) { return; } // Learn familiar parameters int[] skills = FamiliarTrainingFrame.learnFamiliarParameters( trials ); // Save familiar parameters if ( skills != null ) { int[] original = FamiliarDatabase.getFamiliarSkills( FamiliarTrainingPanel.this.familiar.getId() ); boolean changed = false; for ( int i = 0; i < original.length; ++i ) { if ( skills[ i ] != original[ i ] ) { changed = true; break; } } if ( changed && InputFieldUtilities.confirm( "Save arena parameters for the " + FamiliarTrainingPanel.this.familiar.getRace() + "?" ) ) { FamiliarDatabase.setFamiliarSkills( FamiliarTrainingPanel.this.familiar.getRace(), skills ); } KoLmafia.updateDisplay( MafiaState.CONTINUE, "Learned skills are " + ( changed ? "different from" : "the same as" ) + " those in familiar database." ); } } } private class EquipAllListener extends ThreadedListener { @Override protected void execute() { FamiliarManager.equipAllFamiliars(); } } /* * private class DebugListener extends ThreadedListener { public void run() { debug(); } } */ } private class ResultsPanel extends JPanel { RequestPane resultsDisplay; public ResultsPanel() { this.setLayout( new BorderLayout( 10, 10 ) ); RequestPane resultsDisplay = new RequestPane(); JScrollPane scroller = FamiliarTrainingFrame.results.addDisplay( resultsDisplay ); JComponentUtilities.setComponentSize( scroller, 400, 400 ); this.add( scroller, BorderLayout.CENTER ); } } private class ChangeComboBox extends JComboBox { public ChangeComboBox( final LockableListModel selector ) { super( selector ); this.addActionListener( new ChangeComboBoxListener() ); } private class ChangeComboBoxListener extends ThreadedListener { @Override protected void execute() { FamiliarData selection = (FamiliarData) ChangeComboBox.this.getSelectedItem(); if ( selection == null || selection == FamiliarTrainingPanel.this.familiar ) { return; } RequestThread.postRequest( new FamiliarRequest( selection ) ); FamiliarTrainingPanel.this.familiar = KoLCharacter.getFamiliar(); } } } } @Override public JTabbedPane getTabbedPane() { return null; } private static final boolean levelFamiliar( final int goal, final int type ) { return FamiliarTrainingFrame.levelFamiliar( goal, type, Preferences.getBoolean( "debugFamiliarTraining" ) ); } /** * Utility method to level the current familiar by fighting the current arena opponents. * * @param goal Weight goal for the familiar * @param type BASE, BUFF, or TURNS * @param buffs true if should cast buffs during training * @param debug true if we are debugging */ public static final boolean levelFamiliar( final int goal, final int type, final boolean debug ) { // Clear the output FamiliarTrainingFrame.results.clear(); // Permit training session to proceed FamiliarTrainingFrame.stop = false; KoLmafia.forceContinue(); // Get current familiar FamiliarData familiar = KoLCharacter.getFamiliar(); if ( familiar == FamiliarData.NO_FAMILIAR ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "No familiar selected to train." ); return false; } if ( !familiar.trainable() ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Don't know how to train a " + familiar.getRace() + " yet." ); return false; } // Get the status of current familiar FamiliarStatus status = new FamiliarStatus(); // Identify the familiar we are training FamiliarTrainingFrame.printFamiliar( status, goal, type ); FamiliarTrainingFrame.results.append( "<br>" ); // Print available buffs and items and current buffs FamiliarTrainingFrame.results.append( status.printCurrentBuffs() ); FamiliarTrainingFrame.results.append( status.printAvailableBuffs() ); FamiliarTrainingFrame.results.append( status.printCurrentEquipment() ); FamiliarTrainingFrame.results.append( status.printAvailableEquipment() ); FamiliarTrainingFrame.results.append( "<br>" ); // Get opponent list LockableListModel opponents = CakeArenaManager.getOpponentList(); // Print the opponents FamiliarTrainingFrame.printOpponents( opponents ); FamiliarTrainingFrame.results.append( "<br>" ); // Make a Familiar Tool FamiliarTool tool = new FamiliarTool( opponents ); // Save your current outfit SpecialOutfit.createImplicitCheckpoint(); // Let the battles begin! KoLmafia.updateDisplay( "Starting training session..." ); // Iterate until we reach the goal FamiliarTrainingFrame.losses = 0; while ( !FamiliarTrainingFrame.goalMet( status, goal, type ) && FamiliarTrainingFrame.losses < 5 ) { // If user canceled, bail now if ( FamiliarTrainingFrame.stop || !KoLmafia.permitsContinue() ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Training session aborted.", true ); return false; } // Make sure you have an adventure left if ( KoLCharacter.getAdventuresLeft() < 1 ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Training stopped: out of adventures.", true ); return false; } // Make sure you have enough meat to pay for the contest if ( KoLCharacter.getAvailableMeat() < 100 ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Training stopped: out of meat.", true ); return false; } // Switch to the required familiar if ( KoLCharacter.getFamiliar() != familiar ) { RequestThread.postRequest( new FamiliarRequest( familiar ) ); } // Choose possible weights int[] weights = status.getWeights(); // Choose next opponent ArenaOpponent opponent = tool.bestOpponent( familiar.getId(), weights ); if ( opponent == null ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Couldn't choose a suitable opponent.", true ); return false; } // Change into appropriate gear status.changeGear( tool.bestWeight() ); if ( !KoLmafia.permitsContinue() ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Training stopped: internal error.", true ); return false; } if ( debug ) { break; } // Enter the contest if ( FamiliarTrainingFrame.fightMatch( status, tool, opponent, tool.bestMatch(), false ) <= 0 ) { ++FamiliarTrainingFrame.losses; } else { FamiliarTrainingFrame.losses = 0; } } if ( FamiliarTrainingFrame.losses >= 5 ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Too many consecutive losses.", true ); return false; } // Done training. Restore original outfit SpecialOutfit.restoreImplicitCheckpoint(); if ( familiar.getId() != FamiliarPool.CHAMELEON ) { // Find and wear an appropriate item familiar.findAndWearItem( false ); } boolean result = type == FamiliarTrainingFrame.BUFFED ? FamiliarTrainingFrame.buffFamiliar( goal ) : true; FamiliarTrainingFrame.statusMessage( MafiaState.CONTINUE, "Training session completed." ); return result; } /** * Utility method to derive the arena parameters of the current familiar * * @param trials How many trials per event */ private static final int[] learnFamiliarParameters( final int trials ) { // Clear the output FamiliarTrainingFrame.results.clear(); // Permit training session to proceed FamiliarTrainingFrame.stop = false; // Get current familiar if ( KoLCharacter.getFamiliar() == FamiliarData.NO_FAMILIAR ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "No familiar selected to train." ); return null; } int events = 12 * trials; // Make sure you have enough adventures left if ( KoLCharacter.getAdventuresLeft() < events ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "You need to have at least " + events + " adventures available." ); return null; } // Make sure you have enough meat to pay for the contests if ( KoLCharacter.getAvailableMeat() < 100 * events ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "You need to have at least " + KoLConstants.COMMA_FORMAT.format( 100 * events ) + " meat available." ); return null; } // Get the status of current familiar FamiliarStatus status = new FamiliarStatus(); // Identify the familiar we are training FamiliarTrainingFrame.printFamiliar( status, trials, FamiliarTrainingFrame.LEARN ); FamiliarTrainingFrame.results.append( "<br>" ); // Print available buffs and items and current buffs FamiliarTrainingFrame.results.append( status.printCurrentBuffs() ); FamiliarTrainingFrame.results.append( status.printAvailableBuffs() ); FamiliarTrainingFrame.results.append( status.printCurrentEquipment() ); FamiliarTrainingFrame.results.append( status.printAvailableEquipment() ); FamiliarTrainingFrame.results.append( "<br>" ); // Get opponent list LockableListModel opponents = CakeArenaManager.getOpponentList(); // Print the opponents FamiliarTrainingFrame.printOpponents( opponents ); FamiliarTrainingFrame.results.append( "<br>" ); // Make a Familiar Tool FamiliarTool tool = new FamiliarTool( opponents ); // Save your current outfit SpecialOutfit.createImplicitCheckpoint(); // Let the battles begin! KoLmafia.updateDisplay( "Starting training session..." ); // XP earned indexed by [event][rank] int[][] xp = new int[ 4 ][ 3 ]; // Array of skills to test with int[] test = new int[ 4 ]; // Array of contest suckage int[] skills = new int[ 4 ]; boolean[] suckage = new boolean[ 4 ]; // Iterate for the specified number of trials for ( int trial = 1; trial <= trials && KoLmafia.permitsContinue(); ++trial ) { skills = FamiliarTrainingFrame.learnFamiliarParameters( trial, status, tool, xp, test, suckage ); } // Done training. Restore original outfit SpecialOutfit.restoreImplicitCheckpoint(); return skills; } private static final int[] learnFamiliarParameters( final int trial, final FamiliarStatus status, final FamiliarTool tool, final int[][] xp, final int[] test, final boolean[] suckage ) { // Iterate through the contests for ( int contest = 0; contest < 4; ++contest ) { // Initialize test parameters test[ 0 ] = test[ 1 ] = test[ 2 ] = test[ 3 ] = 0; // Iterate through the ranks for ( int rank = 0; rank < 3; ++rank ) { // Skip contests in which the familiar sucks if ( suckage[ contest ] ) { continue; } // If user canceled, bail now if ( FamiliarTrainingFrame.stop || !KoLmafia.permitsContinue() ) { FamiliarTrainingFrame.printTrainingResults( trial, status, xp, suckage ); FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Training session aborted.", true ); return null; } // Initialize test parameters test[ contest ] = rank + 1; FamiliarTrainingFrame.statusMessage( MafiaState.CONTINUE, CakeArenaManager.eventIdToName( contest + 1 ) + " rank " + ( rank + 1 ) + ": trial " + trial ); // Choose possible weights int[] weights = status.getWeights(); // Choose next opponent ArenaOpponent opponent = tool.bestOpponent( test, weights ); if ( opponent == null ) { FamiliarTrainingFrame.printTrainingResults( trial, status, xp, suckage ); FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Couldn't choose a suitable opponent.", true ); return null; } int match = tool.bestMatch(); if ( match != contest + 1 ) { // Informative message only. Do not stop session. FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Internal error: Selected " + CakeArenaManager.eventIdToName( match ) + " rather than " + CakeArenaManager.eventIdToName( contest + 1 ) ); // Use contest, even if with bad weight match = contest + 1; } // Change into appropriate gear status.changeGear( tool.bestWeight() ); if ( !KoLmafia.permitsContinue() ) { FamiliarTrainingFrame.printTrainingResults( trial, status, xp, suckage ); FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Training stopped: internal error.", true ); return null; } // Enter the contest int trialXP = FamiliarTrainingFrame.fightMatch( status, tool, opponent, match, true ); if ( trialXP < 0 ) { suckage[ contest ] = true; } else { xp[ contest ][ rank ] += trialXP; } } } return FamiliarTrainingFrame.printTrainingResults( trial, status, xp, suckage ); } private static int[] printTrainingResults( final int trial, final FamiliarStatus status, final int[][] xp, final boolean[] suckage ) { // Original skill rankings int[] original = FamiliarDatabase.getFamiliarSkills( KoLCharacter.getFamiliar().getId() ); // Derived skill rankings int skills[] = new int[ 4 ]; StringBuilder text = new StringBuilder(); text.append( "<br>Results for " + KoLCharacter.getFamiliar().getRace() + " after " + trial + " trials using " + status.turnsUsed() + " turns:<br><br>" ); // Open the table text.append( "<table>" ); // Table header text.append( "<tr>" ); text.append( "<th>Contest</th>" ); text.append( "<th>XP[1]</th>" ); text.append( "<th>XP[2]</th>" ); text.append( "<th>XP[3]</th>" ); text.append( "<th>Original Rank</th>" ); text.append( "<th>Derived Rank</th>" ); text.append( "</tr>" ); for ( int contest = 0; contest < 4; ++contest ) { text.append( "<tr>" ); text.append( "<td>" + CakeArenaManager.eventIdToName( contest + 1 ) + "</td>" ); int bestXP = 0; int bestRank = 0; for ( int rank = 0; rank < 3; ++rank ) { int rankXP = suckage[ contest ] ? 0 : xp[ contest ][ rank ]; text.append( "<td align=center>" + rankXP + "</td>" ); if ( rankXP > bestXP ) { bestXP = rankXP; bestRank = rank + 1; } } skills[ contest ] = bestRank; text.append( "<td align=center>" + original[ contest ] + "</td>" ); text.append( "<td align=center>" + bestRank + "</td>" ); text.append( "</tr>" ); } // Close the table text.append( "</table><br><br>" ); FamiliarTrainingFrame.results.append( text.toString() ); return skills; } /** * Utility method to buff the current familiar to the specified weight or higher. * * @param weight Weight goal for the familiar */ public static final boolean buffFamiliar( final int weight ) { // Get current familiar. If none, punt. FamiliarData familiar = KoLCharacter.getFamiliar(); if ( familiar == FamiliarData.NO_FAMILIAR ) { KoLmafia.updateDisplay( MafiaState.ERROR, "You don't have a familiar equipped." ); return false; } // If the familiar is already heavy enough, nothing to do if ( familiar.getModifiedWeight() >= weight ) { return true; } FamiliarStatus status = new FamiliarStatus(); int[] weights = status.getWeights(); Arrays.sort( weights ); status.changeGear( weights[ weights.length - 1 ] ); if ( familiar.getModifiedWeight() >= weight ) { return true; } if ( !InventoryManager.hasItem( FamiliarData.PUMPKIN_BUCKET ) && !InventoryManager.hasItem( FamiliarData.FLOWER_BOUQUET ) && !InventoryManager.hasItem( FamiliarData.FIREWORKS ) && !InventoryManager.hasItem( FamiliarData.SUGAR_SHIELD ) && status.familiarItemWeight != 0 && !InventoryManager.hasItem( status.familiarItem ) && InventoryManager.itemAvailable( status.familiarItem ) ) { KoLmafiaCLI.DEFAULT_SHELL.executeLine( "buy 1 " + status.familiarItem.getName() ); RequestThread.postRequest( new EquipmentRequest( status.familiarItem ) ); if ( familiar.getModifiedWeight() >= weight ) { return true; } } if ( FamiliarTrainingFrame.leashAvailable && FamiliarTrainingFrame.leashActive == 0 ) { RequestThread.postRequest( UseSkillRequest.getInstance( "leash of linguini", 1 ) ); if ( familiar.getModifiedWeight() >= weight ) { return true; } } if ( FamiliarTrainingFrame.empathyAvailable && FamiliarTrainingFrame.empathyActive == 0 ) { RequestThread.postRequest( UseSkillRequest.getInstance( "empathy of the newt", 1 ) ); if ( familiar.getModifiedWeight() >= weight ) { return true; } } // Add on a green heart first, if you know the difference is // less than three. if ( FamiliarTrainingFrame.greenHeartAvailable && FamiliarTrainingFrame.greenHeartActive == 0 && familiar.getModifiedWeight() + 3 >= weight ) { RequestThread.postRequest( UseItemRequest.getInstance( FamiliarTrainingFrame.GREEN_CANDY ) ); if ( familiar.getModifiedWeight() >= weight ) { return true; } } if ( FamiliarTrainingFrame.bestialAvailable && FamiliarTrainingFrame.bestialActive == 0 ) { RequestThread.postRequest( UseItemRequest.getInstance( FamiliarTrainingFrame.HALF_ORCHID ) ); if ( familiar.getModifiedWeight() >= weight ) { return true; } } if ( FamiliarTrainingFrame.heavyPettingAvailable && FamiliarTrainingFrame.heavyPettingActive == 0 ) { RequestThread.postRequest( UseItemRequest.getInstance( FamiliarTrainingFrame.BUFFING_SPRAY ) ); if ( familiar.getModifiedWeight() >= weight ) { return true; } } if ( FamiliarTrainingFrame.greenConeAvailable && FamiliarTrainingFrame.greenTongueActive == 0 ) { RequestThread.postRequest( UseItemRequest.getInstance( FamiliarTrainingFrame.GREEN_SNOWCONE ) ); if ( familiar.getModifiedWeight() >= weight ) { return true; } } if ( !FamiliarTrainingFrame.greenConeAvailable && FamiliarTrainingFrame.greenTongueActive == 0 && FamiliarTrainingFrame.blackConeAvailable && FamiliarTrainingFrame.blackTongueActive == 0 ) { RequestThread.postRequest( UseItemRequest.getInstance( FamiliarTrainingFrame.BLACK_SNOWCONE ) ); if ( familiar.getModifiedWeight() >= weight ) { return true; } } if ( FamiliarTrainingFrame.worstEnemyAvailable && FamiliarTrainingFrame.worstEnemyActive == 0 ) { RequestThread.postRequest( UseItemRequest.getInstance( FamiliarTrainingFrame.SPIKY_COLLAR ) ); if ( familiar.getModifiedWeight() >= weight ) { return true; } } KoLmafia.updateDisplay( MafiaState.ERROR, "Can't buff and equip familiar to reach " + weight + " lbs." ); return false; } private static final void statusMessage( final MafiaState state, final String message ) { FamiliarTrainingFrame.statusMessage( state, message, false ); } private static final void statusMessage( final MafiaState state, final String message, boolean restoreOutfit ) { if ( restoreOutfit ) { SpecialOutfit.restoreImplicitCheckpoint(); } if ( state == MafiaState.ERROR || message.endsWith( "lost." ) ) { FamiliarTrainingFrame.results.append( "<font color=red>" + message + "</font><br>" ); } else if ( message.indexOf( "experience" ) != -1 ) { FamiliarTrainingFrame.results.append( "<font color=green>" + message + "</font><br>" ); } else if ( message.indexOf( "prize" ) != -1 ) { FamiliarTrainingFrame.results.append( "<font color=blue>" + message + "</font><br>" ); } else { FamiliarTrainingFrame.results.append( message + "<br>" ); } KoLmafia.updateDisplay( state, message ); } private static final void printFamiliar( final FamiliarStatus status, final int goal, final int type ) { FamiliarData familiar = status.getFamiliar(); String name = familiar.getName(); String race = familiar.getRace(); int weight = familiar.getWeight(); String hope = ""; if ( type == FamiliarTrainingFrame.BASE ) { hope = " to " + goal + " lbs. base weight"; } else if ( type == FamiliarTrainingFrame.BUFFED ) { hope = " to " + goal + " lbs. buffed weight"; } else if ( type == FamiliarTrainingFrame.TURNS ) { hope = " for " + goal + " turns"; } else if ( type == FamiliarTrainingFrame.LEARN ) { hope = " for " + goal + " iterations to learn arena strengths"; } FamiliarTrainingFrame.results.append( "Training " + name + " the " + weight + " lb. " + race + hope + ".<br>" ); } private static final void printOpponents( final LockableListModel opponents ) { FamiliarTrainingFrame.results.append( "Opponents:<br>" ); int opponentCount = opponents.size(); for ( int i = 0; i < opponentCount; ++i ) { ArenaOpponent opponent = (ArenaOpponent) opponents.get( i ); String name = opponent.getName(); String race = opponent.getRace(); int weight = opponent.getWeight(); FamiliarTrainingFrame.results.append( name + " the " + weight + " lb. " + race + "<br>" ); } } private static final boolean goalMet( final FamiliarStatus status, final int goal, final int type ) { switch ( type ) { case BASE: return status.baseWeight() >= goal; case BUFFED: return status.maxWeight( true ) >= goal; case TURNS: return status.turnsUsed() >= goal; } return false; } private static final void printMatch( final FamiliarStatus status, final ArenaOpponent opponent, final FamiliarTool tool, final int match ) { FamiliarData familiar = status.getFamiliar(); int weight = tool.bestWeight(); int diff = tool.difference(); StringBuilder text = new StringBuilder(); int round = status.turnsUsed() + 1; text.append( "Round " + round + ": " ); text.append( familiar.getName() ); text.append( " (" + weight + " lbs." ); if ( diff != 0 ) { text.append( "; optimum = " ); text.append( weight - diff ); text.append( " lbs." ); } text.append( ") vs. " + opponent.getName() ); text.append( " in the " + CakeArenaManager.eventIdToName( match ) ); text.append( " event.<br>" ); FamiliarTrainingFrame.results.append( text.toString() ); KoLmafia.updateDisplay( "Round " + round + ": " + familiar.getName() + " vs. " + opponent.getName() + "..." ); } private static final int fightMatch( final FamiliarStatus status, final FamiliarTool tool, final ArenaOpponent opponent, final int match, final boolean ignoreCounters ) { // If user aborted, bail now if ( KoLmafia.refusesContinue() ) { return 0; } // Tell the user about the match FamiliarTrainingFrame.printMatch( status, opponent, tool, match ); // Run the match CakeArenaRequest request = new CakeArenaRequest( opponent.getId(), match, ignoreCounters ); RequestThread.postRequest( request ); // If the request failed to produce responseText, bail if ( request.responseText == null ) { return 0; } // Figure out how much xp the familiar earned in this contest int xp = request.earnedXP(); // Log the results, add familiar items, deduct turn. status.processMatchResult( request, xp ); // Return the amount of XP the familiar earned return request.badContest() ? -1 : xp; } /** * A class to hold everything that can modify the weight of the current familiar: available items and whether they * are equipped available skills and whether they are active */ private static class FamiliarStatus { // The familiar we are tracking FamiliarData familiar; // How many turns we have trained it int turns; // Details about its special familiar item AdventureResult familiarItem; int familiarItemWeight; // Currently equipped gear which affects weight AdventureResult hat; AdventureResult item; AdventureResult weapon; AdventureResult offhand; AdventureResult[] acc = new AdventureResult[ 3 ]; // Available equipment which affects weight AdventureResult specItem; int specWeight; boolean pithHelmet; boolean crumpledFedora; boolean leadNecklace; boolean ratHeadBalloon; boolean bathysphere; boolean dasBoot; boolean pumpkinBucket; boolean flowerBouquet; boolean boxFireworks; boolean sugarShield; boolean doppelganger; int tpCount; int whipCount; AdventureResult[] tp = new AdventureResult[ 3 ]; // Weights TreeSet<Integer> weights; // Gear sets ArrayList<GearSet> gearSets; public FamiliarStatus() { // Find out which familiar we are working with this.familiar = KoLCharacter.getFamiliar(); // Get details about the special item it can wear int itemId = FamiliarDatabase.getFamiliarItemId( this.familiar.getId() ); this.familiarItem = ItemPool.get( itemId ); this.familiarItemWeight = FamiliarData.itemWeightModifier( itemId ); // No turns have been used yet this.turns = 0; // Initialize set of weights this.weights = new TreeSet<Integer>(); // Initialize the list of GearSets this.gearSets = new ArrayList<GearSet>(); // Check skills and equipment this.updateStatus(); } public void updateStatus() { // Check available skills this.checkSkills(); // Check current equipment this.checkCurrentEquipment(); // Check available equipment this.checkAvailableEquipment( (SortedListModel) KoLConstants.inventory ); } private void checkSkills() { // Look at skills to decide which ones are possible FamiliarTrainingFrame.sympathyAvailable = KoLCharacter.hasAmphibianSympathy(); FamiliarTrainingFrame.empathyAvailable = KoLCharacter.hasSkill( "Empathy of the Newt" ) && UseSkillRequest.hasTotem(); FamiliarTrainingFrame.leashAvailable = KoLCharacter.hasSkill( "Leash of Linguini" ); FamiliarTrainingFrame.bestialAvailable = InventoryManager.itemAvailable( FamiliarTrainingFrame.HALF_ORCHID ); FamiliarTrainingFrame.blackConeAvailable = InventoryManager.itemAvailable( FamiliarTrainingFrame.BLACK_SNOWCONE ); FamiliarTrainingFrame.greenConeAvailable = InventoryManager.itemAvailable( FamiliarTrainingFrame.GREEN_SNOWCONE ); FamiliarTrainingFrame.greenHeartAvailable = InventoryManager.itemAvailable( FamiliarTrainingFrame.GREEN_CANDY ); FamiliarTrainingFrame.heavyPettingAvailable = InventoryManager.itemAvailable( FamiliarTrainingFrame.BUFFING_SPRAY ); FamiliarTrainingFrame.worstEnemyAvailable = InventoryManager.itemAvailable( FamiliarTrainingFrame.SPIKY_COLLAR ); // Look at effects to decide which ones are active; FamiliarTrainingFrame.empathyActive = FamiliarTrainingFrame.EMPATHY.getCount( KoLConstants.activeEffects ); FamiliarTrainingFrame.leashActive = FamiliarTrainingFrame.LEASH.getCount( KoLConstants.activeEffects ); FamiliarTrainingFrame.bestialActive = FamiliarTrainingFrame.BESTIAL_SYMPATHY.getCount( KoLConstants.activeEffects ); FamiliarTrainingFrame.blackTongueActive = FamiliarTrainingFrame.BLACK_TONGUE.getCount( KoLConstants.activeEffects ); FamiliarTrainingFrame.greenGlowActive = FamiliarTrainingFrame.GREEN_GLOW.getCount( KoLConstants.activeEffects ); FamiliarTrainingFrame.greenHeartActive = FamiliarTrainingFrame.GREEN_HEART.getCount( KoLConstants.activeEffects ); FamiliarTrainingFrame.greenTongueActive = FamiliarTrainingFrame.GREEN_TONGUE.getCount( KoLConstants.activeEffects ); FamiliarTrainingFrame.heavyPettingActive = FamiliarTrainingFrame.HEAVY_PETTING.getCount( KoLConstants.activeEffects ); FamiliarTrainingFrame.worstEnemyActive = FamiliarTrainingFrame.WORST_ENEMY.getCount( KoLConstants.activeEffects ); } private void checkCurrentEquipment() { this.checkCurrentEquipment( EquipmentManager.getEquipment( EquipmentManager.WEAPON ), EquipmentManager.getEquipment( EquipmentManager.OFFHAND ), EquipmentManager.getEquipment( EquipmentManager.HAT ), EquipmentManager.getEquipment( EquipmentManager.FAMILIAR ), EquipmentManager.getEquipment( EquipmentManager.ACCESSORY1 ), EquipmentManager.getEquipment( EquipmentManager.ACCESSORY2 ), EquipmentManager.getEquipment( EquipmentManager.ACCESSORY3 ) ); } private void checkCurrentEquipment( final AdventureResult weapon, final AdventureResult offhand, final AdventureResult hat, final AdventureResult item, final AdventureResult acc1, final AdventureResult acc2, final AdventureResult acc3 ) { // Initialize equipment to default this.weapon = null; this.offhand = null; this.hat = null; this.item = null; this.specItem = null; this.specWeight = 0; this.pithHelmet = false; this.crumpledFedora = false; this.leadNecklace = false; this.ratHeadBalloon = false; this.bathysphere = false; this.dasBoot = false; this.pumpkinBucket = false; this.flowerBouquet = false; this.boxFireworks = false; this.sugarShield = false; this.doppelganger = false; this.whipCount = 0; this.tpCount = 0; // Check hat for pithiness if ( hat.getItemId() == FamiliarTrainingFrame.PITH_HELMET.getItemId() ) { this.pithHelmet = true; this.hat = FamiliarTrainingFrame.PITH_HELMET; } if ( hat.getItemId() == FamiliarTrainingFrame.CRUMPLED_FEDORA.getItemId() ) { this.crumpledFedora = true; this.hat = FamiliarTrainingFrame.CRUMPLED_FEDORA; } if ( weapon != null && weapon.getItemId() == FamiliarTrainingFrame.BAR_WHIP.getItemId() ) { ++this.whipCount; this.weapon = FamiliarTrainingFrame.BAR_WHIP; } if ( offhand != null && offhand.getItemId() == FamiliarTrainingFrame.BAR_WHIP.getItemId() ) { ++this.whipCount; this.offhand = FamiliarTrainingFrame.BAR_WHIP; } // Check current familiar item if ( item != null ) { int itemId = item.getItemId(); if ( itemId == this.familiarItem.getItemId() ) { this.item = this.specItem = this.familiarItem; this.specWeight = this.familiarItemWeight; } if ( itemId == FamiliarData.PUMPKIN_BUCKET.getItemId() ) { this.pumpkinBucket = true; this.item = FamiliarData.PUMPKIN_BUCKET; } if ( itemId == FamiliarData.FLOWER_BOUQUET.getItemId() ) { this.flowerBouquet = true; this.item = FamiliarData.FLOWER_BOUQUET; } if ( itemId == FamiliarData.FIREWORKS.getItemId() ) { this.boxFireworks = true; this.item = FamiliarData.FIREWORKS; } if ( itemId == FamiliarData.SUGAR_SHIELD.getItemId() ) { this.sugarShield = true; this.item = FamiliarData.SUGAR_SHIELD; } if ( itemId == FamiliarData.LEAD_NECKLACE.getItemId() ) { this.leadNecklace = true; this.item = FamiliarData.LEAD_NECKLACE; } if ( itemId == FamiliarData.RAT_HEAD_BALLOON.getItemId() ) { this.ratHeadBalloon = true; this.item = FamiliarData.RAT_HEAD_BALLOON; } if ( itemId == ItemPool.BATHYSPHERE ) { this.bathysphere = true; this.item = FamiliarData.BATHYSPHERE; } if ( itemId == ItemPool.DAS_BOOT ) { this.dasBoot = true; this.item = FamiliarData.DAS_BOOT; } if ( itemId == FamiliarData.DOPPELGANGER.getItemId() ) { this.doppelganger = true; this.item = FamiliarData.DOPPELGANGER; } } // Check accessories for tininess and plasticity this.checkAccessory( 0, acc1 ); this.checkAccessory( 1, acc2 ); this.checkAccessory( 2, acc3 ); } private void checkAccessory( final int index, final AdventureResult accessory ) { if ( this.isTinyPlasticItem( accessory ) ) { this.acc[ index ] = accessory; this.tp[ this.tpCount++ ] = accessory; } } public boolean isTinyPlasticItem( final AdventureResult ar ) { if ( ar == null ) { return false; } int id = ar.getItemId(); for ( int i = 0; i < FamiliarTrainingFrame.tinyPlasticNormal.length; ++i ) { if ( id == FamiliarTrainingFrame.tinyPlasticNormal[ i ] ) { return true; } } for ( int i = 0; i < FamiliarTrainingFrame.tinyPlasticCrimbo.length; ++i ) { if ( id == FamiliarTrainingFrame.tinyPlasticCrimbo[ i ] ) { return true; } } return false; } private void checkAvailableEquipment( final LockableListModel inventory ) { // If not wearing a pith helmet, search inventory this.pithHelmet |= FamiliarTrainingFrame.PITH_HELMET.getCount( inventory ) > 0 && EquipmentManager.canEquip( "plexiglass pith helmet" ); // If not wearing a crumpled fedora helmet, search inventory this.crumpledFedora |= FamiliarTrainingFrame.CRUMPLED_FEDORA.getCount( inventory ) > 0 && EquipmentManager.canEquip( "crumpled felt fedora" ); // If current familiar item is not the special item and // such an item affects weight, search inventory if ( this.familiarItem != this.item && this.familiarItemWeight != 0 && this.familiarItem.getCount( inventory ) > 0 ) { this.specItem = this.familiarItem; this.specWeight = this.familiarItemWeight; } if ( !KoLCharacter.isHardcore() ) { // If current familiar is not wearing a pumpkin bucket, // search inventory this.pumpkinBucket |= FamiliarData.PUMPKIN_BUCKET.getCount( inventory ) > 0; // If current familiar is not wearing a Mayflower bouquet, // search inventory this.flowerBouquet |= FamiliarData.FLOWER_BOUQUET.getCount( inventory ) > 0; // If current familiar is not wearing a box of fireworks, // search inventory this.boxFireworks |= FamiliarData.FIREWORKS.getCount( inventory ) > 0; } // If current familiar is not wearing a sugar shield, // search inventory this.sugarShield |= FamiliarData.SUGAR_SHIELD.getCount( inventory ) > 0; // If current familiar is not wearing a lead necklace, // search inventory this.leadNecklace |= FamiliarData.LEAD_NECKLACE.getCount( inventory ) > 0; // If current familiar is not wearing a rat head // balloon, search inventory this.ratHeadBalloon |= FamiliarData.RAT_HEAD_BALLOON.getCount( inventory ) > 0; // If current familiar is not wearing a bathysphere, // search inventory this.bathysphere |= FamiliarData.BATHYSPHERE.getCount( inventory ) > 0; // If current familiar is not wearing das boot // search inventory this.dasBoot |= FamiliarData.DAS_BOOT.getCount( inventory ) > 0; // If current familiar is not wearing a doppel, // search inventory this.doppelganger |= FamiliarData.DOPPELGANGER.getCount( inventory ) > 0; this.whipCount = Math.min( KoLCharacter.hasSkill( "Double-Fisted Skull Smashing" ) ? 2 : 1, this.whipCount + FamiliarTrainingFrame.BAR_WHIP.getCount( inventory ) ); // If equipped with fewer than three tiny plastic items // equipped, search inventory for more for ( int i = 0; i < FamiliarTrainingFrame.tinyPlasticNormal.length; ++i ) { this.addTinyPlastic( FamiliarTrainingFrame.tinyPlasticNormal[ i ] ); } // Check Tiny Plastic Crimbo objects for ( int i = 0; i < FamiliarTrainingFrame.tinyPlasticCrimbo.length; ++i ) { this.addTinyPlastic( FamiliarTrainingFrame.tinyPlasticCrimbo[ i ] ); } // If we're not training a chameleon and we don't have // a lead necklace or a rat head balloon, search other // familiars; we'll steal it from them if necessary if ( this.familiar.getId() == FamiliarPool.CHAMELEON || this.leadNecklace && this.ratHeadBalloon && this.pumpkinBucket && this.flowerBouquet && this.boxFireworks && this.sugarShield && this.bathysphere && this.dasBoot ) { return; } // Find first familiar with item List familiars = KoLCharacter.getFamiliarList(); for ( int i = 0; i < familiars.size(); ++i ) { FamiliarData familiar = (FamiliarData) familiars.get( i ); AdventureResult item = familiar.getItem(); if ( item == null ) { continue; } this.leadNecklace |= item.getItemId() == FamiliarData.LEAD_NECKLACE.getItemId(); this.ratHeadBalloon |= item.getItemId() == FamiliarData.RAT_HEAD_BALLOON.getItemId(); this.bathysphere |= item.getItemId() == ItemPool.BATHYSPHERE; this.dasBoot |= item.getItemId() == ItemPool.DAS_BOOT; this.pumpkinBucket |= item.getItemId() == FamiliarData.PUMPKIN_BUCKET.getItemId(); this.flowerBouquet |= item.getItemId() == FamiliarData.FLOWER_BOUQUET.getItemId(); this.doppelganger |= item.getItemId() == FamiliarData.DOPPELGANGER.getItemId(); this.boxFireworks |= item.getItemId() == FamiliarData.FIREWORKS.getItemId(); this.sugarShield |= item.getItemId() == FamiliarData.SUGAR_SHIELD.getItemId(); } } private void addTinyPlastic( final int id ) { if ( this.tpCount == 3 ) { return; } AdventureResult ar = ItemPool.get( id ); int count = ar.getCount( KoLConstants.inventory ); // Make a new one for each slot while ( count-- > 0 && this.tpCount < 3 ) { this.tp[ this.tpCount++ ] = ItemPool.get( id ); } } /** *********************************************************** */ public int[] getWeights() { // Clear the set of possible weights this.weights.clear(); // Calculate base weight int weight = this.familiar.getWeight(); // Sympathy adds 5 lbs. except to a dodecapede, for // which it subtracts 5 lbs. if ( FamiliarTrainingFrame.sympathyAvailable ) { weight += this.familiar.getId() == FamiliarPool.DODECAPEDE ? -5 : 5; } if ( FamiliarTrainingFrame.empathyActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.leashActive > 0 ) { weight += 5; } // One snowcone effect at a time if ( FamiliarTrainingFrame.greenTongueActive > 0 || FamiliarTrainingFrame.blackTongueActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.bestialActive > 0 ) { weight += 3; } if ( FamiliarTrainingFrame.greenGlowActive > 0 ) { weight += 10; } if ( FamiliarTrainingFrame.greenHeartActive > 0 ) { weight += 3; } if ( FamiliarTrainingFrame.heavyPettingActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.worstEnemyActive > 0 ) { weight += 5; } this.getItemWeights( weight ); // Make an array to hold values Object[] vals = this.weights.toArray(); int[] value = new int[ vals.length ]; // Read Integers from the set and store ints for ( int i = 0; i < vals.length; ++i ) { value[ i ] = ( (Integer) vals[ i ] ).intValue(); } return value; } private void getItemWeights( final int weight ) { // Get current familiar FamiliarData familiar = KoLCharacter.getFamiliar(); // Calculate Accessory Weights with no Familiar Items this.getAccessoryWeights( weight ); // Only consider familiar items if current familiar is // not a chameleon and you have no doppelganger if ( familiar.getId() == FamiliarPool.CHAMELEON || this.doppelganger ) { return; } // If familiar specific item adds weight, calculate if ( this.specWeight != 0 ) { this.getAccessoryWeights( weight + this.specWeight ); } // If we have a sugar shield, use it if ( this.sugarShield ) { this.getAccessoryWeights( weight + 10 ); } // If we have a pumpkin bucket, use it if ( this.pumpkinBucket ) { this.getAccessoryWeights( weight + 5 ); } // If we have a Mayflower bouquet, use it if ( this.flowerBouquet ) { this.getAccessoryWeights( weight + 5 ); } // If we have a little box of fireworks, use it if ( this.boxFireworks ) { this.getAccessoryWeights( weight + 5 ); } // If we have a lead necklace, use it if ( this.leadNecklace ) { this.getAccessoryWeights( weight + 3 ); } // If we have a rat head balloon, use it if ( this.ratHeadBalloon ) { this.getAccessoryWeights( weight - 3 ); } // If we have das boot, use it if ( this.dasBoot ) { this.getAccessoryWeights( weight - 10 ); } // If we have a bathysphere, use it if ( this.bathysphere ) { this.getAccessoryWeights( weight - 20 ); } } private void getAccessoryWeights( final int weight ) { // Calculate using variable #s of tiny plastic objects for ( int i = 0; i <= this.tpCount; ++i ) { this.getWhipWeights( weight + i ); } } private void getWhipWeights( final int weight ) { // Calculate using variable #s of whips for ( int i = 0; i <= this.whipCount; ++i ) { this.getHatWeights( weight + i * 2 ); } } private void getHatWeights( final int weight ) { // Add weight with helmet if ( this.pithHelmet ) { this.weights.add( IntegerPool.get( Math.max( weight + 5, 1 ) ) ); } // Add weight with fedora if ( this.crumpledFedora ) { this.weights.add( IntegerPool.get( Math.max( weight + 10, 1 ) ) ); } // Add weight with no helmet this.weights.add( IntegerPool.get( Math.max( weight, 1 ) ) ); } /** *********************************************************** */ /* * Change gear and cast buffs such that your familiar's modified weight is as specified. */ public void changeGear( final int weight ) { // Make a GearSet describing what we have now GearSet current = new GearSet(); if ( this.doppelganger ) { RequestThread.postRequest( new EquipmentRequest( FamiliarData.DOPPELGANGER, EquipmentManager.FAMILIAR ) ); } // If we are already suitably equipped, stop now if ( weight == current.weight() ) { return; } // Choose a new GearSet with desired weight GearSet next = this.chooseGearSet( current, weight ); // If we couldn't pick one, that's an internal error if ( next == null || weight < next.weight() ) { FamiliarTrainingFrame.statusMessage( MafiaState.ERROR, "Could not select gear set to achieve " + weight + " lbs." ); if ( next == null ) { FamiliarTrainingFrame.results.append( "No gear set found.<br>" ); } else { FamiliarTrainingFrame.results.append( "Selected gear set provides " + next.weight() + " lbs.<br>" ); } return; } // Change into the new GearSet this.changeGear( current, next ); } /* * Debug: choose and print desired GearSet */ public void chooseGear( final int weight ) { // Make a GearSet describing what we have now GearSet current = new GearSet(); // If we are already suitably equipped, stop now if ( weight == current.weight() ) { FamiliarTrainingFrame.results.append( "Current gear is acceptable/<br>" ); return; } // Choose a new GearSet with desired weight GearSet next = this.chooseGearSet( current, weight ); if ( next == null ) { FamiliarTrainingFrame.results.append( "Could not find a gear set to achieve " + weight + " lbs.<br>" ); return; } FamiliarTrainingFrame.results.append( "Chosen gear set: " + next + " provides " + next.weight() + " lbs.<br>" ); } /* * Swap gear and cast buffs to match desired GearSet. Return false if failed to swap or buff */ public void changeGear( final GearSet current, final GearSet next ) { this.swapItem( current.weapon, next.weapon, EquipmentManager.WEAPON ); this.swapItem( current.offhand, next.offhand, EquipmentManager.OFFHAND ); this.swapItem( current.hat, next.hat, EquipmentManager.HAT ); this.swapItem( current.item, next.item, EquipmentManager.FAMILIAR ); this.swapItem( current.acc1, next.acc1, EquipmentManager.ACCESSORY1 ); this.swapItem( current.acc2, next.acc2, EquipmentManager.ACCESSORY2 ); this.swapItem( current.acc3, next.acc3, EquipmentManager.ACCESSORY3 ); } private void swapItem( final AdventureResult current, final AdventureResult next, final int slot ) { // Nothing to do if already wearing this item if ( current == next ) { return; } // EquipmentRequest will notice if something else is in // the slot and will remove it first, if necessary // // Therefore, we can simply equip the new item. if ( next != null ) { FamiliarTrainingFrame.results.append( "Putting on " + next.getName() + "<br>" ); RequestThread.postRequest( new EquipmentRequest( next, slot ) ); this.setItem( slot, next ); } else if ( current != null ) { FamiliarTrainingFrame.results.append( "Taking off " + current.getName() + "<br>" ); RequestThread.postRequest( new EquipmentRequest( EquipmentRequest.UNEQUIP, slot ) ); this.setItem( slot, null ); } } private void setItem( final int slot, final AdventureResult item ) { switch ( slot ) { case EquipmentManager.WEAPON: this.weapon = item; break; case EquipmentManager.OFFHAND: this.offhand = item; break; case EquipmentManager.HAT: this.hat = item; break; case EquipmentManager.FAMILIAR: this.item = item; break; case EquipmentManager.ACCESSORY1: this.acc[ 0 ] = item; break; case EquipmentManager.ACCESSORY2: this.acc[ 1 ] = item; break; case EquipmentManager.ACCESSORY3: this.acc[ 2 ] = item; break; } } /* * Choose a GearSet that gives the current familiar the desired weight. Of the (potentially many) possible such * sets, return the one that requires the smallest number of changes from what is currently in effect. */ private GearSet chooseGearSet( final GearSet current, final int weight ) { // Clear out the accumulated list of GearSets this.gearSets.clear(); this.getHatGearSets( weight ); // Iterate over all the GearSets and choose the first // one which is closest to the current GearSet Collections.sort( this.gearSets ); return this.gearSets.isEmpty() ? null : (GearSet) this.gearSets.get( 0 ); } private void getHatGearSets( final int weight ) { if ( this.pithHelmet ) { this.getItemGearSets( weight, FamiliarTrainingFrame.PITH_HELMET ); } if ( this.crumpledFedora ) { this.getItemGearSets( weight, FamiliarTrainingFrame.CRUMPLED_FEDORA ); } this.getItemGearSets( weight, null ); } private void getItemGearSets( final int weight, final AdventureResult hat ) { // If it's a comma chameleon or we have a doppelganger, // don't look at familiar items. if ( KoLCharacter.getFamiliar().getId() == FamiliarPool.CHAMELEON ) { this.getAccessoryGearSets( weight, null, hat ); return; } if ( this.doppelganger ) { this.getAccessoryGearSets( weight, FamiliarData.DOPPELGANGER, hat ); return; } if ( this.specItem != null ) { this.getAccessoryGearSets( weight, this.specItem, hat ); } if ( this.pumpkinBucket ) { this.getAccessoryGearSets( weight, FamiliarData.PUMPKIN_BUCKET, hat ); } if ( this.flowerBouquet ) { this.getAccessoryGearSets( weight, FamiliarData.FLOWER_BOUQUET, hat ); } if ( this.boxFireworks ) { this.getAccessoryGearSets( weight, FamiliarData.FIREWORKS, hat ); } if ( this.sugarShield ) { this.getAccessoryGearSets( weight, FamiliarData.SUGAR_SHIELD, hat ); } if ( this.leadNecklace ) { this.getAccessoryGearSets( weight, FamiliarData.LEAD_NECKLACE, hat ); } if ( this.ratHeadBalloon ) { this.getAccessoryGearSets( weight, FamiliarData.RAT_HEAD_BALLOON, hat ); } if ( this.bathysphere ) { this.getAccessoryGearSets( weight, FamiliarData.BATHYSPHERE, hat ); } if ( this.dasBoot ) { this.getAccessoryGearSets( weight, FamiliarData.DAS_BOOT, hat ); } this.getAccessoryGearSets( weight, null, hat ); } private void getAccessoryGearSets( final int weight, final AdventureResult item, final AdventureResult hat ) { // No matter how many Tiny Plastic Objects we have, a // configuration with none equipped is legal this.addGearSet( weight, null, null, null, item, hat ); if ( this.tpCount == 0 ) { return; } // If we have at least one and it started out equipped, // then it might be in any of the three accessory // slots. this.addGearSet( weight, this.tp[ 0 ], null, null, item, hat ); this.addGearSet( weight, null, this.tp[ 0 ], null, item, hat ); this.addGearSet( weight, null, null, this.tp[ 0 ], item, hat ); if ( this.tpCount == 1 ) { return; } // If we have at least two and they were both equipped // when we came in, they'll be in one of the following // patterns. this.addGearSet( weight, this.tp[ 0 ], this.tp[ 1 ], null, item, hat ); this.addGearSet( weight, this.tp[ 0 ], null, this.tp[ 1 ], item, hat ); this.addGearSet( weight, null, this.tp[ 0 ], this.tp[ 1 ], item, hat ); // If one of the two was in the inventory, the first // could have been in any of the three accessory // slots. Add a pattern for where it was in the third // slot. this.addGearSet( weight, this.tp[ 1 ], null, this.tp[ 0 ], item, hat ); if ( this.tpCount == 2 ) { return; } // If we have three and they were all equipped when we // came in, they'll be in the following pattern this.addGearSet( weight, this.tp[ 0 ], this.tp[ 1 ], this.tp[ 2 ], item, hat ); // If two of them were equipped and the third was in // the inventory, the following patterns are legal this.addGearSet( weight, this.tp[ 0 ], this.tp[ 2 ], this.tp[ 1 ], item, hat ); this.addGearSet( weight, this.tp[ 2 ], this.tp[ 0 ], this.tp[ 1 ], item, hat ); // If only one was equipped, based on which // two-accessory patterns are legal, the following // three-accessory patterns are also legal this.addGearSet( weight, this.tp[ 1 ], this.tp[ 2 ], this.tp[ 0 ], item, hat ); } private void addGearSet( final int weight, final AdventureResult acc1, final AdventureResult acc2, final AdventureResult acc3, final AdventureResult item, final AdventureResult hat ) { int gearWeight = this.gearSetWeight( null, null, acc1, acc2, acc3, item, hat ); if ( weight == gearWeight ) { this.gearSets.add( new GearSet( null, null, acc1, acc2, acc3, item, hat ) ); } if ( this.whipCount > 0 ) { gearWeight = this.gearSetWeight( FamiliarTrainingFrame.BAR_WHIP, null, acc1, acc2, acc3, item, hat ); if ( weight == gearWeight ) { this.gearSets.add( new GearSet( FamiliarTrainingFrame.BAR_WHIP, null, acc1, acc2, acc3, item, hat ) ); } } if ( this.whipCount > 1 ) { gearWeight = this.gearSetWeight( FamiliarTrainingFrame.BAR_WHIP, FamiliarTrainingFrame.BAR_WHIP, acc1, acc2, acc3, item, hat ); if ( weight == gearWeight ) { this.gearSets.add( new GearSet( FamiliarTrainingFrame.BAR_WHIP, FamiliarTrainingFrame.BAR_WHIP, acc1, acc2, acc3, item, hat ) ); } } } private int gearSetWeight( final AdventureResult weapon, final AdventureResult offhand, final AdventureResult acc1, final AdventureResult acc2, final AdventureResult acc3, final AdventureResult item, final AdventureResult hat ) { int weight = this.familiar.getWeight(); if ( hat == FamiliarTrainingFrame.CRUMPLED_FEDORA ) { weight += 10; } else if ( hat == FamiliarTrainingFrame.PITH_HELMET ) { weight += 5; } if ( FamiliarTrainingFrame.sympathyAvailable ) { weight += this.familiar.getId() == FamiliarPool.DODECAPEDE ? -5 : 5; } if ( FamiliarTrainingFrame.leashActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.empathyActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.bestialActive > 0 ) { weight += 3; } if ( FamiliarTrainingFrame.greenGlowActive > 0 ) { weight += 10; } if ( FamiliarTrainingFrame.greenHeartActive > 0 ) { weight += 3; } if ( FamiliarTrainingFrame.greenTongueActive > 0 || FamiliarTrainingFrame.blackTongueActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.heavyPettingActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.worstEnemyActive > 0 ) { weight += 5; } if ( item == FamiliarData.DOPPELGANGER ) { ; } else if ( item == this.specItem ) { weight += this.specWeight; } else if ( item == FamiliarData.SUGAR_SHIELD ) { weight += 10; } else if ( item == FamiliarData.PUMPKIN_BUCKET ) { weight += 5; } else if ( item == FamiliarData.FLOWER_BOUQUET ) { weight += 5; } else if ( item == FamiliarData.FIREWORKS ) { weight += 5; } else if ( item == FamiliarData.LEAD_NECKLACE ) { weight += 3; } else if ( item == FamiliarData.RAT_HEAD_BALLOON ) { weight -= 3; } else if ( item == FamiliarData.BATHYSPHERE ) { weight -= 20; } else if ( item == FamiliarData.DAS_BOOT ) { weight -= 10; } if ( weapon == FamiliarTrainingFrame.BAR_WHIP ) { weight += 2; } if ( offhand == FamiliarTrainingFrame.BAR_WHIP ) { weight += 2; } if ( this.isTinyPlasticItem( acc1 ) ) { weight += 1; } if ( this.isTinyPlasticItem( acc2 ) ) { weight += 1; } if ( this.isTinyPlasticItem( acc3 ) ) { weight += 1; } return Math.max( weight, 1 ); } /** *********************************************************** */ public void processMatchResult( final CakeArenaRequest request, final int xp ) { String response = request.responseText; // If the contest did not take place, bail now if ( response.indexOf( "You enter" ) == -1 ) { return; } // Find and report how much experience was gained String message; if ( xp > 0 ) { message = this.familiar.getName() + " gains " + xp + " experience" + ( response.indexOf( "gains a pound" ) != -1 ? " and a pound." : "." ); } else { message = this.familiar.getName() + " lost."; } FamiliarTrainingFrame.statusMessage( MafiaState.CONTINUE, message ); // If a prize was won, report it Matcher prizeMatcher = FamiliarTrainingFrame.PRIZE_PATTERN.matcher( response ); Matcher stealMatcher = FamiliarTrainingFrame.STEAL_PATTERN.matcher( response ); String prize = null; if ( prizeMatcher.find() ) { prize = prizeMatcher.group( 1 ); FamiliarTrainingFrame.statusMessage( MafiaState.CONTINUE, "You win a prize: " + prize + "." ); } else if ( stealMatcher.find() ) { prize = stealMatcher.group( 1 ); FamiliarTrainingFrame.statusMessage( MafiaState.CONTINUE, "Your familiar steals an item: " + prize + "." ); } if ( prize != null ) { if ( prize.equals( FamiliarData.LEAD_NECKLACE.getName() ) ) { this.leadNecklace = true; } else if ( this.familiarItemWeight > 0 ) { if ( this.specItem == null ) { this.specItem = this.familiarItem; this.specWeight = this.familiarItemWeight; } } } // Increment count of turns in this training session this.turns++ ; // Decrement buffs if ( FamiliarTrainingFrame.leashActive > 0 ) { FamiliarTrainingFrame.leashActive-- ; } if ( FamiliarTrainingFrame.empathyActive > 0 ) { FamiliarTrainingFrame.empathyActive-- ; } if ( FamiliarTrainingFrame.bestialActive > 0 ) { FamiliarTrainingFrame.bestialActive-- ; } if ( FamiliarTrainingFrame.blackTongueActive > 0 ) { FamiliarTrainingFrame.blackTongueActive-- ; } if ( FamiliarTrainingFrame.greenGlowActive > 0 ) { FamiliarTrainingFrame.greenHeartActive-- ; } if ( FamiliarTrainingFrame.greenHeartActive > 0 ) { FamiliarTrainingFrame.greenHeartActive-- ; } if ( FamiliarTrainingFrame.greenTongueActive > 0 ) { FamiliarTrainingFrame.greenTongueActive-- ; } if ( FamiliarTrainingFrame.heavyPettingActive > 0 ) { FamiliarTrainingFrame.heavyPettingActive-- ; } if ( FamiliarTrainingFrame.worstEnemyActive > 0 ) { FamiliarTrainingFrame.worstEnemyActive-- ; } } public FamiliarData getFamiliar() { return this.familiar; } public int turnsUsed() { return this.turns; } public int baseWeight() { return this.familiar.getWeight(); } public int maxWeight( final boolean buffs ) { // Start with current base weight of familiar int weight = this.familiar.getWeight(); // Add possible skills if ( FamiliarTrainingFrame.sympathyAvailable ) { weight += this.familiar.getId() == FamiliarPool.DODECAPEDE ? -5 : 5; } if ( buffs ) { if ( FamiliarTrainingFrame.leashAvailable || FamiliarTrainingFrame.leashActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.empathyAvailable || FamiliarTrainingFrame.empathyActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.bestialAvailable || FamiliarTrainingFrame.bestialActive > 0 ) { weight += 3; } if ( FamiliarTrainingFrame.greenConeAvailable || FamiliarTrainingFrame.greenTongueActive > 0 || FamiliarTrainingFrame.blackConeAvailable || FamiliarTrainingFrame.blackTongueActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.greenGlowActive > 0 ) { weight += 10; } if ( FamiliarTrainingFrame.greenHeartAvailable || FamiliarTrainingFrame.greenHeartActive > 0 ) { weight += 3; } if ( FamiliarTrainingFrame.heavyPettingAvailable || FamiliarTrainingFrame.heavyPettingActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.worstEnemyAvailable || FamiliarTrainingFrame.worstEnemyActive > 0 ) { weight += 5; } } else { if ( FamiliarTrainingFrame.leashActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.empathyActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.bestialActive > 0 ) { weight += 3; } if ( FamiliarTrainingFrame.greenGlowActive > 0 ) { weight += 10; } if ( FamiliarTrainingFrame.greenHeartActive > 0 ) { weight += 3; } if ( FamiliarTrainingFrame.greenTongueActive > 0 || FamiliarTrainingFrame.blackTongueActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.heavyPettingActive > 0 ) { weight += 5; } if ( FamiliarTrainingFrame.worstEnemyActive > 0 ) { weight += 5; } } // Add available familiar items if ( this.pumpkinBucket ) { weight += 5; } else if ( this.flowerBouquet ) { weight += 5; } else if ( this.boxFireworks ) { weight += 5; } else if ( this.sugarShield ) { weight += 10; } else if ( this.specWeight > 3 ) { weight += this.specWeight; } else if ( this.leadNecklace ) { weight += 3; } // Add available tiny plastic items weight += this.tpCount; weight += 2 * this.whipCount; // Add crumpled fedora if ( this.crumpledFedora ) { weight += 10; } else if ( this.pithHelmet ) { weight += 5; } return Math.max( weight, 1 ); } public String printAvailableBuffs() { StringBuilder text = new StringBuilder(); text.append( "Castable buffs:" ); if ( FamiliarTrainingFrame.empathyAvailable ) { text.append( " Empathy (+5)" ); } if ( FamiliarTrainingFrame.leashAvailable ) { text.append( " Leash (+5)" ); } if ( !FamiliarTrainingFrame.empathyAvailable && !FamiliarTrainingFrame.leashAvailable ) { text.append( " None" ); } text.append( "<br>" ); return text.toString(); } public String printCurrentBuffs() { StringBuilder text = new StringBuilder(); text.append( "Current buffs:" ); if ( FamiliarTrainingFrame.sympathyAvailable ) { text.append( " Sympathy (" + ( this.familiar.getId() == FamiliarPool.DODECAPEDE ? "-" : "+" ) + "5 permanent)" ); } if ( FamiliarTrainingFrame.empathyActive > 0 ) { text.append( " Empathy (+5 for " + FamiliarTrainingFrame.empathyActive + " turns)" ); } if ( FamiliarTrainingFrame.leashActive > 0 ) { text.append( " Leash (+5 for " + FamiliarTrainingFrame.leashActive + " turns)" ); } if ( FamiliarTrainingFrame.bestialActive > 0 ) { text.append( " Bestial Sympathy (+3 for " + FamiliarTrainingFrame.bestialActive + " turns)" ); } if ( FamiliarTrainingFrame.blackTongueActive > 0 ) { text.append( " Black Tongue (+5 for " + FamiliarTrainingFrame.blackTongueActive + " turns)" ); } if ( FamiliarTrainingFrame.greenGlowActive > 0 ) { text.append( " Healthy Green Glow (+10 for " + FamiliarTrainingFrame.greenGlowActive + " turns)" ); } if ( FamiliarTrainingFrame.greenHeartActive > 0 ) { text.append( " Heart of Green (+3 for " + FamiliarTrainingFrame.greenHeartActive + " turns)" ); } if ( FamiliarTrainingFrame.greenTongueActive > 0 ) { text.append( " Green Tongue (+5 for " + FamiliarTrainingFrame.greenTongueActive + " turns)" ); } if ( FamiliarTrainingFrame.heavyPettingActive > 0 ) { text.append( " Heavy Petting (+5 for " + FamiliarTrainingFrame.heavyPettingActive + " turns)" ); } if ( FamiliarTrainingFrame.worstEnemyActive > 0 ) { text.append( " Man's Worst Enemy (+5 for " + FamiliarTrainingFrame.worstEnemyActive + " turns)" ); } if ( !FamiliarTrainingFrame.sympathyAvailable && FamiliarTrainingFrame.empathyActive == 0 && FamiliarTrainingFrame.leashActive == 0 && FamiliarTrainingFrame.bestialActive == 0 && FamiliarTrainingFrame.blackTongueActive == 0 && FamiliarTrainingFrame.greenGlowActive == 0 && FamiliarTrainingFrame.greenHeartActive == 0 && FamiliarTrainingFrame.greenTongueActive == 0 && FamiliarTrainingFrame.heavyPettingActive == 0 && FamiliarTrainingFrame.worstEnemyActive == 0 ) { text.append( " None" ); } text.append( "<br>" ); return text.toString(); } public String printCurrentEquipment() { StringBuilder text = new StringBuilder(); text.append( "Current equipment:" ); if ( this.weapon == FamiliarTrainingFrame.BAR_WHIP ) { text.append( " bar whip (+2)" ); } if ( this.offhand == FamiliarTrainingFrame.BAR_WHIP ) { text.append( " bar whip (+2)" ); } if ( this.hat == FamiliarTrainingFrame.CRUMPLED_FEDORA ) { text.append( " crumpled felt fedora (+10)" ); } if ( this.hat == FamiliarTrainingFrame.PITH_HELMET ) { text.append( " plexiglass pith helmet (+5)" ); } if ( this.item == FamiliarData.DOPPELGANGER ) { text.append( " " + FamiliarData.DOPPELGANGER.getName() + " (+0)" ); } else if ( this.item == FamiliarData.PUMPKIN_BUCKET ) { text.append( " " + FamiliarData.PUMPKIN_BUCKET.getName() + " (+5)" ); } else if ( this.item == FamiliarData.FLOWER_BOUQUET ) { text.append( " " + FamiliarData.FLOWER_BOUQUET.getName() + " (+5)" ); } else if ( this.item == FamiliarData.FIREWORKS ) { text.append( " " + FamiliarData.FIREWORKS.getName() + " (+5)" ); } else if ( this.item == FamiliarData.SUGAR_SHIELD ) { text.append( " " + FamiliarData.SUGAR_SHIELD.getName() + " (+10)" ); } else if ( this.item == FamiliarData.LEAD_NECKLACE ) { text.append( " " + FamiliarData.LEAD_NECKLACE.getName() + " (+3)" ); } else if ( this.item == FamiliarData.RAT_HEAD_BALLOON ) { text.append( " " + FamiliarData.RAT_HEAD_BALLOON.getName() + " (-3)" ); } else if ( this.item == FamiliarData.BATHYSPHERE ) { text.append( " " + FamiliarData.BATHYSPHERE.getName() + " (-20)" ); } else if ( this.item == FamiliarData.DAS_BOOT ) { text.append( " " + FamiliarData.DAS_BOOT.getName() + " (-10)" ); } else if ( this.item != null ) { text.append( " " + this.specItem.getName() + " (+" + this.specWeight + ")" ); } for ( int i = 0; i < 3; ++i ) { if ( this.acc[ i ] != null ) { text.append( " " + this.acc[ i ].getName() + " (+1)" ); } } text.append( "<br>" ); return text.toString(); } public String printAvailableEquipment() { StringBuilder text = new StringBuilder(); text.append( "Available equipment:" ); for ( int i = 0; i < this.whipCount; ++i ) { text.append( " bar whip (+2)" ); } if ( this.hat == FamiliarTrainingFrame.CRUMPLED_FEDORA ) { text.append( " crumpled felt fedora (+10)" ); } if ( this.hat == FamiliarTrainingFrame.PITH_HELMET ) { text.append( " plexiglass pith helmet (+5)" ); } if ( this.doppelganger ) { text.append( " flaming familiar doppelgänger (+0)" ); } else { if ( this.crumpledFedora ) { text.append( " crumpled felt fedora (+10)" ); } if ( this.pithHelmet ) { text.append( " plexiglass pith helmet (+5)" ); } if ( this.specItem != null ) { text.append( " " + this.specItem.getName() + " (+" + this.specWeight + ")" ); } if ( this.sugarShield ) { text.append( " sugar shield (+10)" ); } if ( this.pumpkinBucket ) { text.append( " plastic pumpkin bucket (+5)" ); } if ( this.flowerBouquet ) { text.append( " mayflower bouquet (+5)" ); } if ( this.boxFireworks ) { text.append( " little box of fireworks (+5)" ); } if ( this.leadNecklace ) { text.append( " lead necklace (+3)" ); } if ( this.ratHeadBalloon ) { text.append( " rat head balloon (-3)" ); } if ( this.bathysphere ) { text.append( " little bitty bathysphere (-20)" ); } if ( this.dasBoot ) { text.append( " das boot (-10)" ); } } for ( int i = 0; i < this.tpCount; ++i ) { text.append( " " + this.tp[ i ].getName() + " (+1)" ); } text.append( "<br>" ); return text.toString(); } private class GearSet implements Comparable<GearSet> { public AdventureResult weapon; public AdventureResult offhand; public AdventureResult hat; public AdventureResult item; public AdventureResult acc1; public AdventureResult acc2; public AdventureResult acc3; public GearSet() { this( FamiliarStatus.this.weapon, FamiliarStatus.this.offhand, FamiliarStatus.this.acc[ 0 ], FamiliarStatus.this.acc[ 1 ], FamiliarStatus.this.acc[ 2 ], FamiliarStatus.this.item, FamiliarStatus.this.hat ); } public GearSet( final AdventureResult weapon, final AdventureResult offhand, final AdventureResult acc1, final AdventureResult acc2, final AdventureResult acc3, final AdventureResult item, final AdventureResult hat ) { this.weapon = weapon; this.offhand = offhand; this.acc1 = acc1; this.acc2 = acc2; this.acc3 = acc3; this.item = item; this.hat = hat; } public int weight() { return FamiliarStatus.this.gearSetWeight( this.weapon, this.offhand, this.acc1, this.acc2, this.acc3, this.item, this.hat ); } public int compareTo( final GearSet o ) { // Keep in mind that all unequips are considered // better than equips, so unequips have a change // weight of 1. All others vary from that. GearSet that = (GearSet) o; int changes = 0; // Crumpled felt fedora is considered the // most ideal change, if it exists. if ( this.hat != that.hat ) { changes += that.item == null ? 1 : 10; } // Pumpkin bucket is also ideal, lead necklace // is less than ideal, standard item is ideal. if ( this.item != that.item ) { changes += that.item == null ? 1 : that.item.getItemId() == FamiliarData.LEAD_NECKLACE.getItemId() ? 10 : 5; } if ( this.weapon != that.weapon ) { changes += 15; } if ( this.offhand != that.offhand ) { changes += 10; } // Tiny plastic accessory changes are expensive // because they involve frequent changes. if ( this.acc1 != that.acc1 ) { changes += 20; } if ( this.acc2 != that.acc2 ) { changes += 20; } if ( this.acc3 != that.acc3 ) { changes += 20; } return changes; } @Override public String toString() { StringBuilder text = new StringBuilder(); text.append( "(" ); if ( this.weapon == null ) { text.append( "null" ); } else { text.append( this.weapon.getItemId() ); } text.append( ", " ); if ( this.offhand == null ) { text.append( "null" ); } else { text.append( this.offhand.getItemId() ); } text.append( ", " ); if ( this.hat == null ) { text.append( "null" ); } else { text.append( this.hat.getItemId() ); } text.append( ", " ); if ( this.item == null ) { text.append( "null" ); } else { text.append( this.item.getItemId() ); } text.append( ", " ); if ( this.acc1 == null ) { text.append( "null" ); } else { text.append( this.acc1.getItemId() ); } text.append( ", " ); if ( this.acc2 == null ) { text.append( "null" ); } else { text.append( this.acc2.getItemId() ); } text.append( ", " ); if ( this.acc3 == null ) { text.append( "null" ); } else { text.append( this.acc3.getItemId() ); } text.append( ")" ); return text.toString(); } } } /** * An internal class used to handle requests which resets a property for the duration of the current session. */ public class LocalSettingChanger extends JButton implements ActionListener { private final String title; private final String property; public LocalSettingChanger( final String title, final String property ) { super( title ); this.title = title; this.property = property; // Turn everything off and back on again // so that it's off to start. this.actionPerformed( null ); this.actionPerformed( null ); this.addActionListener( this ); } public void actionPerformed( ActionEvent e ) { boolean toggleValue = Preferences.getBoolean( this.property ); Preferences.setBoolean( this.property, toggleValue ); if ( toggleValue ) { this.setText( "Turn Off " + this.title ); } else { this.setText( "Turn On " + this.title ); } } } }