/** * 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 net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLConstants.MafiaState; import net.sourceforge.kolmafia.KoLmafia; public abstract class PurchaseRequest extends GenericRequest implements Comparable<PurchaseRequest> { public static final int MAX_QUANTITY = 16777215; protected static boolean usePriceComparison; protected String hashField; protected String shopName; public boolean isMallStore; protected AdventureResult item; protected int quantity; protected int price; protected int limit; protected boolean canPurchase; protected long timestamp; protected int initialCount; // for detecting partial yields public PurchaseRequest( final String location ) { super( location ); } @Override public String getHashField() { return this.hashField; } public AdventureResult getItem() { return this.item; } public int getItemId() { return this.item.getItemId(); } public String getItemName() { return this.item.getName(); } public String getShopName() { return this.shopName; } public long getTimestamp() { return this.timestamp; } /** * Retrieves the price of the item being purchased. * * @return The price of the item being purchased */ public int getPrice() { return this.price; } public String getPriceString() { return KoLConstants.COMMA_FORMAT.format( this.getPrice() ); } public AdventureResult getCost() { return new AdventureResult( AdventureResult.MEAT, this.getPrice() ); } public String getCurrency( final long count ) { return "Meat"; } /** * Retrieves the quantity of the item available in the store. * * @return The quantity of the item in the store */ public int getQuantity() { return this.quantity; } /** * Sets the quantity of the item available in the store. * * @param quantity The quantity of the item available in the store. */ public void setQuantity( final int quantity ) { this.quantity = quantity; } /** * Retrieves the quantity of the item being purchased. * * @return The quantity of the item being purchased */ public int getLimit() { return this.limit; } /** * Sets the maximum number of items that can be purchased through this request. * * @param limit The maximum number of items to be purchased with this request */ public void setLimit( final int limit ) { this.limit = Math.min( this.quantity, limit ); } /** * Converts this request into a readable string. */ @Override public String toString() { StringBuilder buffer = new StringBuilder(); String color = this.color(); buffer.append( "<html><nobr>" ); if ( color != null ) { buffer.append( "<font color=\"" ); buffer.append( color ); buffer.append( "\">" ); } buffer.append( this.item.getName() ); buffer.append( " (" ); if ( this.quantity == PurchaseRequest.MAX_QUANTITY ) { buffer.append( "unlimited" ); } else if ( this.quantity < 0 ) { buffer.append( "unknown" ); } else { buffer.append( KoLConstants.COMMA_FORMAT.format( this.quantity ) ); if ( this.limit < this.quantity ) { buffer.append( " limit " ); buffer.append( KoLConstants.COMMA_FORMAT.format( this.limit ) ); } } buffer.append( " @ " ); buffer.append( this.getPriceString() ); buffer.append( "): " ); buffer.append( this.shopName ); if ( color != null ) { buffer.append( "</font>" ); } buffer.append( "</nobr></html>" ); return buffer.toString(); } public int getAvailableMeat() { return KoLCharacter.getAvailableMeat(); } public void setCanPurchase( final boolean canPurchase ) { this.canPurchase = canPurchase; } public void setCanPurchase() { this.setCanPurchase( this.getAvailableMeat() >= this.getPrice() ); } public boolean canPurchase() { return this.canPurchase && this.getAvailableMeat() >= this.getPrice(); } public String color() { return this.canPurchase && this.getAvailableMeat() >= this.getPrice() ? null : "gray"; } public boolean canPurchaseIgnoringMeat() { return this.canPurchase; } public int affordableCount() { return this.getAvailableMeat() / this.getPrice(); } public boolean isAccessible() { return true; } /** * Executes the purchase request. This calculates the number of items * which will be purchased and adds it to the list. Note that it marks * whether or not it's already been run to avoid problems with * repeating the request. */ @Override public void run() { if ( this.limit < 1 || !this.canPurchase() ) { return; } // If the item is not currently recognized, notify user. This // should never happen, since we automatically detect and // register new items in mall stores and in NPC stores. if ( this.item.getItemId() == -1 ) { KoLmafia.updateDisplay( MafiaState.ERROR, "Item not present in KoLmafia database." ); return; } // Make sure we have enough Meat to buy what we want. if ( this.getAvailableMeat() < this.limit * this.getPrice() ) { return; } // Make sure we are wearing the appropriate outfit, if necessary if ( !this.ensureProperAttire() ) { return; } // Now that we're ready, make the purchase! KoLmafia.updateDisplay( "Purchasing " + this.item.getName() + " (" + KoLConstants.COMMA_FORMAT.format( this.limit ) + " @ " + this.getPriceString() + ")..." ); this.initialCount = this.getCurrentCount(); super.run(); } public int getCurrentCount() { return this.item.getCount( KoLConstants.inventory ); } public static final void setUsePriceComparison( final boolean usePriceComparison ) { PurchaseRequest.usePriceComparison = usePriceComparison; } public int compareTo( final PurchaseRequest pr ) { if ( pr == null ) { return -1; } if ( !PurchaseRequest.usePriceComparison ) { int nameComparison = this.item.getName().compareToIgnoreCase( pr.item.getName() ); if ( nameComparison != 0 ) { return nameComparison; } } // Sort first in order of price int thisPrice = this.getPrice(); int thatPrice = pr.getPrice(); if ( thisPrice != thatPrice ) { return thisPrice - thatPrice; } // limit is how many items you can actually buy // sort high to low on limit if ( this.limit != pr.limit ) { return pr.limit - this.limit; } // If limits are equal but quantity is not, one or the other // stores has an artificial limit. Reward those that don't do // that by sorting low to high on quantity. if ( this.quantity != pr.quantity ) { return this.quantity - pr.quantity; } return this.shopName.compareToIgnoreCase( pr.shopName ); } public boolean ensureProperAttire() { return true; } @Override public boolean equals( final Object o ) { return o == null || !( o instanceof PurchaseRequest ) ? false : this.shopName.equals( ( (PurchaseRequest) o ).shopName ) && this.item.getItemId() == ( (PurchaseRequest) o ).item.getItemId(); } @Override public int hashCode() { int hash = 0; hash += this.shopName != null ? this.shopName.hashCode() : 0; hash += this.item != null ? this.item.hashCode() : 0; return hash; } public static boolean registerRequest( final String urlString ) { if ( urlString.startsWith( "mallstore.php" ) ) { return MallPurchaseRequest.registerRequest( urlString ); } if ( urlString.startsWith( "town_giftshop.php" ) ) { return NPCPurchaseRequest.registerRequest( urlString ); } if ( urlString.startsWith( "shop.php" ) ) { return NPCPurchaseRequest.registerShopRequest( urlString, false ); } return false; } }