/**
* 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.request;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.kolmafia.AdventureResult;
import net.sourceforge.kolmafia.KoLConstants.MafiaState;
import net.sourceforge.kolmafia.KoLConstants.Stat;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.RequestLogger;
import net.sourceforge.kolmafia.moods.RecoveryManager;
import net.sourceforge.kolmafia.objectpool.EffectPool;
import net.sourceforge.kolmafia.objectpool.ItemPool;
import net.sourceforge.kolmafia.persistence.MonsterDatabase.Phylum;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.session.ChoiceManager;
import net.sourceforge.kolmafia.session.EncounterManager;
import net.sourceforge.kolmafia.session.InventoryManager;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public class DeckOfEveryCardRequest
extends GenericRequest
{
private static final TreeMap<Integer,EveryCard> idToCard = new TreeMap<Integer,EveryCard>();
private static final TreeMap<String,EveryCard> canonicalNameToCard = new TreeMap<String,EveryCard>();
private static final TreeMap<Phylum,EveryCard> phylumToCard = new TreeMap<Phylum,EveryCard>();
private static final HashSet<EveryCard> monsterCards = new HashSet<EveryCard>();
private static final TreeMap<Stat,EveryCard> statToCard = new TreeMap<Stat,EveryCard>();
private static final TreeMap<AdventureResult,EveryCard> buffToCard = new TreeMap<AdventureResult,EveryCard>();
public static final TreeSet<String> allCardNames = new TreeSet<String>();
public static final AdventureResult STRONGLY_MOTIVATED = EffectPool.get( EffectPool.STRONGLY_MOTIVATED, 20 );
public static final AdventureResult MAGICIANSHIP = EffectPool.get( EffectPool.MAGICIANSHIP, 20 );
public static final AdventureResult DANCIN_FOOL = EffectPool.get( EffectPool.DANCIN_FOOL_CARD, 20 );
public static final AdventureResult FORTUNE_OF_THE_WHEEL = EffectPool.get( EffectPool.FORTUNE_OF_THE_WHEEL, 20 );
public static final AdventureResult RACING = EffectPool.get( EffectPool.RACING, 20 );
static
{
registerCard( 1, "X of Clubs" ); // gives X seal-clubbing clubs, and 3 PvP fights
registerCard( 3, "X of Diamonds" ); // gives X hyper-cubic zirconiae
registerCard( 2, "X of Hearts" ); // gives X bubblegum hearts
registerCard( 4, "X of Spades" ); // gives X grave-robbing shovels, and X letters of a string
registerCard( 42, "X of Papayas" ); // gives X papayas
registerCard( 65, "X of Kumquats" ); // gives X kumquats
registerCard( 43, "X of Salads" ); // gives X delicious salads
registerCard( 5, "X of Cups" ); // gives X random boozes
registerCard( 8, "X of Coins" ); // gives X valuable coins
registerCard( 7, "X of Swords" ); // gives X swords
registerCard( 6, "X of Wands" ); // gives 5 turns of X random buffs.
registerCard( 47, "XVI - The Tower" ); // Gives a random hero tower key
registerCard( 66, "Professor Plum" ); // Get 10 plums
registerCard( 59, "Spare Tire" ); // Get a tires
registerCard( 60, "Extra Tank" ); // Get a full meat tank
registerCard( 61, "Sheep" ); // Get 3 stone wool
registerCard( 62, "Year of Plenty" ); // Get 5 random foods.
registerCard( 63, "Mine" ); // Get one each of asbestos ore, linoleum ore, and chrome ore
registerCard( 64, "Laboratory" ); // Get five random potions
// The following give new items
registerCard( 31, "Plains" ); // Gives a white mana
registerCard( 32, "Swamp" ); // Gives a black mana
registerCard( 33, "Mountain" ); // Gives a red mana
registerCard( 34, "Forest" ); // Gives a green mana
registerCard( 35, "Island" ); // Gives a blue mana
registerCard( 52, "Lead Pipe" ); // Get a Lead Pipe
registerCard( 53, "Rope" ); // Get a Rope
registerCard( 54, "Wrench" ); // Get a Wrench
registerCard( 55, "Candlestick" ); // Get a Candlestick
registerCard( 56, "Knife" ); // Get a Knife
registerCard( 57, "Revolver" ); // Get a Revolver
registerCard( 41, "Gift Card" ); // Get a Gift Card
registerCard( 58, "1952 Mickey Mantle" ); // Get a 1952 Mickey Mantle card
// The following give stats
registerCard( 68, "XXI - The World", Stat.MUSCLE ); // Gives 500 muscle substats
registerCard( 70, "III - The Empress", Stat.MYSTICALITY ); // Gives 500 mysticality substats
registerCard( 69, "VI - The Lovers", Stat.MOXIE ); // Gives 500 moxie substats
// The following give skills
registerCard( 36, "Healing Salve" ); // Gives the skill Healing Salve
registerCard( 37, "Dark Ritual" ); // Gives the skill Dark Ritual
registerCard( 38, "Lightning Bolt" ); // Gives the skill Lightning Bolt
registerCard( 39, "Giant Growth" ); // Gives the skill Giant Growth
registerCard( 40, "Ancestral Recall" ); // Gives the skill Ancestral Recall
// The following give buffs
registerCard( 51, "XI - Strength", STRONGLY_MOTIVATED ); // Gives 20 turns of Strongly Motivated (+200% muscle)
registerCard( 50, "I - The Magician", MAGICIANSHIP ); // Gives 20 turns of Magicianship (+200% mysticality)
registerCard( 49, "0 - The Fool", DANCIN_FOOL ); // Gives 20 turns of Dancin' Fool (+200% moxie)
registerCard( 67, "X - The Wheel of Fortune", FORTUNE_OF_THE_WHEEL ); // Gives 20 turns of Fortune of the Wheel (+100% item drop)
registerCard( 48, "The Race Card", RACING ); // Gives 20 turns of Racing! (+200% init)
// The following lead to fights
registerMonsterCard( 46, "Green Card" ); // Fight a legal alien
registerMonsterCard( 45, "IV - The Emperor" ); // Fight The Emperor (drops The Emperor's dry cleaning)
registerMonsterCard( 44, "IX - The Hermit" ); // Fight The Hermit
registerCard( 15, "Werewolf", Phylum.BEAST ); // Fight a random Beast
registerCard( 11, "The Hive", Phylum.BUG ); // Fight a random Bug
registerCard( 26, "XVII - The Star", Phylum.CONSTELLATION ); // Fight a random Constellation
registerCard( 19, "VII - The Chariot", Phylum.CONSTRUCT ); // Fight a random Construct
registerCard( 16, "XV - The Devil", Phylum.DEMON ); // Fight a random Demon
registerCard( 13, "V - The Hierophant", Phylum.DUDE ); // Fight a random Dude
registerCard( 17, "Fire Elemental", Phylum.ELEMENTAL ); // Fight a random Elemental
registerCard( 28, "Christmas Card", Phylum.ELF ); // Fight a random Elf
registerCard( 29, "Go Fish", Phylum.FISH ); // Fight a random Fish
registerCard( 10, "Goblin Sapper", Phylum.GOBLIN ); // Fight a random Goblin
registerCard( 20, "II - The High Priestess", Phylum.HIPPY ); // Fight a random Hippy
registerCard( 24, "XIV - Temperance", Phylum.HOBO ); // Fight a random Hobo
registerCard( 14, "XVIII - The Moon", Phylum.HORROR ); // Fight a random Horror
registerCard( 12, "Hunky Fireman Card", Phylum.HUMANOID ); // Fight a random Humanoid
registerCard( 30, "Aquarius Horoscope", Phylum.MER_KIN ); // Fight a random Mer-Kin
registerCard( 21, "XII - The Hanged Man", Phylum.ORC ); // Fight a random Orc
registerCard( 27, "Suit Warehouse Discount Card", Phylum.PENGUIN ); // Fight a random Penguin
registerCard( 23, "Pirate Birthday Card", Phylum.PIRATE ); // Fight a random Pirate
registerCard( 22, "Plantable Greeting Card", Phylum.PLANT ); // Fight a random Plant
registerCard( 18, "Slimer Trading Card", Phylum.SLIME ); // Fight a random Slime
registerCard( 9, "XIII - Death", Phylum.UNDEAD ); // Fight a random Undead
registerCard( 25, "Unstable Portal", Phylum.WEIRD ); // Fight a random Weird
};
private static EveryCard registerCard( int id, String name )
{
EveryCard card = new EveryCard( id, name );
DeckOfEveryCardRequest.idToCard.put( id, card );
DeckOfEveryCardRequest.canonicalNameToCard.put( StringUtilities.getCanonicalName( name ), card );
DeckOfEveryCardRequest.allCardNames.add( name );
return card;
}
private static EveryCard registerMonsterCard( int id, String name )
{
EveryCard card = DeckOfEveryCardRequest.registerCard( id, name );
DeckOfEveryCardRequest.monsterCards.add( card );
return card;
}
private static void registerCard( int id, String name, Phylum phylum )
{
EveryCard card = DeckOfEveryCardRequest.registerMonsterCard( id, name );
DeckOfEveryCardRequest.phylumToCard.put( phylum, card );
}
private static void registerCard( int id, String name, Stat stat )
{
EveryCard card = DeckOfEveryCardRequest.registerCard( id, name );
DeckOfEveryCardRequest.statToCard.put( stat, card );
}
private static void registerCard( int id, String name, AdventureResult thing )
{
EveryCard card = DeckOfEveryCardRequest.registerCard( id, name );
if ( thing.isStatusEffect() )
{
DeckOfEveryCardRequest.buffToCard.put( thing, card );
}
}
private static String [] CANONICAL_CARDS_ARRAY;
static
{
Set<String> keys = DeckOfEveryCardRequest.canonicalNameToCard.keySet();
DeckOfEveryCardRequest.CANONICAL_CARDS_ARRAY = keys.toArray( new String[ keys.size() ] );
};
public static final List<String> getMatchingNames( final String substring )
{
return StringUtilities.getMatchingNames( DeckOfEveryCardRequest.CANONICAL_CARDS_ARRAY, substring );
}
public static boolean isMonsterCard( EveryCard card )
{
return DeckOfEveryCardRequest.monsterCards.contains( card );
}
public static EveryCard phylumToCard( Phylum phylum )
{
return DeckOfEveryCardRequest.phylumToCard.get( phylum );
}
public static EveryCard canonicalNameToCard( String name )
{
return DeckOfEveryCardRequest.canonicalNameToCard.get( name );
}
public static EveryCard statToCard( Stat stat )
{
return DeckOfEveryCardRequest.statToCard.get( stat );
}
public static EveryCard buffToCard( AdventureResult buff )
{
return DeckOfEveryCardRequest.buffToCard.get( buff );
}
private EveryCard card;
public DeckOfEveryCardRequest()
{
super( "choice.php" );
this.addFormField( "whichchoice", "1085" );
this.addFormField( "option", "1" );
this.card = null;
}
public DeckOfEveryCardRequest( EveryCard card )
{
super( "choice.php" );
this.addFormField( "whichchoice", "1086" );
this.addFormField( "option", "1" );
this.addFormField( "which", String.valueOf( card.id ) );
this.card = card;
}
@Override
protected boolean shouldFollowRedirect()
{
return true;
}
@Override
public int getAdventuresUsed()
{
return DeckOfEveryCardRequest.getAdventuresUsed( this.card );
}
public static int getAdventuresUsed( final String urlString )
{
EveryCard card = DeckOfEveryCardRequest.extractCardFromURL( urlString );
return DeckOfEveryCardRequest.getAdventuresUsed( card );
}
public static int getAdventuresUsed( final EveryCard card )
{
// Only cards that lead to a fight take a turn.
// If this is a random draw, it might lead to a fight
if ( card == null )
{
return 1;
}
// Otherwise, only monster cards lead to a fight.
return DeckOfEveryCardRequest.isMonsterCard( card ) ? 1 : 0;
}
@Override
public void run()
{
if ( GenericRequest.abortIfInFightOrChoice() )
{
return;
}
// If you can't get a deck into inventory, punt
if ( !InventoryManager.retrieveItem( ItemPool.DECK_OF_EVERY_CARD, 1, true ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You don't have a Deck of Every Card available" );
return;
}
// If you've used up your draws for the day, punt
int drawsUsed = Preferences.getInteger( "_deckCardsDrawn" );
int drawsNeeded = card == null ? 1 : 5;
if ( drawsUsed + drawsNeeded > 15 )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You don't have enough draws left from the deck to do that today" );
return;
}
GenericRequest useRequest = new GenericRequest( "inv_use.php" );
useRequest.addFormField( "whichitem", String.valueOf( ItemPool.DECK_OF_EVERY_CARD ) );
if ( this.card == null )
{
useRequest.addFormField( "which", "3" );
}
else
{
useRequest.addFormField( "cheat", "1" );
}
// If we are drawing a card which is known to lead to a fight -
// or are drawing a random card - recover first.
if ( this.getAdventuresUsed() > 0 )
{
// set location to "None" for the benefit of
// betweenBattleScripts
Preferences.setString( "nextAdventure", "None" );
RecoveryManager.runBetweenBattleChecks( true );
}
useRequest.run();
String responseText = useRequest.responseText;
// No response because of I/O error
if ( responseText == null )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "I/O error" );
return;
}
// You're too beaten up. An accidental papercut would kill you at this point.
if ( responseText.contains( "You're too beaten up" ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You are too beaten up to draw a card" );
return;
}
// You've already drawn your day's allotment of cards from the Deck of Every Card.
// Shouldn't happen, unless you've drawn cards outside of KoLmafia
if ( responseText.contains( "You've already drawn your day's allotment of cards" ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You've already used all your draws for the day" );
Preferences.setInteger( "_deckCardsDrawn", 15 );
return;
}
// You don't have enough energy left to cheat today.
if ( responseText.contains( "You don't have enough energy left to cheat today" ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You don't have enough draws left to cheat today" );
return;
}
// You don't have time to draw a card right now.
if ( responseText.contains( "You don't have time to draw a card right now" ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You don't have any adventures left" );
return;
}
// If you have already cheated a particular card today, it will
// not be available. Unfortunately, if you submit the request
// to cheat that card again, KoL says "Huh?" - and counts it as
// 5 draws. I submitted a bug report for this, but unless they
// decide to fix it, we'd better make sure the card is
// available
if ( this.card != null )
{
// Update the set of cards we know have been drawn today
DeckOfEveryCardRequest.parseAvailableCards( responseText);
if ( !responseText.contains( this.card.name ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "That card is not currently available." );
this.constructURLString( "choice.php?whichchoice=1086&option=2", true );
super.run();
return;
}
}
super.run();
responseText = this.responseText;
// Are there any generic failures we should look for here?
if ( this.card != null )
{
// <span class='guts'>Huh?</span>
if ( responseText.contains( "<span class='guts'>Huh?</span>" ) )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "You already drew that card today." );
return;
}
// Otherwise, need to confirm the draw
this.constructURLString( "choice.php?whichchoice=1085&option=1", true );
super.run();
}
}
public static final Pattern CHEAT_SELECT_PATTERN = Pattern.compile( "<select name=\"which\".*?</select>", Pattern.DOTALL );
public static final Pattern AVAILABLE_CARD_PATTERN = Pattern.compile( "<option [^>]*>(.*?)</option>" );
public static void parseAvailableCards( final String responseText )
{
// Iterate over the cards in the dropdown and remove them from the set of all cards
Matcher selectMatcher = DeckOfEveryCardRequest.CHEAT_SELECT_PATTERN.matcher( responseText );
if ( !selectMatcher.find() )
{
return;
}
TreeSet<String> cards = new TreeSet<String>( DeckOfEveryCardRequest.allCardNames );
Matcher cardMatcher = DeckOfEveryCardRequest.AVAILABLE_CARD_PATTERN.matcher( selectMatcher.group(0) );
while ( cardMatcher.find() )
{
String card = cardMatcher.group( 1 );
cards.remove( card );
}
// What remains is the set of cards we have drawn.
StringBuilder buffer = new StringBuilder();
for ( String card : cards )
{
if ( buffer.length() > 0 )
{
buffer.append( "|" );
}
buffer.append( card );
}
Preferences.setString( "_deckCardsSeen", buffer.toString() );
}
// <div id="blurb">You draw a card: <b>X - The Wheel of Fortune</b><p>This card has a little wheel pinned to the center of it.</div>
// <div>You draw a card: <b>Dark Ritual</b><p>This card looks like it contains a magic spell of some kind.</div>
public static final Pattern DRAW_CARD_PATTERN = Pattern.compile( "<div id=\"blurb\">.*?You draw a card: <b>(.*?)</b><p>(.*?)</div>", Pattern.DOTALL );
public static String parseCardEncounter( final String responseText )
{
Matcher matcher = DeckOfEveryCardRequest.DRAW_CARD_PATTERN.matcher( responseText );
if ( matcher.find() )
{
String cardName = matcher.group( 1 );
int of = cardName.indexOf( " of " );
String munged = of == -1 ? cardName : ( "X" + cardName.substring( of ) );
String alt = Preferences.getString( "_deckCardsSeen" );
String neu = alt.length() == 0 ? munged : ( alt + "|" + munged );
Preferences.setString( "_deckCardsSeen", neu );
EveryCard card = DeckOfEveryCardRequest.canonicalNameToCard( StringUtilities.getCanonicalName( munged ) );
if ( DeckOfEveryCardRequest.phylumToCard.containsValue( card ) )
{
EncounterManager.ignoreSpecialMonsters();
}
return cardName;
}
return null;
}
// There's something written on the ground under the shovels: GGUGEWCCCI<center>
public static final Pattern SPADE_CARD_PATTERN = Pattern.compile( "There's something written on the ground under the shovels: (.*?)<" );
public static void postChoice1( final String responseText )
{
Matcher matcher = DeckOfEveryCardRequest.SPADE_CARD_PATTERN.matcher( responseText );
if ( matcher.find() )
{
String message = "Spade letters: " + matcher.group( 1 );
RequestLogger.printLine( message );
RequestLogger.updateSessionLog( message );
}
}
public static final Pattern URL_CARD_PATTERN = Pattern.compile( "which=(\\d+)" );
public static EveryCard extractCardFromURL( final String urlString )
{
Matcher matcher = DeckOfEveryCardRequest.URL_CARD_PATTERN.matcher( urlString );
return matcher.find() ?
DeckOfEveryCardRequest.idToCard.get( StringUtilities.parseInt( matcher.group( 1 ) ) ) :
null;
}
public static boolean registerRequest( final String urlString )
{
if ( !urlString.startsWith( "choice.php" ) )
{
return false;
}
int choice = ChoiceManager.extractChoiceFromURL( urlString );
if ( choice != 1085 && choice != 1086 )
{
return false;
}
EveryCard card = DeckOfEveryCardRequest.extractCardFromURL( urlString );
if ( card == null )
{
// You are confirming the action for the card you drew
return true;
}
String message = "play " + card;
RequestLogger.printLine( message );
RequestLogger.updateSessionLog( message );
return true;
}
public static class EveryCard
{
public int id;
public String name;
private String stringForm;
public EveryCard( int id, String name )
{
this.id = id;
this.name = name;
this.stringForm = name + " (" + id + ")";
}
@Override
public boolean equals( final Object o )
{
return ( o instanceof EveryCard ) && ( (EveryCard) o ).id == this.id;
}
@Override
public int hashCode()
{
return this.id;
}
@Override
public String toString()
{
return this.stringForm;
}
}
}