/**
* 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.io.BufferedReader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.MonsterStatusTracker;
import net.sourceforge.kolmafia.objectpool.ItemPool;
import net.sourceforge.kolmafia.persistence.AdventureDatabase;
import net.sourceforge.kolmafia.persistence.MonsterDatabase;
import net.sourceforge.kolmafia.persistence.QuestDatabase;
import net.sourceforge.kolmafia.persistence.QuestDatabase.Quest;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.request.FightRequest;
import net.sourceforge.kolmafia.utilities.FileUtilities;
import net.sourceforge.kolmafia.utilities.LockableListFactory;
public abstract class EncounterManager
{
// Types of special encounters
public enum EncounterType
{
NONE,
STOP,
SEMIRARE,
CLOVER,
GLYPH,
TURTLE,
SEAL,
FIST,
BORIS,
BADMOON,
BUGBEAR,
WANDERER,
SUPERLIKELY,
ULTRARARE,
FREE_COMBAT,
NOWANDER, // Don't start wandering monster counters
}
public static class Encounter
{
String location;
EncounterType encounterType;
String encounter;
public Encounter( String[] row )
{
location = row[ 0 ];
encounterType = EncounterType.valueOf( row[ 1 ] );
encounter = row[ 2 ];
}
public String getLocation()
{
return this.location;
}
public EncounterType getEncounterType()
{
return this.encounterType;
}
public String getEncounter()
{
return this.encounter;
}
};
private static Encounter[] specialEncounters;
static
{
resetEncounters();
}
private static void resetEncounters()
{
BufferedReader reader = FileUtilities.getVersionedReader( "encounters.txt", KoLConstants.ENCOUNTERS_VERSION );
ArrayList<Encounter> encounters = new ArrayList<Encounter>();
String[] data;
while ( ( data = FileUtilities.readData( reader ) ) != null )
{
if ( !AdventureDatabase.validateAdventureArea( data[ 0 ] ) )
{
RequestLogger.printLine( "Invalid adventure area: \"" + data[ 0 ] + "\"" );
continue;
}
encounters.add( new Encounter( data ) );
}
specialEncounters = encounters.toArray( new Encounter[ encounters.size() ] );
}
/**
* Utility method used to register a given adventure in the running adventure summary.
*/
public void registerAdventure( final KoLAdventure adventureLocation )
{
EncounterManager.registerAdventure( adventureLocation.getAdventureName() );
}
public static void registerAdventure( final String adventureName )
{
if ( adventureName == null )
{
return;
}
RegisteredEncounter previousAdventure = (RegisteredEncounter) LockableListFactory.lastElement( KoLConstants.adventureList );
if ( previousAdventure != null && previousAdventure.name.equals( adventureName ) )
{
++previousAdventure.encounterCount;
KoLConstants.adventureList.set( KoLConstants.adventureList.size() - 1, previousAdventure );
}
else
{
KoLConstants.adventureList.add( new RegisteredEncounter( null, adventureName ) );
}
}
public static final Encounter findEncounter( final String encounterName )
{
for ( int i = 0; i < specialEncounters.length; ++i )
{
Encounter encounter = specialEncounters[ i ];
if ( encounterName.equalsIgnoreCase( encounter.encounter ) )
{
return encounter;
}
}
return null;
}
public static final EncounterType encounterType( final String encounterName )
{
Encounter encounter = EncounterManager.findEncounter( encounterName );
return EncounterManager.encounterType( encounter, encounterName );
}
private static final EncounterType encounterType( final Encounter encounter, final String encounterName )
{
return encounter != null ?
encounter.encounterType :
BadMoonManager.specialAdventure( encounterName ) ?
EncounterType.BADMOON :
EncounterType.NONE;
}
public static final String findEncounterForLocation( final String locationName, final EncounterType type )
{
for ( int i = 0; i < specialEncounters.length; ++i )
{
Encounter encounter = specialEncounters[ i ];
if ( locationName.equalsIgnoreCase( encounter.location ) && type.equals( encounter.encounterType ) )
{
return encounter.encounter;
}
}
return null;
}
public static final boolean isAutoStop( final String encounterName )
{
if ( encounterName.equals( "Under the Knife" ) && Preferences.getString( "choiceAdventure21" ).equals( "2" ) )
{
return false;
}
EncounterType encounterType = encounterType( encounterName );
return encounterType == EncounterType.STOP ||
encounterType == EncounterType.BORIS ||
encounterType == EncounterType.GLYPH ||
encounterType == EncounterType.BADMOON;
}
public static boolean isRomanticEncounter( final String responseText, final boolean checkMonster )
{
if ( responseText.contains( "hear a wolf whistle" ) ||
responseText.contains( "you feel the hairs" ) )
{
return true;
}
// This is called from some places before the next monster is set.
// Don't bother checking the monster in those cases.
if ( checkMonster && TurnCounter.isCounting( "Romantic Monster window end", 0, 10 ) &&
Preferences.getString( "nextAdventure" ).equals( "The Deep Machine Tunnels" ) )
{
String name = MonsterStatusTracker.getLastMonsterName();
return name.equalsIgnoreCase( Preferences.getString( "romanticTarget" ) );
}
return false;
}
public static final boolean isEnamorangEncounter( final String responseText, final boolean checkMonster )
{
if ( responseText.contains( "tangled heartstrings" ) )
{
return true;
}
if ( checkMonster && TurnCounter.isCounting( "Enamorang Monster", 0 ) &&
Preferences.getString( "nextAdventure" ).equals( "The Deep Machine Tunnels" ) )
{
String name = MonsterStatusTracker.getLastMonsterName();
return name.equalsIgnoreCase( Preferences.getString( "enamorangMonster" ) );
}
return false;
}
public static final boolean isDigitizedEncounter( final String responseText, final boolean checkMonster )
{
if ( responseText.contains( "must have hit CTRL+V" ) )
{
return true;
}
// This is called from some places before the next monster is set.
// Don't bother checking the monster in those cases.
if ( checkMonster && TurnCounter.isCounting( "Digitize Monster", 0 ) &&
Preferences.getString( "nextAdventure" ).equals( "The Deep Machine Tunnels" ) )
{
String name = MonsterStatusTracker.getLastMonsterName();
return name.equalsIgnoreCase( Preferences.getString( "_sourceTerminalDigitizeMonster" ) );
}
return false;
}
public static final boolean isWanderingMonster( String encounter )
{
MonsterData monster = MonsterDatabase.findMonster( encounter, false );
return monster != null && monster.getType().contains( EncounterType.WANDERER );
}
public static boolean isSemiRareMonster( String encounter )
{
MonsterData monster = MonsterDatabase.findMonster( encounter, false );
return monster != null && monster.getType().contains( EncounterType.SEMIRARE );
}
public static boolean isSuperlikelyMonster( String encounter )
{
MonsterData monster = MonsterDatabase.findMonster( encounter, false );
return monster != null && monster.getType().contains( EncounterType.SUPERLIKELY );
}
public static boolean isFreeCombatMonster( String encounter )
{
MonsterData monster = MonsterDatabase.findMonster( encounter, false );
return monster != null && monster.getType().contains( EncounterType.FREE_COMBAT );
}
public static boolean isUltrarareMonster( String encounter )
{
MonsterData monster = MonsterDatabase.findMonster( encounter, false );
return monster != null && monster.getType().contains( EncounterType.ULTRARARE );
}
public static boolean isNoWanderMonster( String encounter )
{
MonsterData monster = MonsterDatabase.findMonster( encounter, false );
return monster != null && monster.getType().contains( EncounterType.NOWANDER );
}
// Used to ignore special monsters re-encountered via copying
public static boolean ignoreSpecialMonsters = false;
public static void ignoreSpecialMonsters()
{
ignoreSpecialMonsters = true;
}
private static void recognizeEncounter( final String encounterName, final String responseText )
{
Encounter encounter = EncounterManager.findEncounter( encounterName );
EncounterType encounterType = EncounterManager.encounterType( encounter, encounterName );
if ( encounterType == EncounterType.BUGBEAR )
{
BugbearManager.registerEncounter( encounter, responseText );
return;
}
// Use of Rain Man skill fires two encounters, first is a non-combat, second a fight, which could be a semi-rare
if ( KoLCharacter.inRaincore() && responseText.contains( "simulacrum of a previous foe" ) )
{
EncounterManager.ignoreSpecialMonsters();
}
// You stop for a moment to catch your breath, and possibly a
// cold, and hear a wolf whistle from behind you. You spin
// around and see <monster> that looks suspiciously like the
// ones you shot with a love arrow earlier.
// Some semirares can also be clover adventures, if a clover disappears it isn't a semi-rare
if ( encounterType == EncounterType.SEMIRARE &&
!ignoreSpecialMonsters &&
!EncounterManager.isRomanticEncounter( responseText, false ) &&
!EncounterManager.isDigitizedEncounter( responseText, false ) &&
!EncounterManager.isEnamorangEncounter( responseText, false ) &&
!responseText.contains( "clover disappears" ) &&
!FightRequest.edFightInProgress() )
{
KoLCharacter.registerSemirare();
return;
}
if ( encounterType == EncounterType.NONE )
{
return;
}
if ( encounterType == EncounterType.BADMOON )
{
BadMoonManager.registerAdventure( encounterName );
}
if ( encounterType == EncounterType.STOP ||
encounterType == EncounterType.BORIS ||
encounterType == EncounterType.GLYPH ||
encounterType == EncounterType.BADMOON )
{
GoalManager.checkAutoStop( encounterName );
}
}
/**
* Utility. The method used to register a given encounter in the running adventure summary.
*/
public static void registerEncounter( String encounterName, final String encounterType, final String responseText )
{
encounterName = encounterName.trim();
handleSpecialEncounter( encounterName, responseText );
recognizeEncounter( encounterName, responseText );
RegisteredEncounter[] encounters = new RegisteredEncounter[ KoLConstants.encounterList.size() ];
KoLConstants.encounterList.toArray( encounters );
for ( int i = 0; i < encounters.length; ++i )
{
if ( encounters[ i ].name.equals( encounterName ) )
{
++encounters[ i ].encounterCount;
// Manually set to force repainting in GUI
KoLConstants.encounterList.set( i, encounters[ i ] );
return;
}
}
KoLConstants.encounterList.add( new RegisteredEncounter( encounterType, encounterName ) );
}
public static void handleSpecialEncounter( final String encounterName, final String responseText )
{
if ( encounterName.equalsIgnoreCase( "Step Up to the Table, Put the Ball in Play" ) )
{
if ( InventoryManager.hasItem( ItemPool.CARONCH_DENTURES ) )
{
ResultProcessor.processItem( ItemPool.CARONCH_DENTURES, -1 );
QuestDatabase.setQuestIfBetter( Quest.PIRATE, "step4" );
}
if ( InventoryManager.hasItem( ItemPool.FRATHOUSE_BLUEPRINTS ) )
{
ResultProcessor.processItem( ItemPool.FRATHOUSE_BLUEPRINTS, -1 );
}
return;
}
if ( encounterName.equalsIgnoreCase( "Granny, Does Your Dogfish Bite?" ) )
{
if ( InventoryManager.hasItem( ItemPool.GRANDMAS_MAP ) )
{
ResultProcessor.processItem( ItemPool.GRANDMAS_MAP, -1 );
}
return;
}
if ( encounterName.equalsIgnoreCase( "Meat For Nothing and the Harem for Free" ) )
{
Preferences.setBoolean( "_treasuryEliteMeatCollected", true );
return;
}
if ( encounterName.equalsIgnoreCase( "Finally, the Payoff" ) )
{
Preferences.setBoolean( "_treasuryHaremMeatCollected", true );
return;
}
if ( encounterName.equals( "Faction Traction = Inaction" ) )
{
Preferences.setInteger( "booPeakProgress", 98 );
return;
}
if ( encounterName.equals( "Daily Done, John." ) )
{
// Daily Dungeon Complete
Preferences.setBoolean( "dailyDungeonDone", true );
Preferences.setInteger( "_lastDailyDungeonRoom", 15 );
return;
}
if ( encounterName.equals( "A hidden surprise!" ) )
{
// Since this content is short-lived, create the patterns here every time
// the encounter is found instead of globally
Pattern GIFT_SENDER_PATTERN = Pattern.compile( "nounder><b>(.*?)</b></a>" );
Pattern NOTE_PATTERN = Pattern.compile( "1px solid black;'>(.*?)</td></tr>", Pattern.DOTALL );
Matcher senderMatcher = GIFT_SENDER_PATTERN.matcher( responseText );
if ( senderMatcher.find() )
{
String sender = senderMatcher.group( 1 );
RequestLogger.printLine( "Gift sender: " + sender );
RequestLogger.updateSessionLog( "Gift sender: " + sender );
}
Matcher noteMatcher = NOTE_PATTERN.matcher( responseText );
if ( noteMatcher.find() )
{
String note = noteMatcher.group( 1 );
RequestLogger.printLine( "Gift note: " + note );
RequestLogger.updateSessionLog( "Gift note: " + note );
}
}
}
public static class RegisteredEncounter
implements Comparable<RegisteredEncounter>
{
private final String type;
private final String name;
private final String stringform;
private int encounterCount;
public RegisteredEncounter( final String type, final String name )
{
this.type = type;
// The name is likely a substring of a page load, so storing it
// as-is would keep the entire page in memory.
this.name = new String( name );
this.stringform = type == null ? name : type + ": " + name;
this.encounterCount = 1;
}
@Override
public String toString()
{
return this.stringform + " (" + this.encounterCount + ")";
}
public int compareTo( final RegisteredEncounter o )
{
if ( !( o instanceof RegisteredEncounter ) || o == null )
{
return -1;
}
if ( this.type == null || ( (RegisteredEncounter) o ).type == null || this.type.equals( ( (RegisteredEncounter) o ).type ) )
{
return this.name.compareToIgnoreCase( ( (RegisteredEncounter) o ).name );
}
return this.type.equals( "Combat" ) ? 1 : -1;
}
}
}