/**
* 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.Iterator;
import java.util.StringTokenizer;
import net.sourceforge.kolmafia.KoLCharacter;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.MonsterData;
import net.sourceforge.kolmafia.combat.MonsterStatusTracker;
import net.sourceforge.kolmafia.persistence.MonsterDatabase;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.request.StandardRequest;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public class BanishManager
{
private static final ArrayList<BanishedMonster> banishedMonsters = new ArrayList<BanishedMonster>();
private enum Reset
{
TURN_RESET,
TURN_ROLLOVER_RESET,
ROLLOVER_RESET,
AVATAR_RESET,
NEVER_RESET,
}
private static class Banisher
{
final String name;
final int duration;
final int queueSize;
final boolean isTurnFree;
final Reset resetType;
public Banisher( final String name, final int duration, final int queueSize, final boolean isTurnFree, final Reset resetType )
{
this.name = name;
this.duration = duration;
this.queueSize = queueSize;
this.isTurnFree = isTurnFree;
this.resetType = resetType;
}
public final String getName()
{
return this.name;
}
public final int getDuration()
{
// returns actual duration of banish after the turn used, which varies depending if that turn is free
int turnCost = this.isTurnFree ? 0 : 1;
return this.duration - turnCost;
}
public final int getQueueSize()
{
return this.queueSize;
}
public final boolean isTurnFree()
{
return this.isTurnFree;
}
public final Reset getResetType()
{
return this.resetType;
}
}
// Format is name of banisher, duration of banisher, how many monsters can be banished at once from this source,
// whether banish is turn free, type of reset.
private static final Banisher[] BANISHER = new Banisher[]
{
new Banisher( "banishing shout", -1, 3, false, Reset.AVATAR_RESET ),
new Banisher( "batter up!", -1, 1, false, Reset.ROLLOVER_RESET ),
new Banisher( "beancannon", -1, 5, false, Reset.ROLLOVER_RESET ),
new Banisher( "breathe out", 20, 1, true, Reset.TURN_RESET ),
new Banisher( "bundle of "fragrant" herbs", -1, 3, true, Reset.ROLLOVER_RESET ),
new Banisher( "chatterboxing", 20, 1, true, Reset.TURN_RESET ),
new Banisher( "classy monkey", 20, 1, false, Reset.TURN_RESET ),
new Banisher( "cocktail napkin", 20, 1, true, Reset.TURN_RESET ),
new Banisher( "crystal skull", 20, 1, false, Reset.TURN_RESET ),
new Banisher( "curse of vacation", -1, 1, false, Reset.ROLLOVER_RESET ),
new Banisher( "deathchucks", -1, 1, true, Reset.ROLLOVER_RESET ),
new Banisher( "dirty stinkbomb",-1, 1, true, Reset.ROLLOVER_RESET ),
new Banisher( "divine champagne popper", 5, 1, true, Reset.TURN_RESET ),
new Banisher( "harold's bell", 20, 1, false, Reset.TURN_RESET ),
new Banisher( "howl of the alpha", -1, 3, false, Reset.AVATAR_RESET ),
new Banisher( "gingerbread restraining order", -1, 1, false, Reset.ROLLOVER_RESET ),
new Banisher( "ice hotel bell", -1, 1, true, Reset.ROLLOVER_RESET ),
new Banisher( "ice house", -1, 1, false, Reset.NEVER_RESET ),
new Banisher( "licorice rope", -1, 1, false, Reset.ROLLOVER_RESET ),
new Banisher( "louder than bomb", 20, 1, true, Reset.TURN_RESET ),
new Banisher( "nanorhino", -1, 1, false, Reset.ROLLOVER_RESET ),
new Banisher( "pantsgiving", 30, 1, false, Reset.TURN_RESET ),
new Banisher( "peel out", -1, 1, true, Reset.AVATAR_RESET ),
new Banisher( "pulled indigo taffy", 20, 1, true, Reset.TURN_RESET ),
new Banisher( "smoke grenade", 20, 1, false, Reset.TURN_RESET ),
new Banisher( "snokebomb", 30, 1, true, Reset.TURN_RESET ),
new Banisher( "spooky music box mechanism", -1, 1, false, Reset.ROLLOVER_RESET ),
new Banisher( "staff of the standalone cheese", -1, 5, false, Reset.AVATAR_RESET ),
new Banisher( "stinky cheese eye", 10, 1, true, Reset.TURN_RESET ),
new Banisher( "tennis ball", 30, 1, true, Reset.TURN_RESET ),
new Banisher( "thunder clap", 40, 1, false, Reset.TURN_RESET ),
new Banisher( "v for vivala mask", 10, 1, true, Reset.TURN_RESET ),
new Banisher( "walk away from explosion", 30, 1, false, Reset.TURN_RESET ),
};
private static class BanishedMonster
{
final String monsterName;
final String banishName;
final int turnBanished;
public BanishedMonster( final String monsterName, final String banishName, final int turnBanished )
{
this.monsterName = monsterName;
this.banishName = banishName;
this.turnBanished = turnBanished;
}
public final String getMonsterName()
{
return this.monsterName;
}
public final String getBanishName()
{
return this.banishName;
}
public final int getTurnBanished()
{
return this.turnBanished;
}
}
public static final void clearCache()
{
BanishManager.banishedMonsters.clear();
}
public static final void loadBanishedMonsters()
{
BanishManager.banishedMonsters.clear();
String banishes = Preferences.getString( "banishedMonsters" );
if ( banishes.length() == 0 )
{
return;
}
StringTokenizer tokens = new StringTokenizer( banishes, ":" );
while ( tokens.hasMoreTokens() )
{
String monsterName = tokens.nextToken();
if ( !tokens.hasMoreTokens() ) break;
String banishName = tokens.nextToken();
if ( !tokens.hasMoreTokens() ) break;
int turnBanished = StringUtilities.parseInt( tokens.nextToken() );
int banishDuration = BanishManager.findBanisher( banishName ).getDuration();
Reset resetType = BanishManager.findBanisher( banishName ).getResetType();
if ( ( resetType != Reset.TURN_RESET && resetType != Reset.TURN_ROLLOVER_RESET ) ||
( turnBanished + banishDuration >= KoLCharacter.getCurrentRun() ) )
{
BanishManager.addBanishedMonster( monsterName, banishName, turnBanished );
}
}
}
public static final void saveBanishedMonsters()
{
BanishManager.recalculate();
StringBuilder banishString = new StringBuilder();
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
if ( banishString.length() > 0 )
{
banishString.append( ":" );
}
banishString.append( current.monsterName );
banishString.append( ":" );
banishString.append( current.banishName );
banishString.append( ":" );
banishString.append( current.turnBanished );
}
Preferences.setString( "banishedMonsters", banishString.toString() );
}
public static final void resetRollover()
{
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
Reset type = BanishManager.findBanisher( current.getBanishName() ).getResetType();
if ( type == Reset.ROLLOVER_RESET || type == Reset.TURN_ROLLOVER_RESET )
{
it.remove();
}
}
BanishManager.saveBanishedMonsters();
}
public static final void resetAvatar()
{
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
if ( BanishManager.findBanisher( current.getBanishName() ).getResetType() == Reset.AVATAR_RESET )
{
it.remove();
}
}
BanishManager.saveBanishedMonsters();
}
public static final void resetAscension()
{
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
if ( BanishManager.findBanisher( current.getBanishName() ).getResetType() != Reset.NEVER_RESET )
{
it.remove();
}
}
BanishManager.saveBanishedMonsters();
}
public static final void update()
{
BanishManager.recalculate();
BanishManager.saveBanishedMonsters();
}
private static final void recalculate()
{
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
int banisherDuration = BanishManager.findBanisher( current.getBanishName() ).getDuration();
Reset resetType = BanishManager.findBanisher( current.getBanishName() ).getResetType();
if ( ( resetType == Reset.TURN_RESET || resetType == Reset.TURN_ROLLOVER_RESET ) &&
( current.getTurnBanished() + banisherDuration <= KoLCharacter.getCurrentRun() ) )
{
it.remove();
}
}
}
public static final Banisher findBanisher( final String banisher )
{
for ( Banisher ban : BANISHER )
{
if ( ban.getName().equals( banisher ) )
{
return ban;
}
}
return null;
}
public static final void banishCurrentMonster( final String banishName )
{
MonsterData monster = MonsterStatusTracker.getLastMonster();
if ( monster == null )
{
return;
}
BanishManager.banishMonster( monster.getName(), banishName );
}
public static final void banishMonster( final String monsterName, final String banishName )
{
if ( BanishManager.countBanishes( banishName ) >= BanishManager.findBanisher( banishName ).getQueueSize() )
{
BanishManager.removeOldestBanish( banishName );
}
// Banishes fail in some areas, monsters in them cannot be banished
MonsterData monster = MonsterDatabase.findMonster( monsterName, false );
if ( monster.isNoBanish() )
{
KoLmafia.updateDisplay( "Banish of " + monsterName + " by " + banishName + " failed, as monsters from this area cannot be banished." );
return;
}
KoLmafia.updateDisplay( monsterName + " banished by " + banishName + "." );
int turnCost = BanishManager.findBanisher( banishName ).isTurnFree() ? 0 : 1;
BanishManager.addBanishedMonster( monsterName, banishName, KoLCharacter.getCurrentRun() + turnCost );
BanishManager.saveBanishedMonsters();
// Legacy support
if ( banishName.equals( "nanorhino" ) )
{
Preferences.setString( "_nanorhinoBanishedMonster", monsterName );
}
else if ( banishName.equals( "banishing shout" ) || banishName.equals( "howl of the alpha" ) )
{
String pref = monsterName;
String[] monsters = Preferences.getString( "banishingShoutMonsters" ).split( "\\|" );
for ( int i = 0; i < monsters.length && i < 2; ++i )
{
if ( monsters[ i ].length() > 0 )
{
pref += "|" + monsters[ i ];
}
}
Preferences.setString( "banishingShoutMonsters", pref );
}
else if ( banishName.equals( "staff of the standalone cheese" ) )
{
String pref = monsterName;
String[] monsters = Preferences.getString( "_jiggleCheesedMonsters" ).split( "\\|" );
for ( int i = 0; i < monsters.length; ++i )
{
if ( monsters[ i ].length() > 0 )
{
pref += "|" + monsters[ i ];
}
}
Preferences.setString( "_jiggleCheesedMonsters", pref );
}
}
private static final void addBanishedMonster( final String monsterName, final String banishName, final int turnBanished )
{
BanishedMonster newBanishedMonster = new BanishedMonster( monsterName, banishName, turnBanished );
if ( !BanishManager.banishedMonsters.contains( newBanishedMonster ) )
{
BanishManager.banishedMonsters.add( newBanishedMonster );
}
}
public static final void removeBanishByBanisher( final String banisher )
{
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
if ( current.getBanishName().equals( banisher ) )
{
it.remove();
}
}
BanishManager.saveBanishedMonsters();
}
public static final void removeBanishByMonster( final String monster )
{
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
if ( current.getMonsterName().equals( monster ) )
{
it.remove();
}
}
BanishManager.saveBanishedMonsters();
}
public static final void removeOldestBanish( final String banisher )
{
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
String target = null;
int earliest = -1;
while ( it.hasNext() )
{
BanishedMonster current = it.next();
if ( current.getBanishName().equals( banisher ) )
{
if ( earliest == -1 || current.getTurnBanished() < earliest )
{
target = current.getMonsterName();
earliest = current.getTurnBanished();
}
}
}
if ( target != null )
{
BanishManager.removeBanishByMonster( target );
}
}
public static final boolean isBanished( final String monster )
{
BanishManager.recalculate();
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
if ( current.getMonsterName().equalsIgnoreCase( monster ) )
{
if ( current.getBanishName().equals( "ice house" ) && !StandardRequest.isAllowed( "Items", "ice house" ) )
{
continue;
}
return true;
}
}
return false;
}
private static final int countBanishes( final String banisher )
{
int banishCount = 0;
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
if ( current.getBanishName().equals( banisher ) )
{
banishCount++;
}
}
return banishCount;
}
public static final String getBanishList()
{
BanishManager.recalculate();
StringBuilder banishList = new StringBuilder();
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
if ( banishList.length() > 0 )
{
banishList.append( "," );
}
banishList.append( current.monsterName );
}
return banishList.toString();
}
public static final String getIceHouseMonster()
{
BanishManager.recalculate();
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
while ( it.hasNext() )
{
BanishedMonster current = it.next();
if ( current.banishName.equals( "ice house" ) )
{
return current.monsterName;
}
}
return null;
}
public static final String[][] getBanishData()
{
BanishManager.recalculate();
Iterator<BanishedMonster> it = BanishManager.banishedMonsters.iterator();
int banish = 0;
int count = BanishManager.banishedMonsters.size();
if ( count > 0 )
{
String[][] banishData = new String[ count ][ 4 ];
while ( it.hasNext() )
{
BanishedMonster current = it.next();
banishData[ banish ][ 0 ] = current.monsterName;
banishData[ banish ][ 1 ] = current.banishName;
banishData[ banish ][ 2 ] = String.valueOf( current.turnBanished );
int banisherDuration = BanishManager.findBanisher( current.banishName ).getDuration();
Reset resetType = BanishManager.findBanisher( current.banishName ).getResetType();
if ( resetType == Reset.TURN_RESET )
{
banishData[ banish ][ 3 ] = String.valueOf( current.turnBanished + banisherDuration - KoLCharacter.getCurrentRun() );
}
else if ( resetType == Reset.ROLLOVER_RESET )
{
banishData[ banish ][ 3 ] = "Until Rollover";
}
else if ( resetType == Reset.TURN_ROLLOVER_RESET )
{
banishData[ banish ][ 3 ] = String.valueOf( current.turnBanished + banisherDuration - KoLCharacter.getCurrentRun() ) + "or Until Rollover";
}
else if ( resetType == Reset.AVATAR_RESET )
{
banishData[ banish ][ 3 ] = "Until Prism Break";
}
else if ( resetType == Reset.NEVER_RESET )
{
banishData[ banish ][ 3 ] = "Until Ice House opened";
}
banish++;
}
return banishData;
}
return null;
}
}