/** * 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.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.BufferedReader; import java.io.File; import java.io.PrintStream; import java.util.Arrays; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.JTextArea; import javax.swing.JTree; import javax.swing.tree.DefaultTreeModel; import net.java.dev.spellcast.utilities.JComponentUtilities; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLGUIConstants; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.KoLmafiaCLI; import net.sourceforge.kolmafia.StaticEntity; import net.sourceforge.kolmafia.combat.CombatActionManager; import net.sourceforge.kolmafia.listener.Listener; import net.sourceforge.kolmafia.listener.PreferenceListenerRegistry; import net.sourceforge.kolmafia.persistence.SkillDatabase; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.swingui.button.RelayBrowserButton; import net.sourceforge.kolmafia.swingui.button.ThreadedButton; import net.sourceforge.kolmafia.swingui.widget.AutoFilterComboBox; import net.sourceforge.kolmafia.utilities.FileUtilities; import net.sourceforge.kolmafia.utilities.InputFieldUtilities; import net.sourceforge.kolmafia.utilities.LogStream; import net.sourceforge.kolmafia.webui.RelayLoader; public class CustomCombatPanel extends JPanel { private JComboBox actionSelect; protected JTree combatTree; protected JTextArea combatEditor; protected DefaultTreeModel combatModel; protected JPanel combatCardPanel; protected CardLayout combatCards; public JComboBox availableScripts; private static ImageIcon stealImg, stunImg; private static ImageIcon potionImg, olfactImg, puttyImg; private static ImageIcon antidoteImg, restoreImg, safeImg; static { CustomCombatPanel.stealImg = CustomCombatPanel.getImage( "knobsack.gif" ); CustomCombatPanel.stunImg = CustomCombatPanel.getImage( "entnoodles.gif" ); CustomCombatPanel.potionImg = CustomCombatPanel.getImage( "exclam.gif" ); CustomCombatPanel.olfactImg = CustomCombatPanel.getImage( "footprints.gif" ); CustomCombatPanel.puttyImg = CustomCombatPanel.getImage( "sputtycopy.gif" ); CustomCombatPanel.antidoteImg = CustomCombatPanel.getImage( "poisoncup.gif" ); CustomCombatPanel.restoreImg = CustomCombatPanel.getImage( "mp.gif" ); CustomCombatPanel.safeImg = CustomCombatPanel.getImage( "cast.gif" ); } public CustomCombatPanel() { this.combatTree = new JTree(); this.combatModel = (DefaultTreeModel) this.combatTree.getModel(); this.combatCards = new CardLayout(); this.combatCardPanel = new JPanel( this.combatCards ); this.availableScripts = new CombatComboBox(); this.combatCardPanel.add( "tree", new CustomCombatTreePanel() ); this.combatCardPanel.add( "editor", new CustomCombatEditorPanel() ); this.setLayout( new BorderLayout( 5, 5 ) ); this.add( new SpecialActionsPanel(), BorderLayout.NORTH ); this.add( this.combatCardPanel, BorderLayout.CENTER ); this.updateFromPreferences(); } public void updateFromPreferences() { if ( this.actionSelect != null ) { String battleAction = Preferences.getString( "battleAction" ); int battleIndex = KoLCharacter.getBattleSkillNames().indexOf( battleAction ); KoLCharacter.getBattleSkillNames().setSelectedIndex( battleIndex == -1 ? 0 : battleIndex ); } CombatActionManager.updateFromPreferences(); this.refreshCombatEditor(); } public void refreshCombatEditor() { try { String script = (String) this.availableScripts.getSelectedItem(); BufferedReader reader = FileUtilities.getReader( CombatActionManager.getStrategyLookupFile( script ) ); if ( reader == null ) { return; } StringBuffer buffer = new StringBuffer(); String line; while ( ( line = reader.readLine() ) != null ) { buffer.append( line ); buffer.append( '\n' ); } reader.close(); reader = null; this.combatEditor.setText( buffer.toString() ); } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); } this.refreshCombatTree(); } /** * Internal class used to handle everything related to displaying custom combat. */ public void refreshCombatTree() { this.combatModel.setRoot( CombatActionManager.getStrategyLookup() ); this.combatTree.setRootVisible( false ); for ( int i = 0; i < this.combatTree.getRowCount(); ++i ) { this.combatTree.expandRow( i ); } } private static ImageIcon getImage( final String filename ) { String path = "itemimages/" + filename; FileUtilities.downloadImage( KoLmafia.imageServerPath() + path ); return JComponentUtilities.getImage( path ); } private class SpecialActionsPanel extends GenericPanel implements Listener { private final JPanel special; private final JPopupMenu specialPopup; private final JLabel stealLabel, stunLabel; private final JLabel potionLabel, olfactLabel, puttyLabel; private final JLabel antidoteLabel, restoreLabel, safeLabel; private final JCheckBoxMenuItem stealItem, stunItem; private final JCheckBoxMenuItem potionItem, olfactItem, puttyItem; private final JCheckBoxMenuItem restoreItem, safePickpocket; private final JMenu poisonItem; private boolean updating = true; public SpecialActionsPanel() { super( new Dimension( 70, -1 ), new Dimension( 200, -1 ) ); CustomCombatPanel.this.actionSelect = new AutoFilterComboBox( KoLCharacter.getBattleSkillNames(), false ); CustomCombatPanel.this.actionSelect.addActionListener( new BattleActionListener() ); JPanel special = new JPanel( new FlowLayout( FlowLayout.LEADING, 0, 0 ) ); this.special = special; special.setBackground( Color.WHITE ); special.setBorder( BorderFactory.createLoweredBevelBorder() ); MouseListener listener = new SpecialPopListener(); special.addMouseListener( listener ); String stunSkill = KoLCharacter.getClassStun(); if ( stunSkill.equals( "Shell Up" ) ) { if ( KoLCharacter.getBlessingType() != KoLCharacter.STORM_BLESSING ) { stunSkill = Preferences.getBoolean( "considerShadowNoodles" ) ? "Shadow Noodles" : "none"; } } int stunId = SkillDatabase.getSkillId( stunSkill ); if ( stunId > 0 ) { CustomCombatPanel.stunImg = CustomCombatPanel.getImage( SkillDatabase.getSkillImage( stunId ) ); } this.stealLabel = this.label( special, listener, CustomCombatPanel.stealImg, "Pickpocketing will be tried (if appropriate) with non-CCS actions." ); if ( stunSkill.equals( "none" ) ) { this.stunLabel = this.label( special, listener, CustomCombatPanel.stunImg, "No stun available. Set considerShadowNoodles = true to use Shadow Noodles." ); } else { this.stunLabel = this.label( special, listener, CustomCombatPanel.stunImg, stunSkill + " will be cast before non-CCS actions." ); } this.olfactLabel = this.label( special, listener, CustomCombatPanel.olfactImg, null ); this.puttyLabel = this.label( special, listener, CustomCombatPanel.puttyImg, null ); this.potionLabel = this.label( special, listener, CustomCombatPanel.potionImg, "<html>Dungeons of Doom potions will be identified by using them in combat.<br>Requires 'special' action if a CCS is used.</html>" ); this.antidoteLabel = this.label( special, listener, CustomCombatPanel.antidoteImg, null ); this.restoreLabel = this.label( special, listener, CustomCombatPanel.restoreImg, "MP restores will be used in combat if needed." ); this.safeLabel = this.label( special, listener, CustomCombatPanel.safeImg, "Pickpocketing will be skipped when there are no useful results or it is too dangerous." ); this.specialPopup = new JPopupMenu( "Special Actions" ); this.stealItem = this.checkbox( this.specialPopup, listener, "Pickpocket before simple actions" ); if ( stunSkill.equals( "none" ) ) { this.stunItem = this.checkbox( this.specialPopup, listener, "No stun available. Set considerShadowNoodles = true to use Shadow Noodles." ); } else { this.stunItem = this.checkbox( this.specialPopup, listener, "Cast " + stunSkill + " before simple actions" ); } this.specialPopup.addSeparator(); this.olfactItem = this.checkbox( this.specialPopup, listener, "One-time automatic Olfaction..." ); this.puttyItem = this.checkbox( this.specialPopup, listener, "One-time automatic Spooky Putty/Rain-Doh box/4-d camera..." ); this.potionItem = this.checkbox( this.specialPopup, listener, "Identify bang potions" ); this.specialPopup.addSeparator(); this.poisonItem = new JMenu( "Minimum poison level for antidote use" ); ButtonGroup group = new ButtonGroup(); this.poison( this.poisonItem, group, listener, "No automatic use" ); this.poison( this.poisonItem, group, listener, "Toad In The Hole (-\u00BDHP/round)" ); this.poison( this.poisonItem, group, listener, "Majorly Poisoned (-90%, -11)" ); this.poison( this.poisonItem, group, listener, "Really Quite Poisoned (-70%, -9)" ); this.poison( this.poisonItem, group, listener, "Somewhat Poisoned (-50%, -7)" ); this.poison( this.poisonItem, group, listener, "A Little Bit Poisoned (-30%, -5)" ); this.poison( this.poisonItem, group, listener, "Hardly Poisoned at All (-10%, -3)" ); this.specialPopup.add( this.poisonItem ); this.restoreItem = this.checkbox( this.specialPopup, listener, "Restore MP in combat" ); this.safePickpocket = this.checkbox( this.specialPopup, listener, "Skip pickpocketing when no useful results or too dangerous" ); VerifiableElement[] elements = new VerifiableElement[ 2 ]; elements[ 0 ] = new VerifiableElement( "Action: ", CustomCombatPanel.this.actionSelect ); elements[ 1 ] = new VerifiableElement( "Special: ", special ); this.setContent( elements ); ( (BorderLayout) this.container.getLayout() ).setHgap( 0 ); ( (BorderLayout) this.container.getLayout() ).setVgap( 0 ); PreferenceListenerRegistry.registerPreferenceListener( "autoSteal", this ); PreferenceListenerRegistry.registerPreferenceListener( "autoEntangle", this ); PreferenceListenerRegistry.registerPreferenceListener( "autoOlfact", this ); PreferenceListenerRegistry.registerPreferenceListener( "autoPutty", this ); PreferenceListenerRegistry.registerPreferenceListener( "autoPotionID", this ); PreferenceListenerRegistry.registerPreferenceListener( "autoAntidote", this ); PreferenceListenerRegistry.registerPreferenceListener( "autoManaRestore", this ); PreferenceListenerRegistry.registerPreferenceListener( "safePickpocket", this ); PreferenceListenerRegistry.registerPreferenceListener( "(skill)", this ); this.update(); } public void update() { this.updating = true; CustomCombatPanel.this.actionSelect.setSelectedItem( Preferences.getString( "battleAction" ) ); if ( KoLCharacter.hasSkill( KoLCharacter.getClassStun() ) ) { this.stunItem.setEnabled( true ); } else { this.stunItem.setEnabled( false ); Preferences.setBoolean( "autoEntangle", false ); } String text; boolean pref; pref = Preferences.getBoolean( "autoSteal" ); this.stealLabel.setVisible( pref ); this.stealItem.setSelected( pref ); pref = Preferences.getBoolean( "autoEntangle" ); this.stunLabel.setVisible( pref ); this.stunItem.setSelected( pref ); text = Preferences.getString( "autoOlfact" ); pref = text.length() > 0; this.olfactLabel.setVisible( pref ); this.olfactItem.setSelected( pref ); this.olfactLabel.setToolTipText( "<html>Automatic Olfaction or odor extractor use: " + text + "<br>Requires 'special' action if a CCS is used.</html>" ); text = Preferences.getString( "autoPutty" ); pref = text.length() > 0; this.puttyLabel.setVisible( pref ); this.puttyItem.setSelected( pref ); this.puttyLabel.setToolTipText( "<html>Automatic Spooky Putty sheet, Rain-Doh black box, 4-d camera, crappy camera or portable photocopier use: " + text + "<br>Requires 'special' action if a CCS is used.</html>" ); pref = Preferences.getBoolean( "autoPotionID" ); this.potionLabel.setVisible( pref ); this.potionItem.setSelected( pref ); int antidote = Preferences.getInteger( "autoAntidote" ); this.antidoteLabel.setVisible( antidote > 0 ); if ( antidote >= 0 && antidote < this.poisonItem.getMenuComponentCount() ) { JRadioButtonMenuItem option = (JRadioButtonMenuItem) this.poisonItem.getMenuComponent( antidote ); option.setSelected( true ); this.antidoteLabel.setToolTipText( "Anti-anti-antidote will be used in combat if you get " + option.getText() + " or worse." ); } pref = Preferences.getBoolean( "autoManaRestore" ); this.restoreLabel.setVisible( pref ); this.restoreItem.setSelected( pref ); pref = Preferences.getBoolean( "safePickpocket" ); this.safeLabel.setVisible( pref ); this.safePickpocket.setSelected( pref ); this.updating = false; } private JLabel label( final JPanel special, final MouseListener listener, final ImageIcon img, final String toolTip ) { JLabel rv = new JLabel( img ); rv.setToolTipText( toolTip ); rv.addMouseListener( listener ); special.add( rv ); return rv; } private JCheckBoxMenuItem checkbox( final JPopupMenu menu, final Object listener, final String text ) { JCheckBoxMenuItem rv = new JCheckBoxMenuItem( text ); menu.add( rv ); rv.addItemListener( (ItemListener) listener ); return rv; } private void poison( final JMenu menu, final ButtonGroup group, final Object listener, final String text ) { JRadioButtonMenuItem rb = new JRadioButtonMenuItem( text ); menu.add( rb ); group.add( rb ); rb.addItemListener( (ItemListener) listener ); } @Override public void actionConfirmed() { } @Override public void actionCancelled() { } @Override public void addStatusLabel() { } private class BattleActionListener implements ActionListener { public void actionPerformed( ActionEvent e ) { // Don't set preferences from widgets when we // are in the middle of loading widgets from // preferences. if ( SpecialActionsPanel.this.updating ) { return; } String value = (String) CustomCombatPanel.this.actionSelect.getSelectedItem(); if ( value != null ) { Preferences.setString( "battleAction", value ); } } } private class SpecialPopListener extends MouseAdapter implements ItemListener { @Override public void mousePressed( final MouseEvent e ) { SpecialActionsPanel.this.specialPopup.show( SpecialActionsPanel.this.special, 0, 32 ); } public void itemStateChanged( final ItemEvent e ) { // Don't set preferences from widgets when we // are in the middle of loading widgets from // preferences. if ( SpecialActionsPanel.this.updating ) { return; } boolean state = e.getStateChange() == ItemEvent.SELECTED; JMenuItem source = (JMenuItem) e.getItemSelectable(); if ( source == SpecialActionsPanel.this.stealItem ) { Preferences.setBoolean( "autoSteal", state ); } else if ( source == SpecialActionsPanel.this.stunItem ) { Preferences.setBoolean( "autoEntangle", state ); } else if ( source == SpecialActionsPanel.this.olfactItem ) { if ( state == !Preferences.getString( "autoOlfact" ).equals( "" ) ) { // pref already set externally, don't prompt return; } String option = !state ? null : InputFieldUtilities.input( "Use Transcendent Olfaction or odor extractor when? (item, \"goals\", or \"monster\" plus name; add \"abort\" to stop adventuring)", "goals" ); KoLmafiaCLI.DEFAULT_SHELL.executeCommand( "olfact", option == null ? "none" : option ); } else if ( source == SpecialActionsPanel.this.puttyItem ) { if ( state == !Preferences.getString( "autoPutty" ).equals( "" ) ) { // pref already set externally, don't prompt return; } String option = !state ? null : InputFieldUtilities.input( "Use Spooky Putty sheet, Rain-Doh black box, 4-d camera, crappy camera or portable photocopier when? (item, \"goals\", or \"monster\" plus name; add \"abort\" to stop adventuring)", "goals abort" ); KoLmafiaCLI.DEFAULT_SHELL.executeCommand( "putty", option == null ? "none" : option ); } else if ( source == SpecialActionsPanel.this.potionItem ) { Preferences.setBoolean( "autoPotionID", state ); } else if ( source == SpecialActionsPanel.this.restoreItem ) { Preferences.setBoolean( "autoManaRestore", state ); } else if ( source == SpecialActionsPanel.this.safePickpocket ) { Preferences.setBoolean( "safePickpocket" , state ); } else if ( source instanceof JRadioButtonMenuItem ) { Preferences.setInteger( "autoAntidote", Arrays.asList( SpecialActionsPanel.this.poisonItem.getMenuComponents() ).indexOf( source ) ); } } } } public class CombatComboBox extends JComboBox implements ActionListener, Listener { public CombatComboBox() { super( CombatActionManager.getAvailableLookups() ); this.addActionListener( this ); PreferenceListenerRegistry.registerPreferenceListener( "customCombatScript", this ); } public void update() { CustomCombatPanel.this.combatCards.show( CustomCombatPanel.this.combatCardPanel, "tree" ); this.setSelectedItem( Preferences.getString( "customCombatScript" ) ); } @Override public void actionPerformed( final ActionEvent e ) { String script = (String) this.getSelectedItem(); if ( script != null ) { CombatActionManager.loadStrategyLookup( script ); CustomCombatPanel.this.refreshCombatTree(); } } } private class CustomCombatEditorPanel extends ScrollablePanel { public CustomCombatEditorPanel() { super( "Editor", "save", "cancel", new JTextArea() ); CustomCombatPanel.this.combatEditor = (JTextArea) this.scrollComponent; CustomCombatPanel.this.combatEditor.setFont( KoLGUIConstants.DEFAULT_FONT ); CustomCombatPanel.this.refreshCombatTree(); this.eastPanel.add( new RelayBrowserButton( "help", "http://kolmafia.sourceforge.net/combat.html" ), BorderLayout.SOUTH ); } @Override public void actionConfirmed() { String script = (String) CustomCombatPanel.this.availableScripts.getSelectedItem(); String saveText = CustomCombatPanel.this.combatEditor.getText(); File location = CombatActionManager.getStrategyLookupFile( script ); PrintStream writer = LogStream.openStream( location, true ); writer.print( saveText ); writer.close(); writer = null; KoLCharacter.battleSkillNames.setSelectedItem( "custom combat script" ); Preferences.setString( "battleAction", "custom combat script" ); // After storing all the data on disk, go ahead // and reload the data inside of the tree. CombatActionManager.loadStrategyLookup( script ); CombatActionManager.saveStrategyLookup( script ); CustomCombatPanel.this.refreshCombatTree(); CustomCombatPanel.this.combatCards.show( CustomCombatPanel.this.combatCardPanel, "tree" ); } @Override public void actionCancelled() { CustomCombatPanel.this.refreshCombatEditor(); CustomCombatPanel.this.combatCards.show( CustomCombatPanel.this.combatCardPanel, "tree" ); } @Override public void setEnabled( final boolean isEnabled ) { } } public class CustomCombatTreePanel extends ScrollablePanel { public CustomCombatTreePanel() { super( "", "edit", "help", CustomCombatPanel.this.combatTree ); CustomCombatPanel.this.combatTree.setVisibleRowCount( 8 ); this.centerPanel.add( CustomCombatPanel.this.availableScripts, BorderLayout.NORTH ); JPanel extraButtons = new JPanel( new GridLayout( 2, 1, 5, 5 ) ); extraButtons.add( new ThreadedButton( "new", new NewScriptRunnable() ) ); extraButtons.add( new ThreadedButton( "copy", new CopyScriptRunnable() ) ); JPanel buttonHolder = new JPanel( new BorderLayout() ); buttonHolder.add( extraButtons, BorderLayout.NORTH ); this.eastPanel.add( buttonHolder, BorderLayout.SOUTH ); } @Override public void actionConfirmed() { CustomCombatPanel.this.refreshCombatEditor(); CustomCombatPanel.this.combatCards.show( CustomCombatPanel.this.combatCardPanel, "editor" ); } @Override public void actionCancelled() { RelayLoader.openSystemBrowser( "http://kolmafia.sourceforge.net/combat.html" ); } @Override public void setEnabled( final boolean isEnabled ) { } public class NewScriptRunnable implements Runnable { public void run() { String name = InputFieldUtilities.input( "Give your combat script a name!" ); if ( name == null || name.equals( "" ) || name.equals( "default" ) ) { return; } CombatActionManager.loadStrategyLookup( name ); CustomCombatPanel.this.refreshCombatTree(); } } public class CopyScriptRunnable implements Runnable { public void run() { String name = InputFieldUtilities.input( "Make a copy of current script called:" ); if ( name == null || name.equals( "" ) || name.equals( "default" ) ) { return; } CombatActionManager.copyStrategyLookup( name ); CombatActionManager.loadStrategyLookup( name ); CustomCombatPanel.this.refreshCombatTree(); } } } }