/** * 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.moods; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import net.sourceforge.kolmafia.AdventureResult; 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.RequestLogger; import net.sourceforge.kolmafia.RequestThread; import net.sourceforge.kolmafia.maximizer.Evaluator; import net.sourceforge.kolmafia.persistence.EffectDatabase; import net.sourceforge.kolmafia.persistence.ItemFinder; import net.sourceforge.kolmafia.persistence.SkillDatabase; import net.sourceforge.kolmafia.request.UneffectRequest; import net.sourceforge.kolmafia.request.UseItemRequest; import net.sourceforge.kolmafia.request.UseSkillRequest; import net.sourceforge.kolmafia.utilities.CharacterEntities; import net.sourceforge.kolmafia.utilities.StringUtilities; public class MoodTrigger implements Comparable<MoodTrigger> { private static Map<String, Set<String>> knownSources = new HashMap<String, Set<String>>(); private int skillId = -1; private final AdventureResult effect; private boolean isThiefTrigger = false; private final StringBuffer stringForm; private String action; private final String type; private final String name; private int count; private AdventureResult item; private UseSkillRequest skill; private MoodTrigger( final String type, final AdventureResult effect, final String action ) { this.type = type; this.effect = effect; this.name = effect == null ? null : effect.getName(); if ( ( action.startsWith( "use " ) || action.startsWith( "cast " ) ) && action.indexOf( ";" ) == -1 ) { // Determine the command, the count amount, // and the parameter's unambiguous form. int spaceIndex = action.indexOf( " " ); String parameters = StringUtilities.getDisplayName( action.substring( spaceIndex + 1 ).trim() ); if ( action.startsWith( "use" ) ) { boolean star = parameters.charAt( 0 ) == '*'; if ( star ) { spaceIndex = parameters.indexOf( " " ); parameters = parameters.substring( spaceIndex ).trim(); } AdventureResult item = MoodTrigger.getUsableItem( parameters ); if ( item != null ) { if ( star ) { this.action = "use * " + item.getDataName(); } else { this.item = item; this.count = item.getCount(); this.action = "use " + this.count + " " + item.getDataName(); } } } else { this.count = 1; if ( Character.isDigit( parameters.charAt( 0 ) ) ) { spaceIndex = parameters.indexOf( " " ); this.count = StringUtilities.parseInt( parameters.substring( 0, spaceIndex ) ); parameters = parameters.substring( spaceIndex ).trim(); } if ( !SkillDatabase.contains( parameters ) ) { parameters = SkillDatabase.getUsableSkillName( parameters ); } this.skill = UseSkillRequest.getInstance( parameters ); if ( this.skill != null ) { this.action = "cast " + this.count + " " + this.skill.getSkillName(); } } } if ( this.action == null ) { this.count = 1; this.action = action; } if ( type.equals( "lose_effect" ) && effect != null ) { Set<String> existingActions = (Set<String>) MoodTrigger.knownSources.get( effect.getName() ); if ( existingActions == null ) { existingActions = new LinkedHashSet<String>(); MoodTrigger.knownSources.put( effect.getName(), existingActions ); } existingActions.add( this.action ); String skillName = UneffectRequest.effectToSkill( effect.getName() ); if ( SkillDatabase.contains( skillName ) ) { this.skillId = SkillDatabase.getSkillId( skillName ); this.isThiefTrigger = SkillDatabase.isAccordionThiefSong( skillId ); } } this.stringForm = new StringBuffer(); this.updateStringForm(); } private static AdventureResult getUsableItem( final String parameters ) { AdventureResult item = ItemFinder.getFirstMatchingItem( parameters, false ); if ( item != null ) { String name = item.bangPotionAlias(); if ( !name.equals( item.getDataName() ) ) { item = AdventureResult.tallyItem( name, item.getCount(), false ); } } return item; } public static String getKnownSources( String name ) { Set existingActions = (Set) MoodTrigger.knownSources.get( name ); if ( existingActions == null ) { return ""; } StringBuilder buffer = new StringBuilder(); Iterator actionIterator = existingActions.iterator(); while ( actionIterator.hasNext() ) { if ( buffer.length() > 0 ) { buffer.append( "|" ); } String action = (String) actionIterator.next(); buffer.append( action ); } return buffer.toString(); } public static void clearKnownSources() { MoodTrigger.knownSources.clear(); } public boolean matches() { return KoLConstants.activeEffects.contains( this.effect ); } public boolean matches( AdventureResult effect ) { if ( this.effect == null ) { return effect == null; } if ( effect == null ) { return false; } return this.effect.equals( effect ); } public boolean isSkill() { return this.skillId != -1; } public AdventureResult getEffect() { return this.effect; } public String getType() { return this.type; } public String getName() { return this.name; } public String getAction() { return this.action; } @Override public String toString() { return this.stringForm.toString(); } public String toSetting() { if ( this.effect == null ) { return this.type + " => " + this.action; } String canonical = StringUtilities.getCanonicalName( this.name ); if ( this.item != null ) { return this.type + " " + canonical + " => use " + this.count + " " + StringUtilities.getCanonicalName( this.item.getDataName() ); } if ( this.skill != null ) { return this.type + " " + canonical + " => cast " + this.count + " " + StringUtilities.getCanonicalName( this.skill.getSkillName() ); } return this.type + " " + canonical + " => " + this.action; } @Override public boolean equals( final Object o ) { if ( o == null || !( o instanceof MoodTrigger ) ) { return false; } MoodTrigger mt = (MoodTrigger) o; if ( !this.type.equals( mt.type ) ) { return false; } if ( this.name == null ) { // This is an unconditional trigger. Compare actions. return this.action.equalsIgnoreCase( mt.action ); } return this.name.equals( mt.name ); } @Override public int hashCode() { int hash = 0; hash += this.type != null ? this.type.hashCode() : 0; hash += 31 * (this.name != null ? this.name.hashCode() : 0); return hash; } public void execute( final int multiplicity ) { if ( !this.shouldExecute( multiplicity ) ) { return; } if ( this.item != null ) { AdventureResult item = this.item; int itemId = item.getItemId(); if ( itemId == -1 ) { item = item.resolveBangPotion(); itemId = item.getItemId(); } if ( itemId == -1 ) { RequestLogger.printLine( "You have not yet identified the " + item.toString() ); return; } item = item.getInstance( Math.max( this.count, this.count * multiplicity ) ); RequestThread.postRequest( UseItemRequest.getInstance( item ) ); return; } if ( this.skill != null ) { int casts = Math.max( this.count, this.count * multiplicity ); this.skill.setBuffCount( casts ); this.skill.setTarget( KoLCharacter.getUserName() ); RequestThread.postRequest( this.skill ); if ( !UseSkillRequest.lastUpdate.equals( "" ) ) { String name = this.skill.getSkillName(); KoLmafia.updateDisplay( MafiaState.ERROR, "Mood failed to cast " + casts + " " + name + ": " + UseSkillRequest.lastUpdate ); RequestThread.declareWorldPeace(); } return; } KoLmafiaCLI.DEFAULT_SHELL.executeLine( this.action ); } public boolean shouldExecute( final int multiplicity ) { if ( KoLmafia.refusesContinue() ) { return false; } // Don't cast if you are restricted by your current class/skills if ( this.effect != null && Evaluator.checkEffectConstraints( this.effect.getEffectId() ) ) { return false; } // Don't cast it if you can't if ( this.skill != null && !KoLCharacter.hasSkill( this.skill.getSkillName() ) ) { return false; } if ( this.type.equals( "unconditional" ) || this.effect == null ) { return true; } if ( this.type.equals( "lose_effect" ) && multiplicity > 0 ) { return true; } if ( this.type.equals( "gain_effect" ) ) { return KoLConstants.activeEffects.contains( this.effect ); } if ( MoodManager.unstackableAction( this.action ) ) { return !KoLConstants.activeEffects.contains( this.effect ); } int activeCount = this.effect.getCount( KoLConstants.activeEffects ); if ( multiplicity == -1 ) { return activeCount <= 1; } return activeCount <= 5; } public boolean isThiefTrigger() { return this.isThiefTrigger; } public int compareTo( final MoodTrigger o ) { if ( o == null || !( o instanceof MoodTrigger ) ) { return -1; } MoodTrigger mt = (MoodTrigger) o; String othertype = mt.type; String othername = mt.name; String otherTriggerAction = mt.action; // Order: unconditional, gain_effect, lose_effect if ( this.type.equals( "unconditional" ) ) { if ( othertype.equals( "unconditional" ) ) { return this.action.compareToIgnoreCase( otherTriggerAction ); } return -1; } if ( this.type.equals( "gain_effect" ) ) { if ( othertype.equals( "unconditional" ) ) { return 1; } if ( othertype.equals( "gain_effect" ) ) { return this.name.compareTo( othername ); } return -1; } if ( this.type.equals( "lose_effect" ) ) { if ( othertype.equals( "lose_effect" ) ) { return this.name.compareTo( othername ); } return 1; } return 0; } public void updateStringForm() { this.stringForm.setLength( 0 ); if ( this.type.equals( "gain_effect" ) ) { this.stringForm.append( "When I get" ); } else if ( this.type.equals( "lose_effect" ) ) { this.stringForm.append( "When I run low on" ); } else { this.stringForm.append( "Always" ); } if ( this.name != null ) { this.stringForm.append( " " ); this.stringForm.append( this.name ); } this.stringForm.append( ", " ); this.stringForm.append( this.action ); } public static final MoodTrigger constructNode( final String line ) { String[] pieces = CharacterEntities.unescape( line ).split( " => " ); if ( pieces.length != 2 ) { return null; } String type = null; if ( pieces[ 0 ].startsWith( "gain_effect" ) ) { type = "gain_effect"; } else if ( pieces[ 0 ].startsWith( "lose_effect" ) ) { type = "lose_effect"; } else if ( pieces[ 0 ].startsWith( "unconditional" ) ) { type = "unconditional"; } if ( type == null ) { return null; } AdventureResult effect = null; if ( !type.equals( "unconditional" ) ) { int spaceIndex = pieces[ 0 ].indexOf( " " ); String name = pieces[ 0 ].substring( spaceIndex ).trim(); effect = EffectDatabase.getFirstMatchingEffect( name ); if ( effect == null ) { return null; } } return new MoodTrigger( type, effect, pieces[ 1 ].trim() ); } }