/**
* 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.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import net.sourceforge.kolmafia.AreaCombatData;
import net.sourceforge.kolmafia.KoLAdventure;
import net.sourceforge.kolmafia.KoLCharacter;
import net.sourceforge.kolmafia.KoLConstants;
import net.sourceforge.kolmafia.MonsterData;
import net.sourceforge.kolmafia.RequestLogger;
import net.sourceforge.kolmafia.combat.CombatActionManager;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.request.FightRequest;
import net.sourceforge.kolmafia.utilities.RollingLinkedList;
/*
* Instead of packing and unpacking a giant treemap into user preference files, this is a way of persisting a variable across sessions.
* Uses the Java Serializable interface.
*/
public class AdventureQueueDatabase
implements Serializable
{
private static final long serialVersionUID = -180241952508113931L;
private static TreeMap<String, RollingLinkedList<String>> COMBAT_QUEUE = new TreeMap<String, RollingLinkedList<String>>();
private static TreeMap<String, RollingLinkedList<String>> NONCOMBAT_QUEUE = new TreeMap<String, RollingLinkedList<String>>();
// debugging tool
public static void showQueue()
{
Set<String> keys = COMBAT_QUEUE.keySet();
for ( String key : keys )
{
RollingLinkedList<String> zoneQueue = COMBAT_QUEUE.get( key );
StringBuilder builder = new StringBuilder( key + ": " );
for ( String it : zoneQueue )
{
if ( it != null )
{
builder.append( it );
builder.append( " | " );
}
}
RequestLogger.printLine( builder.toString() );
}
RequestLogger.printLine( );
RequestLogger.printLine( "Noncombats:" );
keys = NONCOMBAT_QUEUE.keySet();
for ( String key : keys )
{
RollingLinkedList<String> zoneQueue = NONCOMBAT_QUEUE.get( key );
StringBuilder builder = new StringBuilder( key + ": " );
for ( String it : zoneQueue )
{
if ( it != null )
{
builder.append( it );
builder.append( " | " );
}
}
RequestLogger.printLine( builder.toString() );
}
}
public static void resetQueue()
{
resetQueue( true );
}
private static void resetQueue( boolean serializeAfterwards )
{
AdventureQueueDatabase.COMBAT_QUEUE = new TreeMap<String, RollingLinkedList<String>>();
AdventureQueueDatabase.NONCOMBAT_QUEUE = new TreeMap<String, RollingLinkedList<String>>();
List<KoLAdventure> list = AdventureDatabase.getAsLockableListModel();
for ( KoLAdventure adv : list )
{
AdventureQueueDatabase.COMBAT_QUEUE.put( adv.getAdventureName(), new RollingLinkedList<String>( 5 ) );
AdventureQueueDatabase.NONCOMBAT_QUEUE.put( adv.getAdventureName(), new RollingLinkedList<String>( 5 ) );
}
if ( serializeAfterwards )
{
AdventureQueueDatabase.serialize();
}
}
private static boolean checkZones()
{
// See if any zones aren't in the TreeMap. Add them if so.
List<KoLAdventure> list = AdventureDatabase.getAsLockableListModel();
Set<String> keys = COMBAT_QUEUE.keySet();
boolean keyAdded = false;
for ( KoLAdventure adv : list )
{
if ( !keys.contains( adv.getAdventureName() ) )
{
AdventureQueueDatabase.COMBAT_QUEUE.put( adv.getAdventureName(), new RollingLinkedList( 5 ) );
keyAdded = true;
}
}
keys = NONCOMBAT_QUEUE.keySet();
for ( KoLAdventure adv : list )
{
if ( !keys.contains( adv.getAdventureName() ) )
{
AdventureQueueDatabase.NONCOMBAT_QUEUE.put( adv.getAdventureName(), new RollingLinkedList( 5 ) );
keyAdded = true;
}
}
return keyAdded;
}
public static void enqueue( KoLAdventure adv, String monster )
{
if ( adv == null || monster == null )
return;
AdventureQueueDatabase.enqueue( adv.getAdventureName(), monster );
}
public static void enqueueNoncombat( KoLAdventure adv, String name )
{
if ( adv == null || name == null )
return;
AdventureQueueDatabase.enqueueNoncombat( adv.getAdventureName(), name );
}
public static void enqueue( String adventureName, String monster )
{
if ( adventureName == null || monster == null )
return;
RollingLinkedList zoneQueue = COMBAT_QUEUE.get( adventureName );
if ( zoneQueue == null )
return;
MonsterData mon = MonsterDatabase.findMonster( CombatActionManager.encounterKey( monster ), true );
if ( mon == null )
{
// We /should/ have canonicalized the string by now (and matching correctly failed), but just in case see if stripping off "the" helps.
// Other articles definitely should have been handled by now.
if ( monster.startsWith( "the " ) || monster.startsWith( "The " ) )
{
mon = MonsterDatabase.findMonster( CombatActionManager.encounterKey( monster.substring( 4 ) ), true );
}
if ( mon == null )
return;
}
zoneQueue.add( mon.getName() );
}
public static void enqueueNoncombat( String noncombatAdventureName, String name )
{
if ( noncombatAdventureName == null )
return;
RollingLinkedList zoneQueue = NONCOMBAT_QUEUE.get( noncombatAdventureName );
if ( zoneQueue == null )
return;
zoneQueue.add( name );
}
public static RollingLinkedList getZoneQueue( KoLAdventure adv )
{
return AdventureQueueDatabase.getZoneQueue( adv.getAdventureName() );
}
public static RollingLinkedList getZoneQueue( String adv )
{
return COMBAT_QUEUE.get( adv );
}
public static RollingLinkedList getZoneNoncombatQueue( KoLAdventure adv )
{
return AdventureQueueDatabase.getZoneNoncombatQueue( adv.getAdventureName() );
}
public static RollingLinkedList getZoneNoncombatQueue( String adv )
{
return NONCOMBAT_QUEUE.get( adv );
}
public static void serialize()
{
File file = new File( KoLConstants.DATA_LOCATION, KoLCharacter.baseUserName() + "_" + "queue.ser" );
try
{
FileOutputStream fileOut = new FileOutputStream( file );
ObjectOutputStream out = new ObjectOutputStream( fileOut );
// make a collection with combat queue first
List<TreeMap<String, RollingLinkedList<String>>> queues = new ArrayList<TreeMap<String, RollingLinkedList<String>>>();
queues.add( COMBAT_QUEUE );
queues.add( NONCOMBAT_QUEUE );
out.writeObject( queues );
out.close();
}
catch ( IOException e )
{
e.printStackTrace();
}
}
/*
* Attempts to load saved adventure queue settings from <username>_queue.ser
*/
@SuppressWarnings( "unchecked" )
public static void deserialize()
{
File file = new File( KoLConstants.DATA_LOCATION, KoLCharacter.baseUserName() + "_" + "queue.ser" );
if ( !file.exists() )
{
AdventureQueueDatabase.resetQueue( false );
return;
}
try
{
FileInputStream fileIn = new FileInputStream( file );
ObjectInputStream in = new ObjectInputStream( fileIn );
List<TreeMap<String, RollingLinkedList<String>>> queues = (List<TreeMap<String, RollingLinkedList<String>>>) in.readObject();
// Combat queue is first
COMBAT_QUEUE = queues.get( 0 );
NONCOMBAT_QUEUE = queues.get( 1 );
in.close();
// after successfully loading, check if there were new zones added that aren't yet in the TreeMap.
AdventureQueueDatabase.checkZones();
}
catch ( FileNotFoundException e )
{
AdventureQueueDatabase.resetQueue( false );
return;
}
catch ( ClassNotFoundException e )
{
// Found the file, but the contents did not contain a properly-serialized treemap.
// Wipe the bogus file.
file.delete();
AdventureQueueDatabase.resetQueue();
return;
}
catch ( ClassCastException e )
{
// Old version of the combat queue handling. Sorry, have to delete your queue.
file.delete();
AdventureQueueDatabase.resetQueue();
return;
}
catch ( EOFException e )
{
// Malformed data. Wipe the bogus file.
file.delete();
AdventureQueueDatabase.resetQueue();
return;
}
catch ( IOException e )
{
e.printStackTrace();
}
}
public static double applyQueueEffects( double numerator, MonsterData monster, AreaCombatData data )
{
String zone = data.getZone();
RollingLinkedList zoneQueue = COMBAT_QUEUE.get( zone );
double denominator = data.totalWeighting();
// without queue effects the result is just numerator/denominator.
if ( zoneQueue == null )
{
return numerator / denominator;
}
// rate for monster IN the queue is 1 / (4a - 3b) and rate for monster NOT IN the queue is 4 / (4a - 3b) where
// a = weight of monsters in the zone
// b = weight of monsters in the queue
HashSet<String> zoneSet = new HashSet<String>( zoneQueue ); // just care about unique elements
// Ignore monsters in the queue that aren't actually part of the zone's normal monster list
// This includes monsters that have special conditions to find and wandering monsters
// that are not part of the location at all
// Ignore olfacted or long conned monsters, as these are never rejected
int queueWeight = 0;
for ( String mon : zoneSet )
{
MonsterData queueMonster = MonsterDatabase.findMonster( mon, false );
int index = data.getMonsterIndex( queueMonster );
boolean olfacted =
queueMonster != null &&
( ( Preferences.getString( "olfactedMonster" ).equals( queueMonster.getName() ) &&
KoLConstants.activeEffects.contains( FightRequest.ONTHETRAIL ) ) ||
Preferences.getString( "longConMonster" ).equals( queueMonster.getName() ) );
if ( index != -1 && data.getWeighting( index ) > 0 && !olfacted )
{
queueWeight += data.getWeighting( index );
}
}
boolean olfacted = ( Preferences.getString( "olfactedMonster" ).equals( monster.getName() ) &&
KoLConstants.activeEffects.contains( FightRequest.ONTHETRAIL ) ) ||
Preferences.getString( "longConMonster" ).equals( monster.getName() );
double newNumerator = numerator * ( zoneQueue.contains( monster.getName() ) && !olfacted ? 1 : 4 );
double newDenominator = ( 4 * denominator - 3 * queueWeight );
return newNumerator / newDenominator;
}
}