/**
* 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.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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;
import net.sourceforge.kolmafia.RequestLogger;
import net.sourceforge.kolmafia.objectpool.ItemPool;
import net.sourceforge.kolmafia.persistence.ItemDatabase;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.session.ContactManager;
import net.sourceforge.kolmafia.session.DisplayCaseManager;
import net.sourceforge.kolmafia.session.ResultProcessor;
import net.sourceforge.kolmafia.utilities.AdventureResultArray;
import net.sourceforge.kolmafia.utilities.StringUtilities;
public abstract class TransferItemRequest
extends GenericRequest
{
public static final Pattern ITEMID_PATTERN = Pattern.compile( "item[^=&]*\\d*=([-\\d]+)" );
public static final Pattern HOWMANY_PATTERN = Pattern.compile( "howmany\\d*=(\\d+)" );
public static final Pattern QTY_PATTERN = Pattern.compile( "qty\\d*=([\\d]+)" );
public static final Pattern QUANTITY_PATTERN = Pattern.compile( "quantity\\d*=([\\d,]+)" );
public static final Pattern RECIPIENT_PATTERN = Pattern.compile( "towho=([^=&]+)" );
private static boolean hadSendMessageFailure = false;
private static boolean updateDisplayOnFailure = true;
public AdventureResult[] attachments;
public List source = KoLConstants.inventory;
public List destination = new ArrayList();
public boolean isSubInstance = false;
public TransferItemRequest( final String formSource )
{
super( formSource );
this.attachments = new AdventureResult[ 0 ];
}
public TransferItemRequest( final String formSource, final AdventureResult attachment )
{
this( formSource );
this.attachments = new AdventureResult[ 1 ];
this.attachments[ 0 ] = attachment;
}
public TransferItemRequest( final String formSource, final AdventureResult[] attachments )
{
this( formSource );
this.attachments = attachments;
}
public void attachItem( final AdventureResult item, final int index )
{
String which, quantity;
if ( this.getCapacity() > 1 )
{
which = this.getItemField() + index;
quantity = this.getQuantityField() + index;
}
else if ( this.alwaysIndex() )
{
which = this.getItemField() + "1";
quantity = this.getQuantityField() + "1";
}
else
{
which = this.getItemField();
quantity = this.getQuantityField();
}
this.addFormField( which, String.valueOf( item.getItemId() ) );
this.addFormField( quantity, String.valueOf( item.getCount() ) );
}
public boolean alwaysIndex()
{
return false;
}
public boolean forceGETMethod()
{
return false;
}
public abstract String getItemField();
public abstract String getQuantityField();
public abstract String getMeatField();
public abstract int getCapacity();
public abstract TransferItemRequest getSubInstance( AdventureResult[] attachments );
public abstract String getStatusMessage();
private void runSubInstances()
{
// Generate the subinstances. Subclasses can override how this
// is done. The first request will have the Meat, if any.
ArrayList subinstances = this.generateSubInstances();
// Run the subinstances.
TransferItemRequest[] requests = new TransferItemRequest[ subinstances.size() ];
subinstances.toArray( requests );
String status = this.getStatusMessage();
for ( int i = 0; i < requests.length; ++i )
{
if ( requests.length == 1 )
{
KoLmafia.updateDisplay( status + "..." );
}
else
{
KoLmafia.updateDisplay( status + " (request " + ( i + 1 ) + " of " + requests.length + ")..." );
}
requests[ i ].run();
}
}
public ArrayList generateSubInstances()
{
ArrayList<TransferItemRequest> subinstances = new ArrayList<TransferItemRequest>();
if ( KoLmafia.refusesContinue() )
{
return subinstances;
}
boolean allowNoDisplay = this.allowUndisplayableTransfer();
boolean allowNoGift = this.allowUngiftableTransfer();
boolean allowSingleton = this.allowSingletonTransfer();
boolean allowNoTrade = this.allowUntradeableTransfer();
boolean allowMemento = !Preferences.getBoolean( "mementoListActive" ) || this.allowMementoTransfer();
int capacity = this.getCapacity();
int meatAttachment = 0;
AdventureResultArray nextAttachments = new AdventureResultArray();
int index = 0;
while ( index < this.attachments.length )
{
nextAttachments.clear();
do
{
AdventureResult item = this.attachments[ index++ ];
if ( item == null )
{
continue;
}
if ( item.getName().equals( AdventureResult.MEAT ) )
{
meatAttachment += item.getCount();
continue;
}
if ( !allowNoDisplay && ItemDatabase.isQuestItem( item.getItemId() ) )
{
continue;
}
if ( !allowNoGift && !ItemDatabase.isGiftable( item.getItemId() ) )
{
continue;
}
if ( !allowNoTrade && !ItemDatabase.isTradeable( item.getItemId() ) )
{
continue;
}
if ( !allowMemento && KoLConstants.mementoList.contains( item ) )
{
continue;
}
int availableCount = item.getCount( this.source );
if ( !allowSingleton && KoLConstants.singletonList.contains( item ) )
{
availableCount = TransferItemRequest.keepSingleton( item, availableCount );
}
if ( availableCount <= 0 )
{
continue;
}
nextAttachments.add( item.getInstance( Math.min( item.getCount(), availableCount ) ) );
}
while ( index < this.attachments.length && nextAttachments.size() < capacity );
// For each broken-up request, create a new request
// which has the appropriate data to post.
if ( !nextAttachments.isEmpty() )
{
TransferItemRequest subinstance = this.getSubInstance( nextAttachments.toArray() );
subinstance.isSubInstance = true;
subinstances.add( subinstance );
}
}
if ( subinstances.size() == 0 )
{
// This can only happen if we are sending no items
this.isSubInstance = true;
subinstances.add( this );
}
if ( meatAttachment > 0 )
{
// Attach all the Meat to the first request
TransferItemRequest first = (TransferItemRequest) subinstances.get(0);
first.addFormField( this.getMeatField(), String.valueOf( meatAttachment ) );
}
return subinstances;
}
public static int keepSingleton( final AdventureResult item, final int count )
{
// We're doing something dangerous with a singleton item
// If we are wearing the item, that counts as keeping one
if ( KoLCharacter.hasEquipped( item ) )
{
return count;
}
// If there is one in the closet, all is well.
if ( item.getCount( KoLConstants.closet ) > 0 )
{
return count;
}
// Otherwise, make sure at least one remains in inventory.
int icount = item.getCount( KoLConstants.inventory );
return ( count < icount ) ? count : ( icount > 0 ) ? icount - 1 : 0;
}
/**
* Runs the request. Note that this does not report an error if it
* fails; it merely parses the results to see if any gains were made.
*/
@Override
public void run()
{
// First, check to see how many attachments are to be
// transferred. If there are too many, then you'll need to
// break up the request
if ( !this.isSubInstance )
{
this.runSubInstances();
return;
}
for ( int i = 1; i <= this.attachments.length; ++i )
{
AdventureResult it = (AdventureResult) this.attachments[ i - 1 ];
if ( it != null && it.isItem() )
{
this.attachItem( it, i );
}
}
// Once all the form fields are broken up, this
// just calls the normal run method from GenericRequest
// to execute the request.
TransferItemRequest.hadSendMessageFailure = false;
if ( this.forceGETMethod() )
{
this.constructURLString( this.getFullURLString(), false );
}
super.run();
}
@Override
public void processResults()
{
if ( this.parseTransfer() )
{
return;
}
TransferItemRequest.hadSendMessageFailure = true;
if ( !TransferItemRequest.updateDisplayOnFailure )
{
return;
}
for ( int i = 0; i < this.attachments.length; ++i )
{
AdventureResult item = (AdventureResult) this.attachments[ i ];
KoLmafia.updateDisplay( MafiaState.ERROR,
"Transfer failed for " + item.toString() );
}
int totalMeat = StringUtilities.parseInt( this.getFormField( this.getMeatField() ) );
if ( totalMeat != 0 )
{
KoLmafia.updateDisplay( MafiaState.ERROR,
"Transfer failed for " + totalMeat + " meat" );
}
}
public abstract boolean parseTransfer();
public static final boolean hadSendMessageFailure()
{
return TransferItemRequest.hadSendMessageFailure;
}
public static final boolean willUpdateDisplayOnFailure()
{
return TransferItemRequest.updateDisplayOnFailure;
}
public static final void setUpdateDisplayOnFailure( final boolean shouldUpdate )
{
TransferItemRequest.updateDisplayOnFailure = shouldUpdate;
}
public abstract boolean allowMementoTransfer();
public boolean allowSingletonTransfer()
{
return true;
}
public abstract boolean allowUntradeableTransfer();
public boolean allowUndisplayableTransfer()
{
return false;
}
public boolean allowUngiftableTransfer()
{
return false;
}
public static final void transferItems( final String urlString,
final List source, final List destination,
final int defaultQuantity )
{
TransferItemRequest.transferItems(
urlString,
TransferItemRequest.ITEMID_PATTERN,
TransferItemRequest.HOWMANY_PATTERN,
source, destination, defaultQuantity );
}
public static final void transferItems( final String urlString,
final Pattern itemPattern, final Pattern quantityPattern,
final List source, final List destination,
final int defaultQuantity )
{
AdventureResultArray itemList = TransferItemRequest.getItemList( urlString, itemPattern, quantityPattern, source, defaultQuantity );
if ( itemList.isEmpty() )
{
return;
}
TransferItemRequest.transferItems( itemList, source, destination );
}
public static final int transferItems( AdventureResultArray itemList,
final List source, final List destination )
{
int count = 0;
for ( int i = 0; i < itemList.size(); ++i )
{
AdventureResult item = itemList.get( i );
count += item.getCount();
if ( source != null )
{
AdventureResult remove = item.getNegation();
if ( source == KoLConstants.inventory )
{
ResultProcessor.processResult( remove );
}
else
{
AdventureResult.addResultToList( source, remove );
}
}
if ( destination == KoLConstants.collection )
{
if ( !KoLConstants.collection.contains( item ) )
{
List shelf = (List) DisplayCaseManager.getShelves().get( 0 );
if ( shelf != null )
{
shelf.add( item );
}
}
AdventureResult.addResultToList( KoLConstants.collection, item );
}
else if ( destination == KoLConstants.inventory )
{
ResultProcessor.processResult( item );
}
else if ( destination != null )
{
AdventureResult.addResultToList( destination, item );
}
}
return count;
}
public static final AdventureResultArray getItemList( final String urlString,
final Pattern itemPattern, final Pattern quantityPattern,
final List source, final int defaultQuantity )
{
AdventureResultArray itemList = new AdventureResultArray();
Matcher itemMatcher = itemPattern.matcher( urlString );
Matcher quantityMatcher = quantityPattern == null ? null : quantityPattern.matcher( urlString );
while ( itemMatcher.find() )
{
int itemId = StringUtilities.parseInt( itemMatcher.group( 1 ) );
String name = ItemDatabase.getItemName( itemId );
// One of the "select" options is a zero value for the
// item id field. Trying to parse it generates an
// exception, so skip it for now.
if ( name == null )
{
continue;
}
int quantity = defaultQuantity;
if ( quantityMatcher != null && quantityMatcher.find() )
{
quantity = StringUtilities.parseInt( quantityMatcher.group( 1 ) );
}
AdventureResult item = ItemPool.get( itemId, quantity );
if ( quantity < 1 )
{
quantity = quantity + item.getCount( source );
}
itemList.add( item.getInstance( quantity ) );
}
return itemList;
}
public static final AdventureResultArray getItemList( final String urlString,
final Pattern itemPattern, final Pattern quantityPattern,
final List source )
{
// Return only items that are on the source list - no default
AdventureResultArray itemList = new AdventureResultArray();
Matcher itemMatcher = itemPattern.matcher( urlString );
Matcher quantityMatcher = quantityPattern == null ? null : quantityPattern.matcher( urlString );
while ( itemMatcher.find() )
{
int itemId = StringUtilities.parseInt( itemMatcher.group( 1 ) );
String name = ItemDatabase.getItemName( itemId );
// One of the "select" options is a zero value for the
// item id field. Trying to parse it generates an
// exception, so skip it for now.
if ( name == null )
{
continue;
}
int quantity = 0;
if ( quantityMatcher != null && quantityMatcher.find() )
{
quantity = StringUtilities.parseInt( quantityMatcher.group( 1 ) );
}
if ( quantity == 0 )
{
continue;
}
AdventureResult item = ItemPool.get( itemId, quantity );
if ( item.getCount( source ) == 0 )
{
continue;
}
itemList.add( item );
}
return itemList;
}
public static final void transferItems( final String responseText,
final Pattern itemPattern,
final List source, final List destination )
{
AdventureResultArray itemList = TransferItemRequest.getItemList( responseText, itemPattern );
if ( itemList.isEmpty() )
{
return;
}
TransferItemRequest.transferItems( itemList, source, destination );
}
public static final Pattern ITEM_PATTERN1 = Pattern.compile( "(.*?) \\((\\d+)\\)" );
public static final Pattern ITEM_PATTERN2 = Pattern.compile( "^(\\d+) ([^,]*)" );
public static final AdventureResultArray getItemList( final String responseText, final Pattern itemPattern )
{
return TransferItemRequest.getItemList( responseText, itemPattern, TransferItemRequest.ITEM_PATTERN1, TransferItemRequest.ITEM_PATTERN2 );
}
public static final AdventureResultArray getItemList( final String responseText, final Pattern outerPattern,
final Pattern innerPattern1, final Pattern innerPattern2 )
{
AdventureResultArray itemList = new AdventureResultArray();
Matcher itemMatcher = outerPattern.matcher( responseText );
while ( itemMatcher.find() )
{
String match = itemMatcher.group( 1 );
TransferItemRequest.getItemCount( itemList, match, innerPattern1 );
TransferItemRequest.getCountItem( itemList, match, innerPattern2 );
}
return itemList;
}
public static final void getItemCount( AdventureResultArray list, String text, final Pattern pattern )
{
if ( pattern == null )
{
return;
}
Matcher m = pattern.matcher( text );
while ( m.find() )
{
String name = m.group( 1 );
int count = StringUtilities.parseInt( m.group( 2 ) );
int itemId = ItemDatabase.getItemId( name, count, true );
AdventureResult item = ItemPool.get( itemId, count );
list.add( item );
}
}
public static final void getCountItem( AdventureResultArray list, String text, final Pattern pattern )
{
if ( pattern == null )
{
return;
}
Matcher m = pattern.matcher( text );
while ( m.find() )
{
String name = m.group( 2 );
int count = StringUtilities.parseInt( m.group( 1 ) );
int itemId = ItemDatabase.getItemId( name, count, true );
AdventureResult item = ItemPool.get( itemId, count );
list.add( item );
}
}
public static final int transferredMeat( final String urlString, final String field )
{
if ( field == null )
{
return 0;
}
Pattern pattern = Pattern.compile( field + "=([\\d,]+)" );
Matcher matcher = pattern.matcher( GenericRequest.decodeField( urlString ) );
if ( !matcher.find() )
{
return 0;
}
return StringUtilities.parseInt( matcher.group(1) );
}
public static final boolean registerRequest( final String command, final String urlString,
final List source, final int defaultQuantity )
{
return TransferItemRequest.registerRequest(
command, urlString, source, defaultQuantity, null );
}
public static final boolean registerRequest( final String command, final String urlString, final List source, final int defaultQuantity, final String meatField )
{
return TransferItemRequest.registerRequest(
command, urlString,
TransferItemRequest.ITEMID_PATTERN,
TransferItemRequest.HOWMANY_PATTERN,
source, defaultQuantity, meatField );
}
public static final boolean registerRequest( final String command, final String urlString,
final Pattern itemPattern, final Pattern quantityPattern,
final List source, final int defaultQuantity )
{
return TransferItemRequest.registerRequest(
command, urlString,
itemPattern, quantityPattern,
source, defaultQuantity, null );
}
public static final boolean registerRequest( final String command, final String urlString,
final Pattern itemPattern, final Pattern quantityPattern,
final List source, final int defaultQuantity,
final String meatField )
{
Matcher recipientMatcher = TransferItemRequest.RECIPIENT_PATTERN.matcher( urlString );
boolean recipients = recipientMatcher.find();
AdventureResultArray itemList = TransferItemRequest.getItemList( urlString, itemPattern, quantityPattern, source, defaultQuantity );
int meat = TransferItemRequest.transferredMeat( urlString, meatField );
if ( !recipients && itemList.isEmpty() && meat == 0 )
{
return false;
}
StringBuilder itemListBuffer = new StringBuilder();
itemListBuffer.append( command );
if ( recipients )
{
itemListBuffer.append( " to " );
itemListBuffer.append( ContactManager.getPlayerName( recipientMatcher.group( 1 ) ) );
}
if ( !itemList.isEmpty() || meat != 0 )
{
itemListBuffer.append( ": " );
}
boolean addedItem = false;
for ( int i = 0; i < itemList.size(); ++i )
{
AdventureResult item = ( (AdventureResult) itemList.get( i ) );
String name = item.getName();
int quantity = item.getCount();
if ( addedItem )
{
itemListBuffer.append( ", " );
}
else
{
addedItem = true;
}
itemListBuffer.append( quantity );
itemListBuffer.append( " " );
itemListBuffer.append( name );
}
if ( meat != 0 )
{
if ( addedItem )
{
itemListBuffer.append( ", " );
}
itemListBuffer.append( meat );
itemListBuffer.append( " Meat" );
}
RequestLogger.updateSessionLog();
RequestLogger.updateSessionLog( itemListBuffer.toString() );
return true;
}
}