/** * 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.webui; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.FamiliarData; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.Modifiers; import net.sourceforge.kolmafia.SpecialOutfit; import net.sourceforge.kolmafia.moods.MoodManager; import net.sourceforge.kolmafia.objectpool.EffectPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.objectpool.FamiliarPool; import net.sourceforge.kolmafia.persistence.ConsumablesDatabase; import net.sourceforge.kolmafia.persistence.EffectDatabase; import net.sourceforge.kolmafia.persistence.ItemFinder; import net.sourceforge.kolmafia.persistence.MallPriceDatabase; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.BasementRequest; import net.sourceforge.kolmafia.session.EquipmentManager; import net.sourceforge.kolmafia.session.InventoryManager; import net.sourceforge.kolmafia.session.StoreManager; import net.sourceforge.kolmafia.utilities.StringUtilities; public class BasementDecorator { public static final void decorate( final StringBuffer buffer ) { addBasementButtons( buffer ); if ( buffer.indexOf( "Got Silk?" ) != -1 ) { BasementDecorator.addBasementChoiceSpoilers( buffer, "Moxie", "Muscle" ); } else if ( buffer.indexOf( "Save the Dolls" ) != -1 ) { BasementDecorator.addBasementChoiceSpoilers( buffer, "Mysticality", "Moxie" ); } else if ( buffer.indexOf( "Take the Red Pill" ) != -1 ) { BasementDecorator.addBasementChoiceSpoilers( buffer, "Muscle", "Mysticality" ); } addBasementSpoilers( buffer ); } private static final void addBasementButtons( final StringBuffer buffer ) { if ( !Preferences.getBoolean( "relayAddsCustomCombat" ) ) { return; } int insertionPoint = buffer.indexOf( "<tr" ); if ( insertionPoint == -1 ) { return; } StringBuffer actionBuffer = new StringBuffer( "<tr><td align=left>" ); StringBuilder autoAction = new StringBuilder(); autoAction.append( "document.location.href='basement.php?action=" ); autoAction.append( BasementRequest.getBasementAction( buffer.toString() ) ); autoAction.append( "'; void(0);" ); BasementDecorator.addBasementButton( "auto", autoAction.toString(), actionBuffer, true ); BasementDecorator.addBasementButton( "rebuff", "runBasementScript(); void(0);", actionBuffer, false ); BasementDecorator.addBasementButton( "refresh", "document.location.href='basement.php'; void(0);", actionBuffer, true ); actionBuffer.append( "</td></tr><tr><td><font size=1> </font></td></tr>" ); buffer.insert( insertionPoint, actionBuffer.toString() ); } private static final void addBasementButton( final String label, final String action, final StringBuffer buffer, final boolean isEnabled ) { buffer.append( "<input type=\"button\" onClick=\"" ); buffer.append( action ); buffer.append( "\" value=\"" ); buffer.append( label ); buffer.append( "\"" ); if ( !isEnabled ) { buffer.append( " disabled" ); } buffer.append( "> " ); } public static final void addBasementSpoilers( final StringBuffer buffer ) { buffer.insert( buffer.indexOf( "</head>" ), "<script language=\"Javascript\" src=\"/" + KoLConstants.BASEMENT_JS + "\"></script></head>" ); StringBuffer changes = new StringBuffer(); changes.append( "<table id=\"basementhelper\" style=\"width:100%;\">" ); changes.append( "<tr><td style=\"width:90%;\"><select id=\"gear\" style=\"width: 100%;\"><option value=\"none\">- change your equipment -</option>" ); // Add outfits. Skip the "No Change" entry at index 0. List outfits = EquipmentManager.getCustomOutfits(); int count = outfits.size(); for ( int i = 1; i < count; ++i ) { SpecialOutfit outfit = (SpecialOutfit) outfits.get( i ); changes.append( "<option value=\"outfit " ); changes.append( outfit.getName() ); changes.append( "\">outfit " ); changes.append( outfit.getName() ); changes.append( "</option>" ); } for ( Iterator i = KoLCharacter.getFamiliarList().iterator(); i.hasNext(); ) { FamiliarData fam = (FamiliarData) i.next(); boolean useful = false; switch ( fam.getId() ) { case FamiliarPool.HAND: case FamiliarPool.SANDWORM: case FamiliarPool.PARROT: case FamiliarPool.PRESSIE: case FamiliarPool.RIFTLET: case FamiliarPool.GIBBERER: case FamiliarPool.HARE: useful = true; break; case FamiliarPool.SOMBRERO: useful = !KoLCharacter.getFamiliarList().contains( BasementRequest.SANDWORM ); break; } if ( fam.hasDrop() ) { useful = fam.dropsToday() < fam.dropDailyCap(); } if ( !useful ) continue; changes.append( "<option value=\"familiar " ); changes.append( fam.getRace() ); changes.append( "\">familiar " ); changes.append( fam.getRace() ); changes.append( "</option>" ); } changes.append( "</select></td><td> </td><td style=\"vertical-align:top; text-align:left;\"><input type=\"button\" value=\"exec\" onClick=\"changeBasementGear();\"></td></tr>" ); // Add effects ArrayList listedEffects = BasementRequest.getStatBoosters(); if ( !listedEffects.isEmpty() ) { String computeFunction = "computeNetBoost(" + BasementRequest.getBasementTestCurrent() + "," + BasementRequest.getBasementTestValue() + ");"; changes.append( "<tr><td style=\"width:90%;\"><select onchange=\"" ); changes.append( computeFunction ); changes.append( "\" id=\"potion\" style=\"width: 100%;\" multiple size=5>" ); if ( KoLCharacter.getCurrentHP() < KoLCharacter.getMaximumHP() ) { if ( KoLCharacter.hasSkill( "Cannelloni Cocoon" ) ) { changes.append( "<option value=0>cast Cannelloni Cocoon (hp restore)</option>" ); } else { changes.append( "<option value=0>use 1 scroll of drastic healing (hp restore)</option>" ); } } if ( KoLCharacter.getCurrentMP() < KoLCharacter.getMaximumMP() ) { changes.append( "<option value=0" ); if ( KoLCharacter.getFullness() == KoLCharacter.getFullnessLimit() ) { changes.append( " disabled" ); } changes.append( ">eat 1 Jumbo Dr. Lucifer (mp restore)</option>" ); } for ( int i = 0; i < listedEffects.size(); ++i ) { StatBooster booster = (StatBooster) listedEffects.get( i ); BasementDecorator.appendBasementEffect( changes, booster ); } changes.append( "</select></td><td> </td><td style=\"vertical-align:top; text-align:left;\">" ); changes.append( "<input type=\"button\" value=\"exec\" onClick=\"changeBasementEffects();\">" ); changes.append( "<br/><br/><font size=-1><nobr id=\"changevalue\">" ); changes.append( BasementRequest.getBasementTestCurrent() ); changes.append( "</nobr><br/><nobr id=\"changetarget\">" ); changes.append( BasementRequest.getBasementTestValue() ); changes.append( "</nobr></td></tr>" ); } changes.append( "</table>" ); buffer.insert( buffer.indexOf( "</center><blockquote>" ), changes.toString() ); String checkString = BasementRequest.getRequirement(); buffer.insert( buffer.lastIndexOf( "</b>" ) + 4, "<br/>" ); buffer.insert( buffer.lastIndexOf( "<img" ), "<table><tr><td>" ); buffer.insert( buffer.indexOf( ">", buffer.lastIndexOf( "<img" ) ) + 1, "</td><td>    </td><td><font id=\"spoiler\" size=2>" + checkString + "</font></td></tr></table>" ); } private static final void addBasementChoiceSpoilers( final StringBuffer buffer, final String choice1, final String choice2 ) { String text = buffer.toString(); buffer.setLength( 0 ); int index1 = 0, index2; // Add first choice spoiler index2 = text.indexOf( "</form>", index1 ); buffer.append( text.substring( index1, index2 ) ); buffer.append( "<br><font size=-1>(" + choice1 + ")</font><br/></form>" ); index1 = index2 + 7; // Add second choice spoiler index2 = text.indexOf( "</form>", index1 ); buffer.append( text.substring( index1, index2 ) ); buffer.append( "<br><font size=-1>(" + choice2 + ")</font><br/></form>" ); index1 = index2 + 7; // Append remainder of buffer buffer.append( text.substring( index1 ) ); } private static final void appendBasementEffect( final StringBuffer changes, final StatBooster effect ) { changes.append( "<option value=" ); changes.append( effect.getEffectiveBoost() ); if ( effect.disabled() ) { changes.append( " disabled" ); } changes.append( ">" ); if ( !effect.itemAvailable() ) { if ( Preferences.getInteger( "basementMallPrices" ) > 0 ) { changes.append( "acquire (~" ); changes.append( KoLConstants.COMMA_FORMAT.format( effect.getItemPrice() * effect.getItem().getCount() ) ); changes.append( " meat) & " ); } else { changes.append( "acquire & " ); } } else if ( effect.getItem() != null && Preferences.getInteger( "basementMallPrices" ) > 1 ) { changes.append( "(~" ); changes.append( KoLConstants.COMMA_FORMAT.format( effect.getItemPrice() * effect.getItem().getCount() ) ); changes.append( " meat) " ); } changes.append( effect.getAction() ); changes.append( " (" ); String effectName = effect.getName(); if ( effect.getComputedBoost() == 0.0 ) { if ( effectName.equals( EffectDatabase.getEffectName( EffectPool.ASTRAL_SHELL ) ) ) { changes.append( "damage absorption/element resist" ); } else if ( effect.isStatEqualizer() ) { changes.append( "stat equalizer" ); } else if ( effect.isDamageAbsorption() ) { changes.append( "damage absorption" ); } else if ( effect.isElementalImmunity() ) { changes.append( "element immunity" ); } else { changes.append( "element resist" ); } } else { changes.append( "+" ); changes.append( KoLConstants.COMMA_FORMAT.format( effect.getEffectiveBoost() ) ); } changes.append( ")</option>" ); } public static class StatBooster implements Comparable<StatBooster> { private final String name, action; private final int computedBoost; private final int effectiveBoost; private AdventureResult item; private boolean itemAvailable; private int fullness; private int spleen; private int inebriety; private boolean isDamageAbsorption; private boolean isElementalImmunity; private boolean isStatEqualizer; private static boolean moxieControlsMP = false; private static boolean absOfTin = false; private static boolean gnomishHardigness = false; private static boolean gnomishUgnderstanding = false; private static boolean marginallyInsane = false; private static boolean spiritOfRavioli = false; private static boolean wisdomOfTheElderTortoise = false; private static final AdventureResult MOXIE_MAGNET = ItemPool.get( ItemPool.MOXIE_MAGNET, 1 ); private static final AdventureResult TRAVOLTAN_TROUSERS = ItemPool.get( ItemPool.TRAVOLTAN_TROUSERS, 1 ); public StatBooster( final String name ) { this.name = name; this.computedBoost = this.computeBoost(); this.effectiveBoost = this.computedBoost > 0.0 ? this.computedBoost : 0 - this.computedBoost; this.action = this.computedBoost < 0 ? "uneffect " + name : MoodManager.getDefaultAction( "lose_effect", name ); this.item = null; this.itemAvailable = true; this.fullness = 0; this.spleen = 0; this.inebriety = 0; this.isDamageAbsorption = this.name.equals( EffectDatabase.getEffectName( EffectPool.ASTRAL_SHELL ) ) || this.name.equals( EffectDatabase.getEffectName( EffectPool.GHOSTLY_SHELL ) ); this.isElementalImmunity = BasementRequest.isElementalImmunity( this.name ); this.isStatEqualizer = this.name.equals( EffectDatabase.getEffectName( EffectPool.EXPERT_OILINESS ) ) || this.name.equals( EffectDatabase.getEffectName( EffectPool.SLIPPERY_OILINESS ) ) || this.name.equals( EffectDatabase.getEffectName( EffectPool.STABILIZING_OILINESS ) ); if ( this.action.startsWith( "use" ) || this.action.startsWith( "chew" ) || this.action.startsWith( "drink" ) || this.action.startsWith( "eat" ) ) { int index = this.action.indexOf( " " ) + 1; this.item = ItemFinder.getFirstMatchingItem( this.action.substring( index ).trim(), false ); if ( this.item != null ) { this.itemAvailable = InventoryManager.hasItem( this.item ); this.fullness = ConsumablesDatabase.getFullness( item.getName() ); this.spleen = ConsumablesDatabase.getSpleenHit( item.getName() ); this.inebriety = ConsumablesDatabase.getInebriety( item.getName() ); } } } public static final boolean moxieControlsMP() { // With Moxie Magnet, uses Moxie, not Mysticality if ( KoLCharacter.hasEquipped( MOXIE_MAGNET ) ) return true; // Ditto if Travoltan trousers and Mox > Mys if ( KoLCharacter.hasEquipped( TRAVOLTAN_TROUSERS ) ) return KoLCharacter.getAdjustedMoxie() > KoLCharacter.getAdjustedMysticality(); return false; } public final boolean isDamageAbsorption() { return this.isDamageAbsorption; } public final boolean isElementalImmunity() { return this.isElementalImmunity; } public final boolean isStatEqualizer() { return this.isStatEqualizer; } public static void checkSkills() { StatBooster.moxieControlsMP = moxieControlsMP(); StatBooster.absOfTin = KoLCharacter.hasSkill( "Abs of Tin" ); StatBooster.gnomishHardigness = KoLCharacter.hasSkill( "Gnomish Hardigness" ); StatBooster.gnomishUgnderstanding = KoLCharacter.hasSkill( "Cosmic Ugnderstanding" ); StatBooster.marginallyInsane = KoLCharacter.hasSkill( "Marginally Insane" ); StatBooster.spiritOfRavioli = KoLCharacter.hasSkill( "Spirit of Ravioli" ); StatBooster.wisdomOfTheElderTortoise = KoLCharacter.hasSkill( "Wisdom of the Elder Tortoises" ); } @Override public boolean equals( final Object o ) { return o instanceof StatBooster && this.name.equals( ( (StatBooster) o ).name ); } @Override public int hashCode() { return this.name != null ? this.name.hashCode() : 0; } public int compareTo( final StatBooster o ) { if ( this.effectiveBoost == 0.0 ) { if ( ( (StatBooster) o ).effectiveBoost != 0.0 ) { return -1; } if ( this.isElementalImmunity ) { return -1; } if ( ( (StatBooster) o ).isElementalImmunity ) { return 1; } return this.name.compareToIgnoreCase( ( (StatBooster) o ).name ); } if ( ( (StatBooster) o ).effectiveBoost == 0.0 ) { return 1; } if ( this.effectiveBoost != ( (StatBooster) o ).effectiveBoost ) { return this.effectiveBoost > ( (StatBooster) o ).effectiveBoost ? -1 : 1; } return this.name.compareToIgnoreCase( ( (StatBooster) o ).name ); } public String getName() { return name; } public AdventureResult getItem() { return item; } public int getItemPrice() { if ( this.item == null ) return 0; if ( MallPriceDatabase.getAge( this.item.getItemId() ) > 7.0 ) { StoreManager.getMallPrice( this.item ); } return MallPriceDatabase.getPrice( this.item.getItemId() ); } public boolean itemAvailable() { return itemAvailable; } public int getFullness() { return spleen; } public int getSpleen() { return spleen; } public int getInebriety() { return inebriety; } public boolean disabled() { if ( item == null ) { if ( this.action.startsWith( "concert " ) && Preferences.getBoolean( "concertVisited" ) ) { return true; } if ( this.action.startsWith( "telescope " ) && Preferences.getBoolean( "telescopeLookedHigh" ) ) { return true; } return false; } if ( this.fullness > 0 && ( KoLCharacter.getFullness() + this.fullness ) > KoLCharacter.getFullnessLimit() ) { return true; } if ( this.spleen > 0 && ( KoLCharacter.getSpleenUse() + this.spleen ) > KoLCharacter.getSpleenLimit() ) { return true; } if ( this.inebriety > 0 && ( KoLCharacter.getInebriety() + this.inebriety ) > KoLCharacter.getInebrietyLimit() ) { return true; } return false; } public int getComputedBoost() { return computedBoost; } public int getEffectiveBoost() { return effectiveBoost; } public String getAction() { return action; } public int computeBoost() { Modifiers m = Modifiers.getModifiers( "Effect", this.name ); if ( m == null ) { return 0; } if ( BasementRequest.getActualStatNeeded() == Modifiers.HP ) { return StatBooster.boostMaxHP( m ); } if ( BasementRequest.getActualStatNeeded() == Modifiers.MP ) { return StatBooster.boostMaxMP( m ); } double base = StatBooster.getEqualizedStat( BasementRequest.getPrimaryBoost() ); double boost = m.get( BasementRequest.getSecondaryBoost() ) + m.get( BasementRequest.getPrimaryBoost() ) * base / 100.0; return (int) Math.ceil( boost ); } public static double getEqualizedStat( final int mod ) { double currentStat = 0.0; switch ( mod ) { case Modifiers.MUS_PCT: currentStat = KoLCharacter.getBaseMuscle(); break; case Modifiers.MYS_PCT: currentStat = KoLCharacter.getBaseMysticality(); break; case Modifiers.MOX_PCT: currentStat = KoLCharacter.getBaseMoxie(); break; default: return 0.0; } if ( KoLConstants.activeEffects.contains( BasementRequest.MUS_EQUAL ) ) { currentStat = Math.max( KoLCharacter.getBaseMuscle(), currentStat ); } if ( KoLConstants.activeEffects.contains( BasementRequest.MYS_EQUAL ) ) { currentStat = Math.max( KoLCharacter.getBaseMysticality(), currentStat ); } if ( KoLConstants.activeEffects.contains( BasementRequest.MOX_EQUAL ) ) { currentStat = Math.max( KoLCharacter.getBaseMoxie(), currentStat ); } return currentStat; } public static int boostMaxHP( final Modifiers m ) { double addedMuscleFixed = m.get( Modifiers.MUS ); double addedMusclePercent = m.get( Modifiers.MUS_PCT ); int addedHealthFixed = (int) m.get( Modifiers.HP ); if ( addedMuscleFixed == 0.0 && addedMusclePercent == 0.0 && addedHealthFixed == 0 ) { return 0; } double muscleBase = StatBooster.getEqualizedStat( Modifiers.MUS_PCT ); double muscleBonus = addedMuscleFixed + Math.floor( addedMusclePercent * muscleBase / 100.0 ); double muscleMultiplicator = 1.0; if ( KoLCharacter.isMuscleClass() ) { muscleMultiplicator += 0.5; } if ( StatBooster.absOfTin ) { muscleMultiplicator += 0.10; } if ( StatBooster.gnomishHardigness ) { muscleMultiplicator += 0.05; } if ( StatBooster.spiritOfRavioli ) { muscleMultiplicator += 0.25; } return (int) Math.ceil( muscleBonus * muscleMultiplicator ) + addedHealthFixed; } public static int boostMaxMP( final Modifiers m ) { int statModifier; int statPercentModifier; if ( StatBooster.moxieControlsMP ) { statModifier = Modifiers.MOX; statPercentModifier = Modifiers.MOX_PCT; } else { statModifier = Modifiers.MYS; statPercentModifier = Modifiers.MYS_PCT; } double addedStatFixed = m.get( statModifier ); double addedStatPercent = m.get( statPercentModifier ); int addedManaFixed = (int) m.get( Modifiers.MP ); if ( addedStatFixed == 0.0 && addedStatPercent == 0.0 && addedManaFixed == 0.0 ) { return 0; } double statBase = StatBooster.getEqualizedStat( statPercentModifier ); double manaBonus = addedStatFixed + addedStatPercent * statBase / 100.0 ; double manaMultiplicator = 1.0; if ( KoLCharacter.isMysticalityClass() ) { manaMultiplicator += 0.5; } if ( StatBooster.gnomishUgnderstanding ) { manaMultiplicator += 0.05; } if ( StatBooster.marginallyInsane ) { manaMultiplicator += 0.1; } if ( StatBooster.wisdomOfTheElderTortoise ) { manaMultiplicator += 0.5; } return (int) Math.ceil( manaBonus * manaMultiplicator ) + addedManaFixed; } } }