/** * 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.panel; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.List; import java.util.TreeMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ScrollPaneConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import net.java.dev.spellcast.utilities.JComponentUtilities; import net.java.dev.spellcast.utilities.LockableListModel; import net.java.dev.spellcast.utilities.LockableListModel.ListElementFilter; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.AreaCombatData; import net.sourceforge.kolmafia.KoLAdventure; 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.RequestThread; import net.sourceforge.kolmafia.listener.CharacterListener; import net.sourceforge.kolmafia.listener.CharacterListenerRegistry; import net.sourceforge.kolmafia.listener.Listener; import net.sourceforge.kolmafia.listener.NamedListenerRegistry; import net.sourceforge.kolmafia.objectpool.IntegerPool; import net.sourceforge.kolmafia.persistence.AdventureDatabase; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.UseSkillRequest; import net.sourceforge.kolmafia.session.EncounterManager.RegisteredEncounter; import net.sourceforge.kolmafia.session.EquipmentManager; import net.sourceforge.kolmafia.session.GoalManager; import net.sourceforge.kolmafia.swingui.button.InvocationButton; import net.sourceforge.kolmafia.swingui.button.ThreadedButton; import net.sourceforge.kolmafia.swingui.listener.ThreadedListener; import net.sourceforge.kolmafia.swingui.widget.AutoFilterComboBox; import net.sourceforge.kolmafia.swingui.widget.AutoFilterTextField; import net.sourceforge.kolmafia.swingui.widget.AutoHighlightSpinner; import net.sourceforge.kolmafia.swingui.widget.AutoHighlightTextField; import net.sourceforge.kolmafia.swingui.widget.GenericScrollPane; import net.sourceforge.kolmafia.swingui.widget.RequestPane; import net.sourceforge.kolmafia.textui.command.ConditionsCommand; import net.sourceforge.kolmafia.utilities.InputFieldUtilities; public class AdventureSelectPanel extends JPanel { private ThreadedButton begin; private final TreeMap zoneMap; private AdventureCountSpinner countField; private final LockableListModel matchingAdventures; private final JList locationSelect; private JComponent zoneSelect; private final LockableListModel locationConditions = new LockableListModel(); private final RedoFreeAdventuresCheckbox redoFreeAdventures; private final JCheckBox conditionsFieldActive; private final ConditionsComboBox conditionField = new ConditionsComboBox(); private SafetyField safetyField = null; public AdventureSelectPanel( final boolean enableAdventures ) { super( new BorderLayout( 10, 10 ) ); this.matchingAdventures = AdventureDatabase.getAsLockableListModel().getMirrorImage(); // West pane is a scroll pane which lists all of the available // locations -- to be included is a map on a separate tab. this.locationSelect = new JList( this.matchingAdventures ); this.locationSelect.setVisibleRowCount( 4 ); JPanel zonePanel = new JPanel( new BorderLayout( 5, 5 ) ); boolean useZoneComboBox = Preferences.getBoolean( "useZoneComboBox" ); if ( useZoneComboBox ) { this.zoneSelect = new FilterAdventureComboBox(); this.matchingAdventures.setFilter( (FilterAdventureComboBox) this.zoneSelect ); } else { this.zoneSelect = new AutoFilterTextField( this.locationSelect ); } this.zoneMap = new TreeMap(); Object[] zones = AdventureDatabase.PARENT_LIST.toArray(); Object currentZone; for ( int i = 0; i < zones.length; ++i ) { currentZone = AdventureDatabase.ZONE_DESCRIPTIONS.get( zones[ i ] ); this.zoneMap.put( currentZone, zones[ i ] ); if ( useZoneComboBox ) { ( (JComboBox) this.zoneSelect ).addItem( currentZone ); } } JComponentUtilities.setComponentSize( this.zoneSelect, 200, -1 ); zonePanel.add( this.zoneSelect, BorderLayout.CENTER ); this.redoFreeAdventures = new RedoFreeAdventuresCheckbox(); if ( enableAdventures ) { JPanel panel = new JPanel(); panel.add( this.redoFreeAdventures ); this.countField = new AdventureCountSpinner(); this.countField.setHorizontalAlignment( AutoHighlightTextField.RIGHT ); JComponentUtilities.setComponentSize( this.countField, 56, -1 ); panel.add( this.countField ); zonePanel.add( panel, BorderLayout.EAST ); } JPanel contentHolder = new JPanel( new BorderLayout( 5, 5 ) ); contentHolder.add( zonePanel, BorderLayout.NORTH ); contentHolder.add( new GenericScrollPane( this.locationSelect ), BorderLayout.CENTER ); this.locationSelect.addListSelectionListener( new AdventureSelectListener() ); JPanel conditionPanel = new JPanel( new BorderLayout( 5, 5 ) ); conditionPanel.add( this.conditionField, BorderLayout.CENTER ); this.conditionsFieldActive = new JCheckBox(); conditionPanel.add( this.conditionsFieldActive, BorderLayout.EAST ); boolean enableConditionsField = Preferences.getBoolean( "autoSetConditions" ); this.conditionsFieldActive.setSelected( enableConditionsField ); this.conditionsFieldActive.setToolTipText( "enable conditions field" ); this.conditionsFieldActive.addActionListener( new EnableObjectivesListener() ); this.conditionField.setEnabled( enableConditionsField ); contentHolder.add( conditionPanel, BorderLayout.SOUTH ); JPanel contentPanel = new JPanel( new BorderLayout( 10, 10 ) ); contentPanel.add( contentHolder, BorderLayout.CENTER ); this.setLayout( new CardLayout( 5, 5 ) ); this.add( "", contentPanel ); if ( enableAdventures ) { JPanel buttonHolder = new JPanel( new GridLayout( 3, 1, 5, 5 ) ); this.begin = new ThreadedButton( "begin", new ExecuteRunnable() ); this.begin.setToolTipText( "Start Adventuring" ); JComponentUtilities.addHotKey( this, KeyEvent.VK_ENTER, this.begin ); buttonHolder.add( this.begin ); buttonHolder.add( new InvocationButton( "stop now", RequestThread.class, "declareWorldPeace" ) ); buttonHolder.add( new StopButton() ); JPanel buttonPanel = new JPanel( new BorderLayout() ); buttonPanel.add( buttonHolder, BorderLayout.NORTH ); contentPanel.add( buttonPanel, BorderLayout.EAST ); this.zoneSelect.addKeyListener( this.begin ); this.countField.addKeyListener( this.begin ); } } public KoLAdventure getSelectedAdventure() { return (KoLAdventure) this.locationSelect.getSelectedValue(); } public void updateFromPreferences() { KoLAdventure location = AdventureDatabase.getAdventure( Preferences.getString( "lastAdventure" ) ); GoalManager.clearGoals(); this.updateSelectedAdventure( location ); } public void updateSafetyDetails() { if ( this.safetyField != null ) { this.safetyField.run(); } } public void updateSelectedAdventure( final KoLAdventure location ) { if ( location == null ) { return; } if ( GoalManager.hasGoals() ) { return; } if ( this.zoneSelect instanceof AutoFilterTextField ) { this.locationSelect.clearSelection(); ( (AutoFilterTextField) this.zoneSelect ).setText( location.getZone() ); } else { ( (JComboBox) this.zoneSelect ).setSelectedItem( location.getParentZoneDescription() ); } this.locationSelect.setSelectedValue( location, true ); } public synchronized void addSelectedLocationListener( final ListSelectionListener listener ) { this.locationSelect.addListSelectionListener( listener ); } private class FilterAdventureComboBox extends JComboBox implements ListElementFilter { private Object selectedZone; @Override public void setSelectedItem( final Object element ) { super.setSelectedItem( element ); this.selectedZone = element; AdventureSelectPanel.this.matchingAdventures.updateFilter( false ); } public boolean isVisible( final Object element ) { return ( (KoLAdventure) element ).getParentZoneDescription().equals( this.selectedZone ); } } private class ObjectivesPanel extends GenericPanel { public ObjectivesPanel() { super( new Dimension( 70, -1 ), new Dimension( 200, -1 ) ); } @Override public void actionConfirmed() { } @Override public void actionCancelled() { } @Override public void addStatusLabel() { } @Override public void setEnabled( final boolean isEnabled ) { if ( AdventureSelectPanel.this.begin != null ) { AdventureSelectPanel.this.begin.setEnabled( isEnabled ); } } } private class RedoFreeAdventuresCheckbox extends JCheckBox implements ActionListener, Listener { public RedoFreeAdventuresCheckbox() { super(); this.setToolTipText( "redo free adventures" ); this.setSelected( KoLmafia.redoSkippedAdventures ); this.addActionListener( this ); NamedListenerRegistry.registerNamedListener( "(adventuring)", this ); } public void actionPerformed( final ActionEvent e ) { KoLmafia.redoSkippedAdventures = this.isSelected(); } // called when (adventuring) fires public void update() { this.setEnabled( !KoLmafia.isAdventuring() ); } } private class EnableObjectivesListener extends ThreadedListener { @Override protected void execute() { boolean enabled = AdventureSelectPanel.this.conditionsFieldActive.isSelected(); Preferences.setBoolean( "autoSetConditions", enabled ); AdventureSelectPanel.this.conditionField.setEnabled( enabled && !KoLmafia.isAdventuring() ); } } private class AdventureSelectListener implements ListSelectionListener, ListDataListener { public AdventureSelectListener() { GoalManager.getGoals().addListDataListener( this ); AdventureSelectPanel.this.fillDefaultConditions(); } public void valueChanged( final ListSelectionEvent e ) { if ( KoLmafia.isAdventuring() ) { return; } KoLAdventure location = AdventureSelectPanel.this.getSelectedAdventure(); if ( location != null ) { KoLAdventure.setNextAdventure( location ); } AdventureSelectPanel.this.fillDefaultConditions(); } public void intervalAdded( final ListDataEvent e ) { AdventureSelectPanel.this.fillCurrentConditions(); } public void intervalRemoved( final ListDataEvent e ) { AdventureSelectPanel.this.fillCurrentConditions(); } public void contentsChanged( final ListDataEvent e ) { AdventureSelectPanel.this.fillCurrentConditions(); } } private class StopButton extends JButton implements ActionListener { public StopButton() { super( "stop after" ); this.addActionListener( this ); this.setToolTipText( "Stop after current adventure" ); } public void actionPerformed( final ActionEvent e ) { KoLmafia.abortAfter( "Manual stop requested." ); } } private class ExecuteRunnable implements Runnable { public void run() { KoLmafia.updateDisplay( "Validating adventure sequence..." ); KoLAdventure request = AdventureSelectPanel.this.getSelectedAdventure(); if ( request == null ) { KoLmafia.updateDisplay( MafiaState.ERROR, "No location selected." ); return; } // If there are conditions in the condition field, be // sure to process them. boolean conditionsActive = AdventureSelectPanel.this.conditionsFieldActive.isSelected(); String text = ( (String) AdventureSelectPanel.this.conditionField.getText() ); String conditionList = text == null ? "" : text.trim().toLowerCase(); List previousGoals = new ArrayList( GoalManager.getGoals() ); GoalManager.clearGoals(); // Retain any stat goal for ( int i = 0; i < previousGoals.size(); ++i ) { AdventureResult previousGoal = (AdventureResult) previousGoals.get( i ); if ( previousGoal.getName().equals( AdventureResult.SUBSTATS ) ) { GoalManager.addGoal( previousGoal ); break; } } boolean shouldAdventure = true; if ( conditionsActive && conditionList.length() > 0 && !conditionList.equals( "none" ) ) { shouldAdventure = this.handleConditions( conditionList, request ); } if ( !shouldAdventure ) { return; } int requestCount = Math.min( InputFieldUtilities.getValue( AdventureSelectPanel.this.countField, 1 ), KoLCharacter.getAdventuresLeft() ); AdventureSelectPanel.this.countField.setValue( requestCount ); boolean resetCount = requestCount == KoLCharacter.getAdventuresLeft(); KoLmafia.makeRequest( request, requestCount ); if ( resetCount ) { AdventureSelectPanel.this.countField.setValue( KoLCharacter.getAdventuresLeft() ); } } private boolean handleConditions( final String conditionList, final KoLAdventure request ) { if ( KoLmafia.isAdventuring() ) { return false; } GoalManager.clearGoals(); String[] splitConditions = conditionList.split( "\\s*,\\s*" ); // First, figure out whether or not you need to do a // disjunction on the conditions, which changes how // KoLmafia handles them. for ( int i = 0; i < splitConditions.length; ++i ) { if ( splitConditions[ i ] == null ) { continue; } if ( splitConditions[ i ].equals( "check" ) ) { // Postpone verification of conditions // until all other conditions added. } else if ( splitConditions[ i ].equals( "outfit" ) ) { // Determine where you're adventuring // and use that to determine which // components make up the outfit pulled // from that area. if ( !EquipmentManager.addOutfitConditions( request ) ) { return true; } } else if ( splitConditions[ i ].startsWith( "+" ) ) { if ( !ConditionsCommand.update( "add", splitConditions[ i ].substring( 1 ) ) ) { return false; } } else if ( !ConditionsCommand.update( "set", splitConditions[ i ] ) ) { return false; } } if ( !GoalManager.hasGoals() ) { KoLmafia.updateDisplay( "All conditions already satisfied." ); return false; } if ( InputFieldUtilities.getValue( AdventureSelectPanel.this.countField ) == 0 ) { AdventureSelectPanel.this.countField.setValue( KoLCharacter.getAdventuresLeft() ); } return true; } } private String getDefaultConditions() { KoLAdventure location = this.getSelectedAdventure(); AdventureDatabase.getDefaultConditionsList( location, this.locationConditions ); return (String) this.locationConditions.get( 0 ); } public void fillCurrentConditions() { String text = GoalManager.getGoalString(); if ( text.length() == 0 ) { text = this.getDefaultConditions(); } this.conditionField.setText( text ); } public void fillDefaultConditions() { this.conditionField.setText( this.getDefaultConditions() ); } public static JPanel getAdventureSummary( final String property ) { int selectedIndex = Preferences.getInteger( property ); CardLayout resultCards = new CardLayout(); JPanel resultPanel = new JPanel( resultCards ); JComboBox resultSelect = new JComboBox(); int cardCount = 0; resultSelect.addItem( "Session Results" ); resultPanel.add( new GenericScrollPane( (LockableListModel<AdventureResult>) KoLConstants.tally, 4 ), String.valueOf( cardCount++ ) ); resultSelect.addItem( "Location Details" ); resultPanel.add( new SafetyField(), String.valueOf( cardCount++ ) ); resultSelect.addItem( "Conditions Left" ); resultPanel.add( new GenericScrollPane( GoalManager.getGoals(), 4 ), String.valueOf( cardCount++ ) ); resultSelect.addItem( "Available Skills" ); resultPanel.add( new GenericScrollPane( (LockableListModel<UseSkillRequest>) KoLConstants.availableSkills, 4 ), String.valueOf( cardCount++ ) ); resultSelect.addItem( "Active Effects" ); resultPanel.add( new GenericScrollPane( (LockableListModel<AdventureResult>) KoLConstants.activeEffects, 4 ), String.valueOf( cardCount++ ) ); resultSelect.addItem( "Encounter Listing" ); resultPanel.add( new GenericScrollPane( (LockableListModel<RegisteredEncounter>) KoLConstants.encounterList, 4 ), String.valueOf( cardCount++ ) ); resultSelect.addItem( "Visited Locations" ); resultPanel.add( new GenericScrollPane( (LockableListModel<RegisteredEncounter>) KoLConstants.adventureList, 4 ), String.valueOf( cardCount++ ) ); resultSelect.addItem( "Daily Deeds" ); resultPanel.add( new GenericScrollPane( new DailyDeedsPanel() ), String.valueOf( cardCount++ ) ); resultSelect.addActionListener( new ResultSelectListener( resultCards, resultPanel, resultSelect, property ) ); if ( selectedIndex >= cardCount ) { selectedIndex = cardCount - 1; } resultSelect.setSelectedIndex( selectedIndex ); JPanel containerPanel = new JPanel( new BorderLayout() ); containerPanel.add( resultSelect, BorderLayout.NORTH ); containerPanel.add( resultPanel, BorderLayout.CENTER ); return containerPanel; } private static class ResultSelectListener implements ActionListener { private final String property; private final CardLayout resultCards; private final JPanel resultPanel; private final JComboBox resultSelect; public ResultSelectListener( final CardLayout resultCards, final JPanel resultPanel, final JComboBox resultSelect, final String property ) { this.resultCards = resultCards; this.resultPanel = resultPanel; this.resultSelect = resultSelect; this.property = property; } public void actionPerformed( final ActionEvent e ) { String index = String.valueOf( this.resultSelect.getSelectedIndex() ); this.resultCards.show( this.resultPanel, index ); Preferences.setString( this.property, index ); } } private static class SafetyField extends JPanel implements Runnable { private String savedText = " "; private final RequestPane safetyDisplay; public SafetyField() { super( new BorderLayout() ); this.safetyDisplay = new RequestPane(); JScrollPane safetyScroller = new JScrollPane( this.safetyDisplay, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); JComponentUtilities.setComponentSize( this.safetyDisplay, 100, 100 ); this.add( safetyScroller, BorderLayout.CENTER ); CharacterListenerRegistry.addCharacterListener( new CharacterListener( this ) ); this.setSafetyString(); } public void run() { this.setSafetyString(); } private synchronized void setSafetyString() { KoLAdventure request = KoLCharacter.getSelectedLocation(); if ( request == null ) { return; } AreaCombatData combat = request.getAreaSummary(); String text = combat == null ? " " : combat.toString( true ); // Avoid rendering and screen flicker if no change. // Compare with our own copy of what we set, since // getText() returns a modified version. if ( text.equals( this.savedText ) ) { return; } this.savedText = text; this.safetyDisplay.setText( text ); } } private class ConditionsComboBox extends AutoFilterComboBox { public ConditionsComboBox() { super( AdventureSelectPanel.this.locationConditions, true ); } } private class AdventureCountSpinner extends AutoHighlightSpinner implements ChangeListener { public AdventureCountSpinner() { super(); this.addChangeListener( this ); } public void stateChanged( final ChangeEvent e ) { int maximum = KoLCharacter.getAdventuresLeft(); if ( maximum == 0 ) { this.setValue( IntegerPool.get( 0 ) ); return; } int desired = InputFieldUtilities.getValue( this, maximum ); if ( desired == maximum + 1 ) { this.setValue( IntegerPool.get( 1 ) ); } else if ( desired <= 0 || desired > maximum ) { this.setValue( IntegerPool.get( maximum ) ); } } } }