/**
* 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.persistence;
import java.io.BufferedReader;
import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import net.sourceforge.kolmafia.AdventureResult;
import net.sourceforge.kolmafia.KoLConstants;
import net.sourceforge.kolmafia.KoLConstants.MafiaState;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.Modifiers;
import net.sourceforge.kolmafia.RequestLogger;
import net.sourceforge.kolmafia.StaticEntity;
import net.sourceforge.kolmafia.objectpool.EffectPool;
import net.sourceforge.kolmafia.objectpool.IntegerPool;
import net.sourceforge.kolmafia.request.UneffectRequest;
import net.sourceforge.kolmafia.textui.command.UseItemCommand;
import net.sourceforge.kolmafia.textui.command.UseSkillCommand;
import net.sourceforge.kolmafia.utilities.FileUtilities;
import net.sourceforge.kolmafia.utilities.LogStream;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public class EffectDatabase
{
private static String [] canonicalNames = new String[0];
private static final Map<Integer, String> nameById = new TreeMap<Integer, String>();
private static final Map<String, int[]> effectIdSetByName = new TreeMap<String, int[]>();
public static final HashMap<Integer, String> defaultActions = new HashMap<Integer, String>();
private static final Map<Integer, String> imageById = new HashMap<Integer, String>();
private static final Map<Integer, String> descriptionById = new TreeMap<Integer, String>();
private static final Map<String, Integer> effectIdByDescription = new HashMap<String, Integer>();
public static boolean newEffects = false;
static
{
EffectDatabase.reset();
}
private static void reset()
{
EffectDatabase.newEffects = false;
BufferedReader reader =
FileUtilities.getVersionedReader( "statuseffects.txt", KoLConstants.STATUSEFFECTS_VERSION );
String[] data;
while ( ( data = FileUtilities.readData( reader ) ) != null )
{
if ( data.length >= 3 )
{
Integer effectId = Integer.valueOf( data[ 0 ] );
if ( effectId.intValue() < 0 )
{
continue;
}
String name = data[ 1 ];
String image = data[ 2 ];
String descId = data.length > 3 ? data[ 3 ] : null;
String defaultAction = data.length > 4 ? data[ 4 ] : null;
EffectDatabase.addToDatabase( effectId, name, image, descId, defaultAction );
}
}
try
{
reader.close();
}
catch ( Exception e )
{
// This should not happen. Therefore, print
// a stack trace for debug purposes.
StaticEntity.printStackTrace( e );
}
EffectDatabase.canonicalNames = new String[ EffectDatabase.effectIdSetByName.size() ];
EffectDatabase.effectIdSetByName.keySet().toArray( EffectDatabase.canonicalNames );
}
private static void addIdToName( String canonicalName, int itemId )
{
int[] idSet = EffectDatabase.effectIdSetByName.get( canonicalName );
int[] newSet;
if ( idSet == null )
{
newSet = new int[1];
}
// *** This assumes the array is sorted
else if ( Arrays.binarySearch( idSet, itemId ) >= 0 )
{
return;
}
else
{
newSet = Arrays.copyOf( idSet, idSet.length + 1 );
}
newSet[ newSet.length - 1 ] = itemId;
// *** Make it so
Arrays.sort( newSet );
EffectDatabase.effectIdSetByName.put( canonicalName, newSet );
}
private static final void addToDatabase( final Integer effectId, final String name, final String image,
final String descriptionId, final String defaultAction )
{
String canonicalName = StringUtilities.getCanonicalName( name );
EffectDatabase.nameById.put( effectId, name );
EffectDatabase.addIdToName( canonicalName, effectId );
EffectDatabase.imageById.put( effectId, image );
if ( descriptionId != null )
{
EffectDatabase.descriptionById.put( effectId, descriptionId );
EffectDatabase.effectIdByDescription.put( descriptionId, effectId );
}
if ( defaultAction != null )
{
EffectDatabase.defaultActions.put( effectId, defaultAction );
}
}
public static final String getDefaultAction( final int effectId )
{
if ( effectId == -1 )
{
return null;
}
String rv = StringUtilities.getDisplayName( EffectDatabase.defaultActions.get( IntegerPool.get( effectId ) ) );
if ( rv == null )
{
return null;
}
if ( rv.startsWith( "#" ) )
{ // Callers of this API expect an actual command, not a note.
return null;
}
for ( String it: rv.split( "\\|" ) )
{
String[] split = it.split( " ", 2 );
boolean works = true; // assume the command works if we don't check it here
if ( it.startsWith( "use" ) || it.startsWith( "eat" ) || it.startsWith( "drink" ) || it.startsWith( "chew" ) )
{
works = UseItemCommand.use( split[ 0 ], split[ 1 ], true );
}
else if ( it.startsWith( "cast" ) )
{
works = UseSkillCommand.cast( split[1], true );
}
if ( works )
{
return it;
}
}
// if nothing worked, fall through and dispatch the command so that an appropriate failure can be printed
return rv.split( "\\|" )[0];
}
public static final Iterator<String> getAllActions( final int effectId )
{
if ( effectId == -1 )
{
return Collections.<String>emptyList().iterator();
}
String actions = StringUtilities.getDisplayName( EffectDatabase.defaultActions.get( IntegerPool.get( effectId ) ) );
if ( actions == null )
{
return Collections.<String>emptyList().iterator();
}
ArrayList<String> rv = new ArrayList<String>();
String[] pieces = actions.split( "\\|" );
for ( int i = 0; i < pieces.length; ++i )
{
String action = pieces[ i ];
String[] either = action.split( " ", 3 );
if ( either.length == 3 && either[ 1 ].equals( "either" ) )
{ // Split commands like "use either X, Y" into "use X", "use Y"
String cmd = either[ 0 ];
either = either[ 2 ].split( "\\s*,\\s*" );
for ( int j = 0; j < either.length; ++j )
{
rv.add( cmd + " " + either[ j ] );
}
}
else
{
rv.add( action );
}
}
return rv.iterator();
}
public static final String getActionNote( final int effectId )
{
if ( effectId == -1 )
{
return null;
}
String rv = StringUtilities.getDisplayName( EffectDatabase.defaultActions.get( IntegerPool.get( effectId ) ) );
if ( rv != null && rv.startsWith( "#" ) )
{
return rv.substring( 1 ).trim();
}
return null;
}
/**
* Returns the name for an effect, given its Id.
*
* @param effectId The Id of the effect to lookup
* @return The name of the corresponding effect
*/
public static final String getEffectName( final int effectId )
{
return effectId == -1 ?
null:
EffectDatabase.nameById.get( IntegerPool.get( effectId ) );
}
public static final String getEffectName( final String descriptionId )
{
Integer effectId = EffectDatabase.effectIdByDescription.get( descriptionId );
return effectId == null ? null : EffectDatabase.getEffectName( effectId.intValue() );
}
public static final String getEffectDisplayName( final String effectName )
{
if ( effectName.startsWith( "[" ) )
{
int ind = effectName.indexOf( "]" );
if ( ind > 0 )
{
int effectId = StringUtilities.parseInt( effectName.substring( 1, ind ) );
return getEffectName( effectId );
}
}
return effectName;
}
public static final int getEffectIdFromDescription( final String descriptionId )
{
Integer effectId = EffectDatabase.effectIdByDescription.get( descriptionId );
return effectId == null ? -1 : effectId.intValue();
}
public static final String getDescriptionId( final int effectId )
{
return EffectDatabase.descriptionById.get( IntegerPool.get( effectId ) );
}
static final Set<Integer> descriptionIdKeySet()
{
return EffectDatabase.descriptionById.keySet();
}
/**
* Returns the Id number for an effect, given its name.
*
* @param effectName The name of the effect to lookup
* @return The Id number of the corresponding effect
*/
public static final int getEffectId( final String effectName )
{
return EffectDatabase.getEffectId( effectName, false );
}
public static final int getEffectId( final String effectName, final boolean exact )
{
if ( effectName == null )
{
return -1;
}
// If name starts with [nnnn] then that is explicitly the effect id
if ( effectName.startsWith( "[" ) )
{
int index = effectName.indexOf( "]" );
if ( index > 0 )
{
String idString = effectName.substring( 1, index );
int effectId = -1;
try
{
effectId = StringUtilities.parseInt( idString );
}
catch (NumberFormatException e)
{
}
return effectId;
}
}
int[] ids = EffectDatabase.effectIdSetByName.get( StringUtilities.getCanonicalName( effectName ) );
if ( ids != null )
{
if ( exact && ids.length > 1)
{
return -1;
}
return ids[ ids.length - 1 ];
}
if ( exact )
{
return -1;
}
List<String> names = EffectDatabase.getMatchingNames( effectName );
if ( names.size() == 1 )
{
return EffectDatabase.getEffectId( names.get( 0 ), true );
}
return -1;
}
private static final int[] NO_EFFECT_IDS = new int[0];
public static final int[] getEffectIds( final String effectName, final boolean exact )
{
if ( effectName == null )
{
return NO_EFFECT_IDS;
}
// If name starts with [nnnn] then that is explicitly the effect id
if ( effectName.startsWith( "[" ) )
{
int index = effectName.indexOf( "]" );
if ( index > 0 )
{
String idString = effectName.substring( 1, index );
int effectId = -1;
try
{
effectId = StringUtilities.parseInt( idString );
}
catch (NumberFormatException e)
{
}
int[] ids = new int[1];
ids[0] = effectId;
return ids;
}
}
int[] ids = EffectDatabase.effectIdSetByName.get( StringUtilities.getCanonicalName( effectName ) );
if ( ids != null )
{
if ( exact && ids.length > 1)
{
return NO_EFFECT_IDS;
}
return ids;
}
if ( exact )
{
return NO_EFFECT_IDS;
}
List<String> names = EffectDatabase.getMatchingNames( effectName );
if ( names.size() != 1 )
{
return NO_EFFECT_IDS;
}
ids = effectIdSetByName.get( StringUtilities.getCanonicalName( names.get( 0 ) ) );
return ids != null ? ids : NO_EFFECT_IDS;
}
/**
* Returns the Id number for an effect, given its name.
*
* @param effectId The Id of the effect to lookup
* @return The name of the corresponding effect
*/
static final String getImageName( final int effectId )
{
String imageName = effectId == -1 ? null : EffectDatabase.imageById.get( IntegerPool.get( effectId ) );
return imageName == null ? "" : imageName;
}
public static final String getImage( final int effectId )
{
String imageName = EffectDatabase.getImageName( effectId );
return imageName.equals( "" ) ?
"/images/debug.gif" :
KoLmafia.imageServerPath() + "itemimages/" + imageName;
}
/**
* Returns the set of status effects keyed by Id
*
* @return The set of status effects keyed by Id
*/
public static final Set entrySet()
{
return EffectDatabase.nameById.entrySet();
}
public static final Collection<String> values()
{
return EffectDatabase.nameById.values();
}
/**
* Returns whether or not an effect with a given name exists in the database
*
* @param effectName The name of the effect to lookup
* @return <code>true</code> if the effect is in the database
*/
public static final boolean contains( final String effectName )
{
return EffectDatabase.contains( EffectDatabase.getEffectId( effectName ) );
}
public static final boolean containsExactly( final String effectName )
{
return EffectDatabase.contains( EffectDatabase.getEffectId( effectName, true ) );
}
public static final boolean contains( final int effectId )
{
if ( effectId == -1 )
{
return false;
}
return EffectDatabase.nameById.get( IntegerPool.get( effectId ) ) != null;
}
/**
* Returns a list of all items which contain the given substring. This is useful for people who are doing lookups on
* items.
*/
public static final List<String> getMatchingNames( final String substring )
{
// If name starts with [nnnn] then that is explicitly the effect id
if ( substring.startsWith( "[" ) )
{
int index = substring.indexOf( "]" );
if ( index > 0 )
{
String idString = substring.substring( 1, index );
try
{
int effectId = StringUtilities.parseInt( idString );
// It parsed to a number so is valid
List<String> list = new ArrayList<String>();
list.add( substring );
return list;
}
catch (NumberFormatException e)
{
}
}
}
return StringUtilities.getMatchingNames( EffectDatabase.canonicalNames, substring );
}
public static final int learnEffectId( String name, String descId )
{
return EffectDatabase.registerEffect( name, descId, null );
}
static final int registerEffect( String name, String descId, String defaultAction )
{
// Load the description text for this effect
String text = DebugDatabase.readEffectDescriptionText( descId );
if ( text == null )
{
return -1;
}
if ( name == null )
{
name = DebugDatabase.parseName( text );
}
int effectId = DebugDatabase.parseEffectId( text );
if ( effectId == -1 )
{
return -1;
}
String image = DebugDatabase.parseImage( text );
// Detach name, descid, and image from being substrings
name = new String( name );
descId = new String( descId );
image = new String( image );
String canonicalName = StringUtilities.getCanonicalName( name );
Integer id = IntegerPool.get( effectId );
EffectDatabase.nameById.put( id, name );
EffectDatabase.addIdToName( canonicalName, id );
EffectDatabase.imageById.put( id, image );
EffectDatabase.descriptionById.put( id, descId );
EffectDatabase.effectIdByDescription.put( descId, id );
if ( defaultAction != null )
{
EffectDatabase.defaultActions.put( id, defaultAction );
}
String printMe;
printMe = "--------------------";
RequestLogger.printLine( printMe );
RequestLogger.updateSessionLog( printMe );
printMe = EffectDatabase.effectString( effectId, name, image, descId, defaultAction );
RequestLogger.printLine( printMe );
RequestLogger.updateSessionLog( printMe );
// Let modifiers database do what it wishes with this effect
Modifiers.registerEffect( name, text );
// Done generating data
printMe = "--------------------";
RequestLogger.printLine( printMe );
RequestLogger.updateSessionLog( printMe );
EffectDatabase.newEffects = true;
return effectId;
}
public static final void writeEffects( final File output )
{
RequestLogger.printLine( "Writing data override: " + output );
PrintStream writer = LogStream.openStream( output, true );
writer.println( KoLConstants.STATUSEFFECTS_VERSION );
int lastInteger = 1;
for ( Entry<Integer, String> entry : EffectDatabase.nameById.entrySet() )
{
Integer nextInteger = entry.getKey();
int effectId = nextInteger.intValue();
// Skip pseudo effects
if ( effectId < 1 )
{
continue;
}
for ( int i = lastInteger; i < nextInteger.intValue(); ++i )
{
writer.println( i );
}
lastInteger = effectId + 1;
String name = entry.getValue();
String image = EffectDatabase.imageById.get( nextInteger );
String descId = EffectDatabase.descriptionById.get( nextInteger );
String defaultAction = EffectDatabase.defaultActions.get( nextInteger );
EffectDatabase.writeEffect( writer, effectId, name, image, descId, defaultAction );
}
writer.close();
}
private static void writeEffect( final PrintStream writer, final int effectId, final String name,
final String image, final String descId, final String defaultAction )
{
writer.println( EffectDatabase.effectString( effectId, name, image, descId, defaultAction ) );
}
private static String effectString( final int effectId, final String name,
String image, String descId, String defaultAction )
{
// The effect file can have 3, 4, or 5 fields. "image" must be
// present, even if we don't have the actual file name.
if ( image == null )
{
image = "";
}
if ( defaultAction != null )
{
// It's unlikely we'll know the default action without
// knowing the effect ID, but handle it just in case
if ( descId == null )
{
descId = "";
}
return effectId + "\t" + name + "\t" + image + "\t" + descId + "\t" + defaultAction;
}
if ( descId != null )
{
return effectId + "\t" + name + "\t" + image + "\t" + descId;
}
return effectId + "\t" + name + "\t" + image;
}
/**
* Utility method which determines the first effect which matches the given parameter string. Note that the string
* may also specify an effect duration before the string.
*/
public static final AdventureResult getFirstMatchingEffect( final String parameters )
{
return EffectDatabase.getFirstMatchingEffect( parameters, true );
}
public static final AdventureResult getFirstMatchingEffect( final String parameters, final boolean errorIfNone )
{
String effectName = null;
int duration = 0;
// First, allow for the person to type without specifying
// the amount, if the amount is 1.
List<String> matchingNames = EffectDatabase.getMatchingNames( parameters );
if ( matchingNames.size() != 0 )
{
effectName = (String) matchingNames.get( 0 );
duration = 1;
}
else
{
String durationString = "";
int spaceIndex = parameters.indexOf( " " );
if ( spaceIndex != -1 )
{
durationString = parameters.substring( 0, spaceIndex );
}
if ( durationString.equals( "*" ) )
{
duration = 0;
}
else
{
if ( StringUtilities.isNumeric( durationString ) )
{
duration = StringUtilities.parseInt( durationString );
}
else
{
durationString = "";
duration = 1;
}
}
String effectNameString = parameters.substring( durationString.length() ).trim();
matchingNames = EffectDatabase.getMatchingNames( effectNameString );
if ( matchingNames.size() == 0 )
{
if ( errorIfNone )
{
KoLmafia.updateDisplay(
MafiaState.ERROR,
"[" + effectNameString + "] does not match anything in the status effect database." );
}
return null;
}
effectName = (String) matchingNames.get( 0 );
}
if ( effectName == null )
{
KoLmafia.updateDisplay(
MafiaState.ERROR, "[" + parameters + "] does not match anything in the status effect database." );
return null;
}
int effectId = EffectDatabase.getEffectId( effectName );
return EffectPool.get( effectId, duration );
}
public static final int[] POISON_ID = {
0,
EffectPool.TOAD_IN_THE_HOLE,
EffectPool.MAJORLY_POISONED,
EffectPool.REALLY_QUITE_POISONED,
EffectPool.SOMEWHAT_POISONED,
EffectPool.A_LITTLE_BIT_POISONED,
EffectPool.HARDLY_POISONED
};
public static int getPoisonLevel( String text )
{
text = text.toLowerCase();
if ( text.contains( "toad in the hole" ) )
{
return 1;
}
if ( !text.contains( "poisoned" ) )
{
return Integer.MAX_VALUE;
}
if ( text.contains( "majorly poisoned" ) )
{
return 2;
}
if ( text.contains( "really quite poisoned" ) )
{
return 3;
}
if ( text.contains( "somewhat poisoned" ) )
{
return 4;
}
if ( text.contains( "a little bit poisoned" ) )
{
return 5;
}
if ( text.contains( "hardly poisoned at all" ) )
{
return 6;
}
return Integer.MAX_VALUE;
}
}