/** * 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.session; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.StringTokenizer; import net.sourceforge.kolmafia.KoLAdventure; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.objectpool.AdventurePool; import net.sourceforge.kolmafia.persistence.AdventureDatabase; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.CreateItemRequest; import net.sourceforge.kolmafia.request.Crimbo09Request; import net.sourceforge.kolmafia.request.GenericRequest; import net.sourceforge.kolmafia.request.RelayRequest; import net.sourceforge.kolmafia.request.UseItemRequest; import net.sourceforge.kolmafia.request.UseSkillRequest; import net.sourceforge.kolmafia.utilities.StringUtilities; public class TurnCounter implements Comparable<TurnCounter> { private static final ArrayList<TurnCounter> relayCounters = new ArrayList<TurnCounter>(); private static final HashSet<String> ALL_LOCATIONS = new HashSet<String>(); private int value; private final String image; private String label; private String URL; private String parsedLabel; private HashSet<String> exemptions; private int lastWarned; private boolean wander = false; public TurnCounter( final int value, final String label, final String image ) { this.value = KoLCharacter.getCurrentRun() + value; this.label = label.replaceAll( ":", "" ); this.image = image.replaceAll( ":", "" ); this.lastWarned = -1; this.parsedLabel = this.label; int pos = this.parsedLabel.lastIndexOf( " " ); while ( pos != -1 ) { String word = this.parsedLabel.substring( pos + 1 ).trim(); if ( word.equals( "loc=*" ) ) { this.exemptions = TurnCounter.ALL_LOCATIONS; } else if ( word.startsWith( "loc=" ) ) { if ( this.exemptions == null ) { this.exemptions = new HashSet<String>(); } this.exemptions.add( word.substring( 4 ) ); } else if ( word.startsWith( "type=" ) ) { if ( word.substring( 5 ).equals( "wander" ) ) { this.wander = true; } } else if ( word.contains( ".php" ) ) { this.URL = word; } else break; this.parsedLabel = this.parsedLabel.substring( 0, pos ).trim(); pos = this.parsedLabel.lastIndexOf( " " ); } if ( this.parsedLabel.length() == 0 ) { this.parsedLabel = "Manual"; } } public boolean isExempt( final String adventureId ) { if ( this.exemptions == TurnCounter.ALL_LOCATIONS || (this.exemptions != null && this.exemptions.contains( adventureId )) ) { return true; } return false; } public String imageURL() { if ( this.URL != null ) return this.URL; if ( this.exemptions != null && this.exemptions.size() == 1 ) { // Exactly one exempt location String loc = this.exemptions.iterator().next(); return "adventure.php?snarfblat=" + loc; } return null; } public String getLabel() { return this.parsedLabel; } public String getImage() { return this.image; } public int getTurnsRemaining() { int remain = this.value - KoLCharacter.getCurrentRun(); if ( remain < 0 && this.wander ) { this.value = KoLCharacter.getCurrentRun(); remain = 0; } return remain; } public static int turnsRemaining( final String label ) { synchronized ( TurnCounter.relayCounters ) { for ( TurnCounter current : TurnCounter.relayCounters ) { if ( current.parsedLabel.equals( label ) ) { return current.value - KoLCharacter.getCurrentRun(); } } } return -1; } @Override public boolean equals( final Object o ) { if ( o == null || !( o instanceof TurnCounter ) ) { return false; } return this.label.equals( ( (TurnCounter) o ).label ) && this.value == ( (TurnCounter) o ).value; } @Override public int hashCode() { int hash = 0; hash += this.value; hash += 31 * (this.label != null ? this.label.hashCode() : 0); return hash; } public int compareTo( final TurnCounter o ) { if ( o == null || !( o instanceof TurnCounter ) ) { return -1; } return this.value - ( (TurnCounter) o ).value; } public static final void clearCounters() { synchronized ( TurnCounter.relayCounters ) { TurnCounter.relayCounters.clear(); TurnCounter.saveCounters(); } } public static final void loadCounters() { synchronized ( TurnCounter.relayCounters ) { TurnCounter.relayCounters.clear(); String counters = Preferences.getString( "relayCounters" ); if ( counters.length() == 0 ) { return; } StringTokenizer tokens = new StringTokenizer( counters, ":" ); while ( tokens.hasMoreTokens() ) { int turns = StringUtilities.parseInt( tokens.nextToken() ) - KoLCharacter.getCurrentRun(); if ( !tokens.hasMoreTokens() ) break; String name = tokens.nextToken(); if ( !tokens.hasMoreTokens() ) break; String image = tokens.nextToken(); startCountingInternal( turns, name, image ); } } } public static final void saveCounters() { synchronized ( TurnCounter.relayCounters ) { StringBuilder counters = new StringBuilder(); for ( TurnCounter current : TurnCounter.relayCounters ) { if ( counters.length() > 0 ) { counters.append( ":" ); } counters.append( current.value ); counters.append( ":" ); counters.append( current.label ); counters.append( ":" ); counters.append( current.image ); } Preferences.setString( "relayCounters", counters.toString() ); } } public static final TurnCounter getExpiredCounter( GenericRequest request, boolean informational ) { String URL = request.getURLString(); KoLAdventure adventure = AdventureDatabase.getAdventureByURL( URL ); String adventureId; int turnsUsed; if ( adventure != null ) { adventureId = adventure.getAdventureId(); turnsUsed = adventure.getRequest().getAdventuresUsed(); } else if ( AdventureDatabase.getUnknownName( URL ) != null ) { adventureId = ""; turnsUsed = 1; } else { adventureId = ""; turnsUsed = TurnCounter.getTurnsUsed( request ); } if ( turnsUsed == 0 ) { return null; } int thisTurn = KoLCharacter.getCurrentRun(); int currentTurns = thisTurn + turnsUsed - 1; synchronized ( TurnCounter.relayCounters ) { Iterator<TurnCounter> it = TurnCounter.relayCounters.iterator(); while ( it.hasNext() ) { TurnCounter current = it.next(); if ( current.value > currentTurns || current.lastWarned == thisTurn || current.isExempt( adventureId ) != informational ) { continue; } if ( informational && current.value > thisTurn ) { // Defer until later, there's no point in reporting an // informational counter prior to actual expiration. continue; } if ( current.value < thisTurn ) { if ( current.wander ) { // This might not actually be necessary continue; } it.remove(); } current.lastWarned = thisTurn; return current; } } return null; } public static final String getUnexpiredCounters() { int currentTurns = KoLCharacter.getCurrentRun(); StringBuilder counters = new StringBuilder(); synchronized ( TurnCounter.relayCounters ) { for ( TurnCounter current : TurnCounter.relayCounters ) { if ( current.value < currentTurns ) { // Can't remove the counter - a counterScript // may still be waiting for it to be delivered. continue; } if ( counters.length() > 0 ) { counters.append( KoLConstants.LINE_BREAK ); } counters.append( current.parsedLabel ); counters.append( " (" ); counters.append( current.value - currentTurns ); counters.append( ")" ); } } return counters.toString(); } public static final void startCounting( final int value, final String label, final String image ) { synchronized ( TurnCounter.relayCounters ) { TurnCounter.startCountingInternal( value, label, image ); TurnCounter.saveCounters(); } } private static final void startCountingInternal( final int value, final String label, final String image ) { // We don't synchronize here because caller has already done so. if ( value >= 0 ) { TurnCounter counter = new TurnCounter( value, label, image ); if ( !TurnCounter.relayCounters.contains( counter ) ) { TurnCounter.relayCounters.add( counter ); } } } public static final void stopCounting( final String label ) { synchronized ( TurnCounter.relayCounters ) { Iterator<TurnCounter> it = TurnCounter.relayCounters.iterator(); while ( it.hasNext() ) { TurnCounter current = it.next(); if ( current.parsedLabel.equals( label ) ) { it.remove(); } } TurnCounter.saveCounters(); } } public static final boolean isCounting( final String label, final int value ) { int searchValue = KoLCharacter.getCurrentRun() + value; synchronized ( TurnCounter.relayCounters ) { for ( TurnCounter current : TurnCounter.relayCounters ) { if ( current.parsedLabel.equals( label ) && current.value == searchValue ) { return true; } } } return false; } public static final boolean isCounting( final String label, final int start, final int stop ) { int begin = KoLCharacter.getCurrentRun() + start; int end = KoLCharacter.getCurrentRun() + stop; synchronized ( TurnCounter.relayCounters ) { for ( TurnCounter current : TurnCounter.relayCounters ) { if ( current.parsedLabel.equals( label ) && current.value >= begin && current.value <= end ) { return true; } } } return false; } public static final boolean isCounting( final String label ) { synchronized ( TurnCounter.relayCounters ) { for ( TurnCounter current : TurnCounter.relayCounters ) { if ( current.parsedLabel.equals( label ) && current.value >= KoLCharacter.getCurrentRun() ) { return true; } } } return false; } public static final TurnCounter[] getCounters() { TurnCounter[] counters; synchronized ( TurnCounter.relayCounters ) { counters = TurnCounter.relayCounters.toArray( new TurnCounter[ TurnCounter.relayCounters.size() ] ); } Arrays.sort( counters ); return counters; } public static final String getCounters( String label, int minTurns, int maxTurns ) { label = label.toLowerCase(); boolean checkExempt = label.length() == 0; minTurns += KoLCharacter.getCurrentRun(); maxTurns += KoLCharacter.getCurrentRun(); StringBuilder buf = new StringBuilder(); synchronized ( TurnCounter.relayCounters ) { for ( TurnCounter current : TurnCounter.relayCounters ) { if ( current.value < minTurns || current.value > maxTurns ) { continue; } if ( checkExempt && current.isExempt( "" ) ) { continue; } if ( !current.parsedLabel.toLowerCase().contains( label ) ) { continue; } if ( buf.length() != 0 ) { buf.append( "\t" ); } buf.append( current.parsedLabel ); } } return buf.toString(); } public static final void startCountingTemporary( int value, String label, String image ) { String temp = Preferences.getString( "_tempRelayCounters" ); temp = temp + value + ":" + label + ":" + image + "|"; Preferences.setString( "_tempRelayCounters", temp ); } public static final void handleTemporaryCounters( final String type, final String encounter ) { String temp = Preferences.getString( "_tempRelayCounters" ); if ( temp.equals( "" ) ) { return; } int snarfblat = KoLAdventure.lastAdventureId(); if ( snarfblat == 0 || snarfblat == AdventurePool.THE_SHORE || snarfblat == AdventurePool.TRAINING_SNOWMAN || snarfblat == AdventurePool.DIRE_WARREN || ( snarfblat >= AdventurePool.GINGERBREAD_CIVIC && snarfblat <= AdventurePool.GINGERBREAD_SEWERS ) ) { return; } if ( type.equals( "Combat" ) ) { if ( EncounterManager.isNoWanderMonster( encounter ) ) { return; } } String[] counters = temp.split( "\\|" ); for ( String counter : counters ) { if ( counter.equals( "" ) ) continue; String[] values = counter.split( ":" ); TurnCounter.startCounting( StringUtilities.parseInt( values[0] ), values[1], values[2] ); } Preferences.setString( "_tempRelayCounters", "" ); } private static final int getTurnsUsed( GenericRequest request ) { if ( !( request instanceof RelayRequest ) ) { return request.getAdventuresUsed(); } String urlString = request.getURLString(); if ( urlString.startsWith( "adventure.php" ) ) { // Assume unknown adventure locations take 1 turn each // This is likely not true under the Sea, for example, // but it's as good a guess as any we can make. return 1; } if ( urlString.startsWith( "inv_use.php" ) || urlString.startsWith( "inv_eat.php" ) ) { return UseItemRequest.getAdventuresUsed( urlString ); } if ( urlString.startsWith( "runskillz.php" ) ) { return UseSkillRequest.getAdventuresUsed( urlString ); } if ( urlString.startsWith( "craft.php" ) || urlString.startsWith( "guild.php" ) ) { return CreateItemRequest.getAdventuresUsed( request ); } if ( urlString.startsWith( "place.php?whichplace=chateau" ) && urlString.contains( "action=chateau_painting" ) ) { return Preferences.getBoolean( "_chateauMonsterFought" ) ? 0 : 1; } if ( urlString.startsWith( "crimbo09.php" ) ) { return Crimbo09Request.getTurnsUsed( request ); } return 0; } public static final void addWarning( final String label ) { synchronized ( TurnCounter.relayCounters ) { Iterator<TurnCounter> it = TurnCounter.relayCounters.iterator(); while ( it.hasNext() ) { TurnCounter counter = it.next(); if ( counter.parsedLabel.equals( label ) && counter.exemptions == TurnCounter.ALL_LOCATIONS ) { counter.exemptions = null; counter.label = counter.label.replace( " loc=*", "" ); } } TurnCounter.saveCounters(); } } public static final void removeWarning( final String label ) { synchronized ( TurnCounter.relayCounters ) { Iterator<TurnCounter> it = TurnCounter.relayCounters.iterator(); while ( it.hasNext() ) { TurnCounter counter = it.next(); if ( counter.parsedLabel.equals( label ) && counter.exemptions == null ) { counter.exemptions = TurnCounter.ALL_LOCATIONS; counter.label += " loc=*"; } } TurnCounter.saveCounters(); } } public static final void deleteByHash( final int hash ) { synchronized ( TurnCounter.relayCounters ) { Iterator<TurnCounter> it = TurnCounter.relayCounters.iterator(); while ( it.hasNext() ) { if ( System.identityHashCode( it.next() ) == hash ) { it.remove(); } } TurnCounter.saveCounters(); } } public static final int count() { synchronized ( TurnCounter.relayCounters ) { return TurnCounter.relayCounters.size(); } } }