/** * 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; import java.io.BufferedReader; import java.io.File; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; import java.util.TimeZone; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.maximizer.Maximizer; import net.sourceforge.kolmafia.objectpool.EffectPool; import net.sourceforge.kolmafia.objectpool.FamiliarPool; import net.sourceforge.kolmafia.objectpool.IntegerPool; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.persistence.AdventureDatabase; import net.sourceforge.kolmafia.persistence.DebugDatabase; import net.sourceforge.kolmafia.persistence.EffectDatabase; import net.sourceforge.kolmafia.persistence.EquipmentDatabase; import net.sourceforge.kolmafia.persistence.FamiliarDatabase; import net.sourceforge.kolmafia.persistence.ItemDatabase; import net.sourceforge.kolmafia.persistence.MonsterDatabase.Element; import net.sourceforge.kolmafia.persistence.SkillDatabase; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.CampgroundRequest; import net.sourceforge.kolmafia.request.CharPaneRequest.Companion; import net.sourceforge.kolmafia.request.EquipmentRequest; import net.sourceforge.kolmafia.request.FloristRequest; import net.sourceforge.kolmafia.request.FloristRequest.Florist; import net.sourceforge.kolmafia.request.UseSkillRequest; import net.sourceforge.kolmafia.utilities.FileUtilities; import net.sourceforge.kolmafia.utilities.LogStream; import net.sourceforge.kolmafia.utilities.StringUtilities; public class Modifiers { private static final HashMap<String, Object> modifiersByName = new HashMap<String, Object>(); private static final HashMap<String,String> familiarEffectByName = new HashMap<String,String>(); private static final ArrayList<UseSkillRequest> passiveSkills = new ArrayList<UseSkillRequest>(); private static final ArrayList synergies = new ArrayList(); private static final ArrayList<String> mutexes = new ArrayList<String>(); public static String currentLocation = ""; public static String currentZone = ""; public static String currentEnvironment = ""; public static double currentML = 4.0; public static String currentFamiliar = ""; public static String mainhandClass = ""; public static double hoboPower = 0.0; public static double smithsness = 0.0; public static double currentWeight = 0.0; public static boolean unarmed = false; private static final Pattern FAMILIAR_EFFECT_PATTERN = Pattern.compile( "Familiar Effect: \"(.*?)\"" ); private static final Pattern FAMILIAR_EFFECT_TRANSLATE_PATTERN = Pattern.compile( "([\\d.]+)\\s*x\\s*(Volley|Somb|Lep|Fairy)" ); private static final String FAMILIAR_EFFECT_TRANSLATE_REPLACEMENT = "$2: $1 "; private static final Pattern FAMILIAR_EFFECT_TRANSLATE_PATTERN2 = Pattern.compile( "cap ([\\d.]+)" ); private static final String FAMILIAR_EFFECT_TRANSLATE_REPLACEMENT2 = "Familiar Weight Cap: $1 "; public static final int FAMILIAR_WEIGHT = 0; public static final int MONSTER_LEVEL = 1; public static final int COMBAT_RATE = 2; public static final int INITIATIVE = 3; public static final int EXPERIENCE = 4; public static final int ITEMDROP = 5; public static final int MEATDROP = 6; public static final int DAMAGE_ABSORPTION = 7; public static final int DAMAGE_REDUCTION = 8; public static final int COLD_RESISTANCE = 9; public static final int HOT_RESISTANCE = 10; public static final int SLEAZE_RESISTANCE = 11; public static final int SPOOKY_RESISTANCE = 12; public static final int STENCH_RESISTANCE = 13; public static final int MANA_COST = 14; public static final int MOX = 15; public static final int MOX_PCT = 16; public static final int MUS = 17; public static final int MUS_PCT = 18; public static final int MYS = 19; public static final int MYS_PCT = 20; public static final int HP = 21; public static final int HP_PCT = 22; public static final int MP = 23; public static final int MP_PCT = 24; public static final int WEAPON_DAMAGE = 25; public static final int RANGED_DAMAGE = 26; public static final int SPELL_DAMAGE = 27; public static final int SPELL_DAMAGE_PCT = 28; public static final int COLD_DAMAGE = 29; public static final int HOT_DAMAGE = 30; public static final int SLEAZE_DAMAGE = 31; public static final int SPOOKY_DAMAGE = 32; public static final int STENCH_DAMAGE = 33; public static final int COLD_SPELL_DAMAGE = 34; public static final int HOT_SPELL_DAMAGE = 35; public static final int SLEAZE_SPELL_DAMAGE = 36; public static final int SPOOKY_SPELL_DAMAGE = 37; public static final int STENCH_SPELL_DAMAGE = 38; public static final int UNDERWATER_COMBAT_RATE = 39; public static final int FUMBLE = 40; public static final int HP_REGEN_MIN = 41; public static final int HP_REGEN_MAX = 42; public static final int MP_REGEN_MIN = 43; public static final int MP_REGEN_MAX = 44; public static final int ADVENTURES = 45; public static final int FAMILIAR_WEIGHT_PCT = 46; public static final int WEAPON_DAMAGE_PCT = 47; public static final int RANGED_DAMAGE_PCT = 48; public static final int STACKABLE_MANA_COST = 49; public static final int HOBO_POWER = 50; public static final int BASE_RESTING_HP = 51; public static final int RESTING_HP_PCT = 52; public static final int BONUS_RESTING_HP = 53; public static final int BASE_RESTING_MP = 54; public static final int RESTING_MP_PCT = 55; public static final int BONUS_RESTING_MP = 56; public static final int CRITICAL_PCT = 57; public static final int PVP_FIGHTS = 58; public static final int VOLLEYBALL_WEIGHT = 59; public static final int SOMBRERO_WEIGHT = 60; public static final int LEPRECHAUN_WEIGHT = 61; public static final int FAIRY_WEIGHT = 62; public static final int MEATDROP_PENALTY = 63; public static final int HIDDEN_FAMILIAR_WEIGHT = 64; public static final int ITEMDROP_PENALTY = 65; public static final int INITIATIVE_PENALTY = 66; public static final int FOODDROP = 67; public static final int BOOZEDROP = 68; public static final int HATDROP = 69; public static final int WEAPONDROP = 70; public static final int OFFHANDDROP = 71; public static final int SHIRTDROP = 72; public static final int PANTSDROP = 73; public static final int ACCESSORYDROP = 74; public static final int VOLLEYBALL_EFFECTIVENESS = 75; public static final int SOMBRERO_EFFECTIVENESS = 76; public static final int LEPRECHAUN_EFFECTIVENESS = 77; public static final int FAIRY_EFFECTIVENESS = 78; public static final int FAMILIAR_WEIGHT_CAP = 79; public static final int SLIME_RESISTANCE = 80; public static final int SLIME_HATES_IT = 81; public static final int SPELL_CRITICAL_PCT = 82; public static final int MUS_EXPERIENCE = 83; public static final int MYS_EXPERIENCE = 84; public static final int MOX_EXPERIENCE = 85; public static final int EFFECT_DURATION = 86; public static final int CANDYDROP = 87; public static final int DB_COMBAT_DAMAGE = 88; public static final int SOMBRERO_BONUS = 89; public static final int FAMILIAR_EXP = 90; public static final int SPORADIC_MEATDROP = 91; public static final int SPORADIC_ITEMDROP = 92; public static final int MEAT_BONUS = 93; public static final int PICKPOCKET_CHANCE = 94; public static final int COMBAT_MANA_COST = 95; public static final int MUS_EXPERIENCE_PCT = 96; public static final int MYS_EXPERIENCE_PCT = 97; public static final int MOX_EXPERIENCE_PCT = 98; public static final int MINSTREL_LEVEL = 99; public static final int MUS_LIMIT = 100; public static final int MYS_LIMIT = 101; public static final int MOX_LIMIT = 102; public static final int SONG_DURATION = 103; public static final int PRISMATIC_DAMAGE = 104; public static final int SMITHSNESS = 105; public static final int SUPERCOLD_RESISTANCE = 106; public static final int REDUCE_ENEMY_DEFENSE = 107; public static final int POOL_SKILL = 108; public static final int SURGEONOSITY = 109; public static final int FAMILIAR_DAMAGE = 110; public static final int GEARDROP = 111; public static final int MAXIMUM_HOOCH = 112; public static final int WATER_LEVEL = 113; public static final int CRIMBOT_POWER = 114; public static final int FAMILIAR_TUNING_MUSCLE = 115; public static final int FAMILIAR_TUNING_MYSTICALITY = 116; public static final int FAMILIAR_TUNING_MOXIE = 117; public static final int RANDOM_MONSTER_MODIFIERS = 118; public static final int LUCK = 119; public static final int OTHELLO_SKILL = 120; public static final int DISCO_STYLE = 121; public static final int ROLLOVER_EFFECT_DURATION = 122; public static final int SIXGUN_DAMAGE = 123; public static final int FISHING_SKILL = 124; public static final int ADDITIONAL_SONG = 125; public static final int SPRINKLES = 126; public static final int ABSORB_ADV = 127; public static final int ABSORB_STAT = 128; public static final String EXPR = "(?:([-+]?[\\d.]+)|\\[([^]]+)\\])"; private static final Object[][] doubleModifiers = { { "Familiar Weight", Pattern.compile( "([+-]\\d+) (to )?Familiar Weight" ), Pattern.compile( "Familiar Weight: " + EXPR ) }, { "Monster Level", new Object[] { Pattern.compile( "([+-]\\d+) to Monster Level" ), Pattern.compile( "Monster Level ([+-]\\d+)" ), }, Pattern.compile( "Monster Level: " + EXPR ) }, { "Combat Rate", null, Pattern.compile( "Combat Rate: " + EXPR ) }, { "Initiative", new Object[] { Pattern.compile( "Combat Initiative ([+-]\\d+)%" ), Pattern.compile( "([+-]\\d+)% Combat Initiative" ), }, Pattern.compile( "Initiative: " + EXPR ) }, { "Experience", Pattern.compile( "([+-]\\d+) Stat.*Per Fight" ), Pattern.compile( "Experience: " + EXPR ) }, { "Item Drop", Pattern.compile( "([+-]\\d+)% Item Drops? [Ff]rom Monsters$" ), Pattern.compile( "Item Drop: " + EXPR ) }, { "Meat Drop", Pattern.compile( "([+-]\\d+)% Meat from Monsters" ), Pattern.compile( "Meat Drop: " + EXPR ) }, { "Damage Absorption", Pattern.compile( "Damage Absorption ([+-]\\d+)" ), Pattern.compile( "Damage Absorption: " + EXPR ) }, { "Damage Reduction", Pattern.compile( "Damage Reduction: ([+-]?\\d+)" ), Pattern.compile( "Damage Reduction: " + EXPR ) }, { "Cold Resistance", null, Pattern.compile( "Cold Resistance: " + EXPR ) }, { "Hot Resistance", null, Pattern.compile( "Hot Resistance: " + EXPR ) }, { "Sleaze Resistance", null, Pattern.compile( "Sleaze Resistance: " + EXPR ) }, { "Spooky Resistance", null, Pattern.compile( "Spooky Resistance: " + EXPR ) }, { "Stench Resistance", null, Pattern.compile( "Stench Resistance: " + EXPR ) }, { "Mana Cost", Pattern.compile( "([+-]\\d+) MP to use Skills$" ), Pattern.compile( "Mana Cost: " + EXPR ) }, { "Moxie", new Object[] { Pattern.compile( "Moxie ([+-]\\d+)$" ), Pattern.compile( "([+-]\\d+) Moxie$" ), }, Pattern.compile( "Moxie: " + EXPR ) }, { "Moxie Percent", new Object[] { Pattern.compile( "Moxie ([+-]\\d+)%" ), Pattern.compile( "([+-]\\d+)% Moxie" ), }, Pattern.compile( "Moxie Percent: " + EXPR ) }, { "Muscle", new Object[] { Pattern.compile( "Muscle ([+-]\\d+)$" ), Pattern.compile( "([+-]\\d+) Muscle$" ), }, Pattern.compile( "Muscle: " + EXPR ) }, { "Muscle Percent", new Object[] { Pattern.compile( "Muscle ([+-]\\d+)%" ), Pattern.compile( "([+-]\\d+)% Muscle" ), }, Pattern.compile( "Muscle Percent: " + EXPR ) }, { "Mysticality", new Object[] { Pattern.compile( "Mysticality ([+-]\\d+)$" ), Pattern.compile( "([+-]\\d+) Mysticality$" ), }, Pattern.compile( "Mysticality: " + EXPR ) }, { "Mysticality Percent", new Object[] { Pattern.compile( "Mysticality ([+-]\\d+)%" ), Pattern.compile( "([+-]\\d+)% Mysticality" ), }, Pattern.compile( "Mysticality Percent: " + EXPR ) }, { "Maximum HP", Pattern.compile( "Maximum HP ([+-]\\d+)$" ), Pattern.compile( "Maximum HP: " + EXPR ) }, { "Maximum HP Percent", Pattern.compile( "Maximum HP ([+-]\\d+)%" ), Pattern.compile( "Maximum HP Percent: " + EXPR ) }, { "Maximum MP", Pattern.compile( "Maximum MP ([+-]\\d+)$" ), Pattern.compile( "Maximum MP: " + EXPR ) }, { "Maximum MP Percent", Pattern.compile( "Maximum MP ([+-]\\d+)%" ), Pattern.compile( "Maximum MP Percent: " + EXPR ) }, { "Weapon Damage", new Object[] { Pattern.compile( "Weapon Damage ([+-]\\d+)$" ), Pattern.compile( "([+-]\\d+) Weapon Damage" ), }, Pattern.compile( "Weapon Damage: " + EXPR ) }, { "Ranged Damage", new Object[] { Pattern.compile( "Ranged Damage ([+-]\\d+)$" ), Pattern.compile( "([+-]\\d+) Ranged Damage" ), }, Pattern.compile( "Ranged Damage: " + EXPR ) }, { "Spell Damage", new Object[] { Pattern.compile( "Spell Damage ([+-]\\d+)$" ), Pattern.compile( "([+-]\\d+) Spell Damage" ), }, Pattern.compile( "(?:^|, )Spell Damage: " + EXPR ) }, { "Spell Damage Percent", new Object[] { Pattern.compile( "Spell Damage ([+-][\\d.]+)%" ), Pattern.compile( "([+-][\\d.]+)% Spell Damage" ), }, Pattern.compile( "Spell Damage Percent: " + EXPR ) }, { "Cold Damage", Pattern.compile( "^([+-]\\d+) <font color=blue>Cold Damage<" ), Pattern.compile( "Cold Damage: " + EXPR ) }, { "Hot Damage", Pattern.compile( "^([+-]\\d+) <font color=red>Hot Damage<" ), Pattern.compile( "Hot Damage: " + EXPR ) }, { "Sleaze Damage", Pattern.compile( "^([+-]\\d+) <font color=blueviolet>Sleaze Damage<" ), Pattern.compile( "Sleaze Damage: " + EXPR ) }, { "Spooky Damage", Pattern.compile( "^([+-]\\d+) <font color=gray>Spooky Damage<" ), Pattern.compile( "Spooky Damage: " + EXPR ) }, { "Stench Damage", Pattern.compile( "^([+-]\\d+) <font color=green>Stench Damage<" ), Pattern.compile( "Stench Damage: " + EXPR ) }, { "Cold Spell Damage", Pattern.compile( "^([+-]\\d+) (Damage )?to <font color=blue>Cold Spells</font>" ), Pattern.compile( "Cold Spell Damage: " + EXPR ) }, { "Hot Spell Damage", Pattern.compile( "^([+-]\\d+) (Damage )?to (<font color=red>)?Hot Spells(</font>)?" ), Pattern.compile( "Hot Spell Damage: " + EXPR ) }, { "Sleaze Spell Damage", Pattern.compile( "^([+-]\\d+) (Damage )?to <font color=blueviolet>Sleaze Spells</font>" ), Pattern.compile( "Sleaze Spell Damage: " + EXPR ) }, { "Spooky Spell Damage", Pattern.compile( "^([+-]\\d+) (Damage )?to <font color=gray>Spooky Spells</font>" ), Pattern.compile( "Spooky Spell Damage: " + EXPR ) }, { "Stench Spell Damage", Pattern.compile( "^([+-]\\d+) (Damage )?to <font color=green>Stench Spells</font>" ), Pattern.compile( "Stench Spell Damage: " + EXPR ) }, { "Underwater Combat Rate", null, Pattern.compile( "Combat Rate \\(Underwater\\): " + EXPR ) }, { "Fumble", Pattern.compile( "(\\d+)x chance of Fumble" ), Pattern.compile( "Fumble: " + EXPR ) }, { "HP Regen Min", null, Pattern.compile( "HP Regen Min: " + EXPR ) }, { "HP Regen Max", null, Pattern.compile( "HP Regen Max: " + EXPR ) }, { "MP Regen Min", null, Pattern.compile( "MP Regen Min: " + EXPR ) }, { "MP Regen Max", null, Pattern.compile( "MP Regen Max: " + EXPR ) }, { "Adventures", Pattern.compile( "([+-]\\d+) Adventure\\(s\\) per day( when equipped)?" ), Pattern.compile( "Adventures: " + EXPR ) }, { "Familiar Weight Percent", Pattern.compile( "([+-]\\d+)% Familiar Weight" ), Pattern.compile( "Familiar Weight Percent: " + EXPR ) }, { "Weapon Damage Percent", Pattern.compile( "Weapon Damage ([+-]\\d+)%" ), Pattern.compile( "Weapon Damage Percent: " + EXPR ) }, { "Ranged Damage Percent", Pattern.compile( "Ranged Damage ([+-]\\d+)%" ), Pattern.compile( "Ranged Damage Percent: " + EXPR ) }, { "Stackable Mana Cost", Pattern.compile( "([+-]\\d+) MP to use Skills$" ), Pattern.compile( "Mana Cost \\(stackable\\): " + EXPR ) }, { "Hobo Power", Pattern.compile( "([+-]\\d+) Hobo Power" ), Pattern.compile( "Hobo Power: " + EXPR ) }, { "Base Resting HP", null, Pattern.compile( "Base Resting HP: " + EXPR ) }, { "Resting HP Percent", null, Pattern.compile( "Resting HP Percent: " + EXPR ) }, { "Bonus Resting HP", null, Pattern.compile( "Bonus Resting HP: " + EXPR ) }, { "Base Resting MP", null, Pattern.compile( "Base Resting MP: " + EXPR ) }, { "Resting MP Percent", null, Pattern.compile( "Resting MP Percent: " + EXPR ) }, { "Bonus Resting MP", null, Pattern.compile( "Bonus Resting MP: " + EXPR ) }, { "Critical Hit Percent", Pattern.compile( "([+-]\\d+)% [Cc]hance of Critical Hit" ), Pattern.compile( "Critical Hit Percent: " + EXPR ) }, { "PvP Fights", Pattern.compile( "([+-]\\d+) PvP [Ff]ight\\(s\\) per day( when equipped)?" ), Pattern.compile( "PvP Fights: " + EXPR ) }, { "Volleyball", null, Pattern.compile( "Volley(?:ball)?: " + EXPR ) }, { "Sombrero", null, Pattern.compile( "Somb(?:rero)?: " + EXPR ) }, { "Leprechaun", null, Pattern.compile( "Lep(?:rechaun)?: " + EXPR ) }, { "Fairy", null, Pattern.compile( "Fairy: " + EXPR ) }, { "Meat Drop Penalty", null, Pattern.compile( "Meat Drop Penalty: " + EXPR ) }, { "Hidden Familiar Weight", null, Pattern.compile( "Familiar Weight \\(hidden\\): " + EXPR ) }, { "Item Drop Penalty", null, Pattern.compile( "Item Drop Penalty: " + EXPR ) }, { "Initiative Penalty", null, Pattern.compile( "Initiative Penalty: " + EXPR ) }, { "Food Drop", Pattern.compile( "([+-]\\d+)% Food Drops? [Ff]rom Monsters$" ), Pattern.compile( "Food Drop: " + EXPR ) }, { "Booze Drop", Pattern.compile( "([+-]\\d+)% Booze Drops? [Ff]rom Monsters$" ), Pattern.compile( "Booze Drop: " + EXPR ) }, { "Hat Drop", Pattern.compile( "([+-]\\d+)% Hat(?:/Pants)? Drops? [Ff]rom Monsters$" ), Pattern.compile( "Hat Drop: " + EXPR ) }, { "Weapon Drop", Pattern.compile( "([+-]\\d+)% Weapon Drops? [Ff]rom Monsters$" ), Pattern.compile( "Weapon Drop: " + EXPR ) }, { "Offhand Drop", Pattern.compile( "([+-]\\d+)% Off-[Hh]and Drops? [Ff]rom Monsters$" ), Pattern.compile( "Offhand Drop: " + EXPR ) }, { "Shirt Drop", Pattern.compile( "([+-]\\d+)% Shirt Drops? [Ff]rom Monsters$" ), Pattern.compile( "Shirt Drop: " + EXPR ) }, { "Pants Drop", Pattern.compile( "([+-]\\d+)% (?:Hat/)?Pants Drops? [Ff]rom Monsters$" ), Pattern.compile( "Pants Drop: " + EXPR ) }, { "Accessory Drop", Pattern.compile( "([+-]\\d+)% Accessory Drops? [Ff]rom Monsters$" ), Pattern.compile( "Accessory Drop: " + EXPR ) }, { "Volleyball Effectiveness", null, Pattern.compile( "Volleyball Effectiveness: " + EXPR ) }, { "Sombrero Effectiveness", null, Pattern.compile( "Sombrero Effectiveness: " + EXPR ) }, { "Leprechaun Effectiveness", null, Pattern.compile( "Leprechaun Effectiveness: " + EXPR ) }, { "Fairy Effectiveness", null, Pattern.compile( "Fairy Effectiveness: " + EXPR ) }, { "Familiar Weight Cap", null, Pattern.compile( "Familiar Weight Cap: " + EXPR ) }, { "Slime Resistance", null, Pattern.compile( "Slime Resistance: " + EXPR ) }, { "Slime Hates It", Pattern.compile( "Slime( Really)? Hates (It|You)" ), Pattern.compile( "Slime Hates It: " + EXPR ) }, { "Spell Critical Percent", Pattern.compile( "([+-]\\d+)% [cC]hance of Spell Critical Hit" ), Pattern.compile( "Spell Critical Percent: " + EXPR ) }, { "Muscle Experience", Pattern.compile( "([+-]\\d+) Muscle Stat.*Per Fight" ), Pattern.compile( "Experience \\(Muscle\\): " + EXPR ), "Experience (Muscle)" }, { "Mysticality Experience", Pattern.compile( "([+-]\\d+) Mysticality Stat.*Per Fight" ), Pattern.compile( "Experience \\(Mysticality\\): " + EXPR ), "Experience (Mysticality)" }, { "Moxie Experience", Pattern.compile( "([+-]\\d+) Moxie Stat.*Per Fight" ), Pattern.compile( "Experience \\(Moxie\\): " + EXPR ), "Experience (Moxie)" }, { "Effect Duration", null, Pattern.compile( "(?:^|, )Effect Duration: " + EXPR ) }, { "Candy Drop", Pattern.compile( "([+-]\\d+)% Candy Drops? [Ff]rom Monsters$" ), Pattern.compile( "Candy Drop: " + EXPR ) }, { "DB Combat Damage", new Object[] { Pattern.compile( "([+-]\\d+) damage to Disco Bandit Combat Skills" ), Pattern.compile( "([+-]\\d+) Disco Bandit Skill Damage" ), }, Pattern.compile( "DB Combat Damage: " + EXPR ) }, { "Sombrero Bonus", Pattern.compile( "([+-]\\d+) lbs?\\. of Sombrero" ), Pattern.compile( "Sombrero Bonus: " + EXPR ) }, { "Familiar Experience", Pattern.compile( "([+-]\\d+) Familiar Experience" ), Pattern.compile( "Experience \\(familiar\\): " + EXPR ), "Experience (familiar)" }, { "Sporadic Meat Drop", null, Pattern.compile( "Meat Drop \\(sporadic\\): " + EXPR ), "Meat Drop (sporadic)" }, { "Sporadic Item Drop", null, Pattern.compile( "Item Drop \\(sporadic\\): " + EXPR ), "Item Drop (sporadic)" }, { "Meat Bonus", null, Pattern.compile( "Meat Bonus: " + EXPR ) }, { "Pickpocket Chance", Pattern.compile( "([+-]\\d+)% Pickpocket Chance" ), Pattern.compile( "Pickpocket Chance: " + EXPR ) }, { "Combat Mana Cost", Pattern.compile( "([+-]\\d+) MP to use Skills \\(in-combat only\\)" ), Pattern.compile( "Mana Cost \\(combat\\): " + EXPR ), "Mana Cost (combat)" }, { "Muscle Experience Percent", Pattern.compile( "([+-]\\d+)% to all Muscle Gains" ), Pattern.compile( "Experience Percent \\(Muscle\\): " + EXPR ), "Experience Percent (Muscle)" }, { "Mysticality Experience Percent", Pattern.compile( "([+-]\\d+)% to all Mysticality Gains" ), Pattern.compile( "Experience Percent \\(Mysticality\\): " + EXPR ), "Experience Percent (Mysticality)" }, { "Moxie Experience Percent", Pattern.compile( "([+-]\\d+)% to all Moxie Gains" ), Pattern.compile( "Experience Percent \\(Moxie\\): " + EXPR ), "Experience Percent (Moxie)" }, { "Minstrel Level", new Object[] { Pattern.compile( "([+-]\\d+) to Minstrel Level" ), Pattern.compile( "Minstrel Level ([+-]\\d+)" ), }, Pattern.compile( "Minstrel Level: " + EXPR ) }, { "Muscle Limit", Pattern.compile( "Base Muscle Limited to (\\d+)" ), Pattern.compile( "Muscle Limit: " + EXPR ) }, { "Mysticality Limit", Pattern.compile( "Base Mysticality Limited to (\\d+)" ), Pattern.compile( "Mysticality Limit: " + EXPR ) }, { "Moxie Limit", Pattern.compile( "Base Moxie Limited to (\\d+)" ), Pattern.compile( "Moxie Limit: " + EXPR ) }, { "Song Duration", Pattern.compile( "Song Duration: ([+-]\\d+) Adventures" ), Pattern.compile( "Song Duration: " + EXPR ) }, { "Prismatic Damage", null, null, }, { "Smithsness", Pattern.compile( "([+-]\\d+) Smithsness" ), Pattern.compile( "Smithsness: " + EXPR ) }, { "Supercold Resistance", null, Pattern.compile( "Supercold Resistance: " + EXPR ) }, { "Reduce Enemy Defense", Pattern.compile( "Reduce enemy defense by (\\d+)%" ), Pattern.compile( "Reduce Enemy Defense: " + EXPR ) }, { "Pool Skill", Pattern.compile( "([+-]\\d+) Pool Skill" ), Pattern.compile( "Pool Skill: " + EXPR ) }, { "Surgeonosity", null, Pattern.compile( "Surgeonosity: (\\+?\\d+)" ) }, { "Familiar Damage", new Object[] { Pattern.compile( "([+-]\\d+) to Familiar Damage" ), Pattern.compile( "Familiar Damage ([+-]\\d+)" ), }, Pattern.compile( "Familiar Damage: " + EXPR ) }, { "Gear Drop", Pattern.compile( "([+-]\\d+)% Gear Drops? [Ff]rom Monsters$" ), Pattern.compile( "Gear Drop: " + EXPR ) }, { "Maximum Hooch", Pattern.compile( "([+-]\\d+) Maximum Hooch" ), Pattern.compile( "Maximum Hooch: " + EXPR ) }, { "Water Level", null, Pattern.compile( "Water Level: " + EXPR ) }, { "Crimbot Outfit Power", Pattern.compile( "([+-]\\d+) Crimbot Outfit Power" ), Pattern.compile( "Crimbot Outfit Power: " + EXPR ) }, { "Familiar Tuning Muscle", null, Pattern.compile( "Familiar Tuning \\(Muscle\\): " + EXPR ), "Familiar Tuning (Muscle)" }, { "Familiar Tuning Mysticality", null, Pattern.compile( "Familiar Tuning \\(Mysticality\\): " + EXPR ), "Familiar Tuning (Mysticality)" }, { "Familiar Tuning Moxie", null, Pattern.compile( "Familiar Tuning \\(Moxie\\): " + EXPR ), "Familiar Tuning (Moxie)" }, { "Random Monster Modifiers", Pattern.compile( "([+-]\\d+) Random Monster Modifier" ), Pattern.compile( "Random Monster Modifiers: " + EXPR ) }, { "Luck", Pattern.compile( "([+-]\\d+) Luck" ), Pattern.compile( "Luck: " + EXPR ) }, { "Othello Skill", Pattern.compile( "([+-]\\d+) Othello Skill" ), Pattern.compile( "Othello Skill: " + EXPR ) }, { "Disco Style", Pattern.compile( "([+-]\\d+) Disco Style" ), Pattern.compile( "Disco Style: " + EXPR ) }, { "Rollover Effect Duration", Pattern.compile( "Grants (\\d+) Adventures of <b>.*?</b> at Rollover" ), Pattern.compile( "Rollover Effect Duration: " + EXPR ) }, { "Sixgun Damage", null, Pattern.compile( "Sixgun Damage: " + EXPR ) }, { "Fishing Skill", Pattern.compile( "([+-]\\d+) Fishing Skill" ), Pattern.compile( "Fishing Skill: " + EXPR ) }, { "Additional Song", Pattern.compile( "Keep (\\d+) additional song in your head" ), Pattern.compile( "Additional Song: " +EXPR ) }, { "Sprinkle Drop", Pattern.compile( "([+-]\\d+)% Sprinkles from Monsters" ), Pattern.compile( "Sprinkle Drop: " + EXPR ) }, { "Absorb Adventures", Pattern.compile( "([+-]\\d+) Adventures when you absorb an item" ), Pattern.compile( "Absorb Adventures: " + EXPR ) }, { "Absorb Stats", Pattern.compile( "([+-]\\d+) Stats when you absorb an item" ), Pattern.compile( "Absorb Stats: " + EXPR ) }, }; public static final int DOUBLE_MODIFIERS = Modifiers.doubleModifiers.length; private static final HashSet<String> numericModifiers = new HashSet<String>(); static { for ( Object[] modifier : Modifiers.doubleModifiers ) { String tag = modifier.length > 3 ? (String) modifier[ 3 ] : (String) modifier[ 0 ]; Modifiers.numericModifiers.add( tag ); } }; public static boolean isNumericModifier( final String key ) { return Modifiers.numericModifiers.contains( key ); } public static final int BOOLEANS = 0; public static final int CLOWNOSITY = 1; public static final int BRIMSTONE = 2; public static final int CLOATHING = 3; public static final int SYNERGETIC = 4; public static final int RAVEOSITY = 5; public static final int MUTEX = 6; public static final int MUTEX_VIOLATIONS = 7; private static final Object[][] bitmapModifiers = { { "(booleans)", null, null }, { "Clownosity", null, Pattern.compile( "Clownosity: (\\+?\\d+)" ) }, { "Brimstone", null, Pattern.compile( "Brimstone" ) }, { "Cloathing", null, Pattern.compile( "Cloathing" ) }, { "Synergetic", null, Pattern.compile( "Synergetic" ) }, { "Raveosity", null, Pattern.compile( "Raveosity: (\\+?\\d+)" ) }, { "Mutually Exclusive", null, null }, { "Mutex Violations", null, null }, }; public static final int BITMAP_MODIFIERS = Modifiers.bitmapModifiers.length; private static final int[] bitmapMasks = new int[ BITMAP_MODIFIERS ]; static { Arrays.fill( bitmapMasks, 1 ); }; public static final int SOFTCORE = 0; public static final int SINGLE = 1; public static final int NEVER_FUMBLE = 2; public static final int WEAKENS = 3; public static final int FREE_PULL = 4; public static final int VARIABLE = 5; public static final int NONSTACKABLE_WATCH = 6; public static final int COLD_IMMUNITY = 7; public static final int HOT_IMMUNITY = 8; public static final int SLEAZE_IMMUNITY = 9; public static final int SPOOKY_IMMUNITY = 10; public static final int STENCH_IMMUNITY = 11; public static final int COLD_VULNERABILITY = 12; public static final int HOT_VULNERABILITY = 13; public static final int SLEAZE_VULNERABILITY = 14; public static final int SPOOKY_VULNERABILITY = 15; public static final int STENCH_VULNERABILITY = 16; public static final int MOXIE_CONTROLS_MP = 17; public static final int MOXIE_MAY_CONTROL_MP = 18; public static final int FOUR_SONGS = 19; public static final int ADVENTURE_UNDERWATER = 20; public static final int UNDERWATER_FAMILIAR = 21; public static final int GENERIC = 22; public static final int UNARMED = 23; public static final int NOPULL = 24; public static final int LASTS_ONE_DAY = 25; public static final int ATTACKS_CANT_MISS = 26; public static final int LOOK_LIKE_A_PIRATE = 27; public static final int BREAKABLE = 28; private static final Object[][] booleanModifiers = { { "Softcore Only", Pattern.compile( "This item cannot be equipped while in Hardcore" ), Pattern.compile( "Softcore Only" ) }, { "Single Equip", null, Pattern.compile( "Single Equip" ) }, { "Never Fumble", Pattern.compile( "Never Fumble" ), Pattern.compile( "Never Fumble" ) }, { "Weakens Monster", Pattern.compile( "Successful hit weakens opponent" ), Pattern.compile( "Weakens Monster" ) }, { "Free Pull", null, Pattern.compile( "Free Pull" ) }, { "Variable", null, Pattern.compile( "Variable" ) }, { "Nonstackable Watch", null, Pattern.compile( "Nonstackable Watch" ) }, { "Cold Immunity", null, Pattern.compile( "Cold Immunity" ) }, { "Hot Immunity", null, Pattern.compile( "Hot Immunity" ) }, { "Sleaze Immunity", null, Pattern.compile( "Sleaze Immunity" ) }, { "Spooky Immunity", null, Pattern.compile( "Spooky Immunity" ) }, { "Stench Immunity", null, Pattern.compile( "Stench Immunity" ) }, { "Cold Vulnerability", null, Pattern.compile( "Cold Vulnerability" ) }, { "Hot Vulnerability", null, Pattern.compile( "Hot Vulnerability" ) }, { "Sleaze Vulnerability", null, Pattern.compile( "Sleaze Vulnerability" ) }, { "Spooky Vulnerability", null, Pattern.compile( "Spooky Vulnerability" ) }, { "Stench Vulnerability", null, Pattern.compile( "Stench Vulnerability" ) }, { "Moxie Controls MP", null, Pattern.compile( "Moxie Controls MP" ) }, { "Moxie May Control MP", null, Pattern.compile( "Moxie May Control MP" ) }, { "Four Songs", Pattern.compile( "Allows you to keep 4 songs in your head instead of 3" ), Pattern.compile( "Four Songs" ) }, { "Adventure Underwater", Pattern.compile( "Lets you [bB]reathe [uU]nderwater" ), Pattern.compile( "Adventure Underwater" ) }, { "Underwater Familiar", Pattern.compile( "Lets your Familiar Breathe Underwater" ), Pattern.compile( "Underwater Familiar" ) }, { "Generic", null, Pattern.compile( "Generic" ) }, { "Unarmed", Pattern.compile( "Bonus for Unarmed Characters only" ), Pattern.compile( "Unarmed" ) }, { "No Pull", null, Pattern.compile( "No Pull" ) }, { "Lasts Until Rollover", Pattern.compile( "This item will disappear at the end of the day" ), Pattern.compile( "Lasts Until Rollover" ) }, { "Attacks Can't Miss", Pattern.compile( "Regular Attacks Can't Miss" ), Pattern.compile( "Attacks Can't Miss" ) }, { "Pirate", null, Pattern.compile( "Look like a Pirate" ) }, { "Breakable", null, Pattern.compile( "Breakable" ) }, }; public static final int BOOLEAN_MODIFIERS = Modifiers.booleanModifiers.length; static { if ( BOOLEAN_MODIFIERS > 32 ) { KoLmafia.updateDisplay( "Too many boolean modifiers to fit into bitmaps[0]. Will have to store bitmaps as longs, or use two bitmaps to hold the booleans." ); } } public static final int CLASS = 0; public static final int INTRINSIC_EFFECT = 1; public static final int EQUALIZE = 2; public static final int WIKI_NAME = 3; public static final int MODIFIERS = 4; public static final int OUTFIT = 5; public static final int STAT_TUNING = 6; public static final int EFFECT = 7; public static final int EQUIPS_ON = 8; public static final int FAMILIAR_EFFCT = 9; public static final int JIGGLE = 10; public static final int EQUALIZE_MUSCLE = 11; public static final int EQUALIZE_MYST = 12; public static final int EQUALIZE_MOXIE = 13; public static final int AVATAR = 14; public static final int ROLLOVER_EFFECT = 15; public static final int SKILL = 16; private static final Object[][] stringModifiers = { { "Class", new Object[] { Pattern.compile( "Only (.*?) may use this item" ), Pattern.compile( "Bonus for (.*?) only" ), Pattern.compile( "Bonus for (.*?) only" ), }, Pattern.compile( "Class: \"(.*?)\"" ) }, { "Intrinsic Effect", Pattern.compile( "Intrinsic Effect: <a.*?><font color=blue>(.*)</font></a>" ), Pattern.compile( "Intrinsic Effect: \"(.*?)\"" ) }, { "Equalize", null, Pattern.compile( "Equalize: \"(.*?)\"" ) }, { "Wiki Name", null, Pattern.compile( "Wiki Name: \"(.*?)\"" ) }, { "Modifiers", null, Pattern.compile( "^(none)$" ) }, { "Outfit", null, null }, { "Stat Tuning", null, Pattern.compile( "Stat Tuning: \"(.*?)\"" ) }, { "Effect", null, Pattern.compile( "(?:^|, )Effect: \"(.*?)\"" ) }, { "Equips On", null, Pattern.compile( "Equips On: \"(.*?)\"" ) }, { "Familiar Effect", null, Pattern.compile( "Familiar Effect: \"(.*?)\"" ) }, { "Jiggle", Pattern.compile( "Jiggle: *(.*?)$" ), Pattern.compile( "Jiggle: \"(.*?)\"" ) }, { "Equalize Muscle", null, Pattern.compile( "Equalize Muscle: \"(.*?)\"" ) }, { "Equalize Mysticality", null, Pattern.compile( "Equalize Mysticality: \"(.*?)\"" ) }, { "Equalize Moxie", null, Pattern.compile( "Equalize Moxie: \"(.*?)\"" ) }, { "Avatar", new Object[] { Pattern.compile( "Makes you look like (?:a |an |the )?(.++)(?<!doctor|gross doctor)" ), Pattern.compile( "Te hace ver como un (.++)" ), }, Pattern.compile( "Avatar: \"(.*?)\"" ) }, { "Rollover Effect", Pattern.compile( "Adventures of <b><a.*?>(.*)</a></b> at Rollover" ), Pattern.compile( "Rollover Effect: \"(.*?)\"" ) }, { "Skill", Pattern.compile( "Grants Skill:.*?<b>(.*?)</b>" ), Pattern.compile( "Skill: \"(.*?)\"" ) }, }; public static final int STRING_MODIFIERS = Modifiers.stringModifiers.length; // Indexes for array returned by predict(): public static final int BUFFED_MUS = 0; public static final int BUFFED_MYS = 1; public static final int BUFFED_MOX = 2; public static final int BUFFED_HP = 3; public static final int BUFFED_MP = 4; private static final Object[][] derivedModifiers = { { "Buffed Muscle" }, { "Buffed Mysticality" }, { "Buffed Moxie" }, { "Buffed HP Maximum" }, { "Buffed MP Maximum" }, }; public static final int DERIVED_MODIFIERS = Modifiers.derivedModifiers.length; public int[] predict() { int[] rv = new int[ Modifiers.DERIVED_MODIFIERS ]; int mus = KoLCharacter.getBaseMuscle(); int mys = KoLCharacter.getBaseMysticality(); int mox = KoLCharacter.getBaseMoxie(); String equalize = this.getString( Modifiers.EQUALIZE ); if ( equalize.startsWith( "Mus" ) ) { mys = mox = mus; } else if ( equalize.startsWith( "Mys" ) ) { mus = mox = mys; } else if ( equalize.startsWith( "Mox" ) ) { mus = mys = mox; } else if ( equalize.startsWith( "High" ) ) { int high = Math.max( Math.max( mus, mys ), mox ); mus = mys = mox = high; } String mus_equalize = this.getString( Modifiers.EQUALIZE_MUSCLE ); if ( mus_equalize.startsWith( "Mys" ) ) { mus = mys; } else if ( mus_equalize.startsWith( "Mox" ) ) { mus = mox; } String mys_equalize = this.getString( Modifiers.EQUALIZE_MYST ); if ( mys_equalize.startsWith( "Mus" ) ) { mys = mus; } else if ( mys_equalize.startsWith( "Mox" ) ) { mys = mox; } String mox_equalize = this.getString( Modifiers.EQUALIZE_MOXIE ); if ( mox_equalize.startsWith( "Mus" ) ) { mox = mus; } else if ( mox_equalize.startsWith( "Mys" ) ) { mox = mys; } int mus_limit = (int) this.get( Modifiers.MUS_LIMIT ); if ( mus_limit > 0 && mus > mus_limit ) { mus = mus_limit; } int mys_limit = (int) this.get( Modifiers.MYS_LIMIT ); if ( mys_limit > 0 && mys > mys_limit ) { mys = mys_limit; } int mox_limit = (int) this.get( Modifiers.MOX_LIMIT ); if ( mox_limit > 0 && mox > mox_limit ) { mox = mox_limit; } rv[ Modifiers.BUFFED_MUS ] = mus + (int) this.get( Modifiers.MUS ) + (int) Math.ceil( this.get( Modifiers.MUS_PCT ) * mus / 100.0 ); rv[ Modifiers.BUFFED_MYS ] = mys + (int) this.get( Modifiers.MYS ) + (int) Math.ceil( this.get( Modifiers.MYS_PCT ) * mys / 100.0 ); rv[ Modifiers.BUFFED_MOX ] = mox + (int) this.get( Modifiers.MOX ) + (int) Math.ceil( this.get( Modifiers.MOX_PCT ) * mox / 100.0); int hpbase = rv[ Modifiers.BUFFED_MUS ]; double C = KoLCharacter.isMuscleClass() ? 1.5 : 1.0; int hp = (int) Math.ceil( (hpbase + 3) * ( C + this.get( Modifiers.HP_PCT ) / 100.0 ) ) + (int) this.get( Modifiers.HP ); rv[ Modifiers.BUFFED_HP ] = Math.max( hp, mus ); int mpbase = (int) rv[ Modifiers.BUFFED_MYS ]; if ( this.getBoolean( Modifiers.MOXIE_CONTROLS_MP ) || (this.getBoolean( Modifiers.MOXIE_MAY_CONTROL_MP ) && (int) rv[ Modifiers.BUFFED_MOX ] > mpbase) ) { mpbase = (int) rv[ Modifiers.BUFFED_MOX ]; } C = KoLCharacter.isMysticalityClass() ? 1.5 : 1.0; int mp = (int) Math.ceil( mpbase * ( C + this.get( Modifiers.MP_PCT ) / 100.0 ) ) + (int) this.get( Modifiers.MP ); rv[ Modifiers.BUFFED_MP ] = Math.max( mp, mys ); return rv; } public static final Iterator getAllModifiers() { return Modifiers.modifiersByName.keySet().iterator(); } public static final void overrideModifier( String lookup, Object value ) { if ( value != null ) { Modifiers.modifiersByName.put( lookup, value ); } else { Modifiers.modifiersByName.remove( lookup ); } } public static final String getModifierName( final int index ) { return Modifiers.modifierName( Modifiers.doubleModifiers, index ); } public static final String getBitmapModifierName( final int index ) { return Modifiers.modifierName( Modifiers.bitmapModifiers, index ); } public static final String getBooleanModifierName( final int index ) { return Modifiers.modifierName( Modifiers.booleanModifiers, index ); } public static final String getStringModifierName( final int index ) { return Modifiers.modifierName( Modifiers.stringModifiers, index ); } public static final String getDerivedModifierName( final int index ) { return Modifiers.modifierName( Modifiers.derivedModifiers, index ); } private static final String modifierName( final Object[][] table, final int index ) { if ( index < 0 || index >= table.length ) { return null; } return (String) table[ index ][ 0 ]; }; private static final Object modifierDescPattern( final Object[][] table, final int index ) { if ( index < 0 || index >= table.length ) { return null; } return table[ index ][ 1 ]; }; private static final Pattern modifierTagPattern( final Object[][] table, final int index ) { if ( index < 0 || index >= table.length ) { return null; } return (Pattern) table[ index ][ 2 ]; }; private static final String modifierTag( final Object[][] table, final int index ) { if ( index < 0 || index >= table.length ) { return null; } return table[ index ].length > 3 ? (String) table[ index ][ 3 ] : (String) table[ index ][ 0 ]; }; private static final String COLD = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.COLD_RESISTANCE ) + ": "; private static final String HOT = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.HOT_RESISTANCE ) + ": "; private static final String SLEAZE = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.SLEAZE_RESISTANCE ) + ": "; private static final String SPOOKY = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.SPOOKY_RESISTANCE ) + ": "; private static final String STENCH = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.STENCH_RESISTANCE ) + ": "; private static final String SLIME = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.SLIME_RESISTANCE ) + ": "; private static final String SUPERCOLD = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.SUPERCOLD_RESISTANCE ) + ": "; private static final String MOXIE = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.MOX ) + ": "; private static final String MUSCLE = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.MUS ) + ": "; private static final String MYSTICALITY = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.MYS ) + ": "; private static final String MOXIE_PCT = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.MOX_PCT ) + ": "; private static final String MUSCLE_PCT = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.MUS_PCT ) + ": "; private static final String MYSTICALITY_PCT = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.MYS_PCT ) + ": "; private static final String HP_TAG = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.HP ) + ": "; private static final String MP_TAG = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.MP ) + ": "; private static final String HP_REGEN_MIN_TAG = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.HP_REGEN_MIN ) + ": "; private static final String HP_REGEN_MAX_TAG = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.HP_REGEN_MAX ) + ": "; private static final String MP_REGEN_MIN_TAG = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.MP_REGEN_MIN ) + ": "; private static final String MP_REGEN_MAX_TAG = Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.MP_REGEN_MAX ) + ": "; public static int elementalResistance( final Element element ) { switch ( element ) { case COLD: return Modifiers.COLD_RESISTANCE; case HOT: return Modifiers.HOT_RESISTANCE; case SLEAZE: return Modifiers.SLEAZE_RESISTANCE; case SPOOKY: return Modifiers.SPOOKY_RESISTANCE; case STENCH: return Modifiers.STENCH_RESISTANCE; } return -1; } public static ArrayList<AdventureResult> getPotentialChanges( final int index ) { ArrayList<AdventureResult> available = new ArrayList<AdventureResult>(); Modifiers currentTest; Object[] check = Modifiers.modifiersByName.keySet().toArray(); boolean hasEffect; AdventureResult currentEffect; for ( int i = 0; i < check.length; ++i ) { String effectName = ( (String) check[ i ] ).replace( "Effect:", "" ); int effectId = EffectDatabase.getEffectId( effectName ); if ( effectId == -1 ) { continue; } currentTest = Modifiers.getEffectModifiers( effectId ); double value = ( (Modifiers) currentTest ).get( index ); if ( value == 0.0 ) { continue; } currentEffect = EffectPool.get( effectId ); hasEffect = KoLConstants.activeEffects.contains( currentEffect ); if ( value > 0.0 && !hasEffect ) { available.add( currentEffect ); } else if ( value < 0.0 && hasEffect ) { available.add( currentEffect ); } } return available; } private static final int findName( final Object[][] table, final String name ) { for ( int i = 0; i < table.length; ++i ) { if ( name.equalsIgnoreCase( (String) table[ i ][ 0 ] ) ) { return i; } } return -1; }; public static final int findName( String name ) { return Modifiers.findName( Modifiers.doubleModifiers, name ); } public static final int findBooleanName( String name ) { return Modifiers.findName( Modifiers.booleanModifiers, name ); } private String name; public boolean variable; private final double[] doubles; private final int[] bitmaps; private final String[] strings; private ModifierExpression[] expressions; // These are used for Steely-Eyed Squint and so on private final double[] extras; public Modifiers() { this.variable = false; this.doubles = new double[ Modifiers.DOUBLE_MODIFIERS ]; this.bitmaps = new int[ Modifiers.BITMAP_MODIFIERS ]; this.strings = new String[ Modifiers.STRING_MODIFIERS ]; this.extras = new double[ Modifiers.STENCH_SPELL_DAMAGE + 1 ]; this.reset(); }; public Modifiers( Modifiers copy ) { this(); this.set( copy ); }; public String getName() { return this.name; }; public final void reset() { Arrays.fill( this.doubles, 0.0 ); Arrays.fill( this.bitmaps, 0 ); Arrays.fill( this.strings, "" ); this.expressions = null; }; private double derivePrismaticDamage() { double damage = this.doubles[ Modifiers.COLD_DAMAGE ]; damage = Math.min( damage, this.doubles[ Modifiers.HOT_DAMAGE ] ); damage = Math.min( damage, this.doubles[ Modifiers.SLEAZE_DAMAGE ] ); damage = Math.min( damage, this.doubles[ Modifiers.SPOOKY_DAMAGE ] ); damage = Math.min( damage, this.doubles[ Modifiers.STENCH_DAMAGE ] ); this.doubles[ Modifiers.PRISMATIC_DAMAGE ] = damage; return damage; }; public double get( final int index ) { if ( index == Modifiers.PRISMATIC_DAMAGE ) { return this.derivePrismaticDamage(); } if ( index < 0 || index >= this.doubles.length ) { return 0.0; } return this.doubles[ index ]; }; public double get( final String name ) { if ( name.equals( "Prismatic Damage" ) ) { return this.derivePrismaticDamage(); } int index = Modifiers.findName( Modifiers.doubleModifiers, name ); if ( index < 0 || index >= this.doubles.length ) { index = Modifiers.findName( Modifiers.derivedModifiers, name ); if ( index < 0 || index >= Modifiers.DERIVED_MODIFIERS ) { return this.getBitmap( name ); } return this.predict()[ index ]; } return this.doubles[ index ]; }; public int getRawBitmap( final int index ) { if ( index < 0 || index >= this.bitmaps.length ) { return 0; } return this.bitmaps[ index ]; }; public int getRawBitmap( final String name ) { int index = Modifiers.findName( Modifiers.bitmapModifiers, name ); if ( index < 0 || index >= this.bitmaps.length ) { return 0; } return this.bitmaps[ index ]; }; public int getBitmap( final int index ) { if ( index < 0 || index >= this.bitmaps.length ) { return 0; } int n = this.bitmaps[ index ]; // Count the bits: if ( n == 0 ) return 0; n = ((n & 0xAAAAAAAA) >>> 1) + (n & 0x55555555); n = ((n & 0xCCCCCCCC) >>> 2) + (n & 0x33333333); n = ((n & 0xF0F0F0F0) >>> 4) + (n & 0x0F0F0F0F); n = ((n & 0xFF00FF00) >>> 8) + (n & 0x00FF00FF); n = ((n & 0xFFFF0000) >>> 16) + (n & 0x0000FFFF); return n; }; public int getBitmap( final String name ) { return this.getBitmap( Modifiers.findName( Modifiers.bitmapModifiers, name ) ); }; public boolean getBoolean( final int index ) { if ( index < 0 || index >= Modifiers.BOOLEAN_MODIFIERS ) { return false; } return ((this.bitmaps[ 0 ] >>> index) & 1) != 0; }; public boolean getBoolean( final String name ) { int index = Modifiers.findName( Modifiers.booleanModifiers, name ); if ( index < 0 || index >= Modifiers.BOOLEAN_MODIFIERS ) { return false; } return ((this.bitmaps[ 0 ] >>> index) & 1) != 0; }; public String getString( final int index ) { if ( index < 0 || index >= this.strings.length ) { return ""; } return this.strings[ index ]; }; public String getString( final String name ) { // Can't cache this as expressions can be dependent on things // that can change within a session, like character level. if ( name.equals( "Evaluated Modifiers" ) ) { return Modifiers.evaluateModifiers( this.name, this.strings[ Modifiers.MODIFIERS ] ); } int index = Modifiers.findName( Modifiers.stringModifiers, name ); if ( index < 0 || index >= this.strings.length ) { return ""; } return this.strings[ index ]; }; public double getExtra( final int index ) { if ( index < 0 || index >= this.extras.length ) { return -9999.0; } return this.extras[ index ]; } public double getExtra( final String name ) { // extras uses the same indexes as doubles, so the same lookup will work int index = Modifiers.findName( Modifiers.doubleModifiers, name ); if ( index < 0 || index >= this.extras.length ) { // For now, make it obvious that something went wrong return -9999.0; } return this.extras[ index ]; } public boolean set( final int index, final double mod ) { if ( index < 0 || index >= this.doubles.length ) { return false; } if ( this.doubles[ index ] != mod ) { this.doubles[ index ] = mod; return true; } return false; }; public boolean set( final int index, final int mod ) { if ( index < 0 || index >= this.bitmaps.length ) { return false; } if ( this.bitmaps[ index ] != mod ) { this.bitmaps[ index ] = mod; return true; } return false; }; public boolean set( final int index, final boolean mod ) { if ( index < 0 || index >= Modifiers.BOOLEAN_MODIFIERS ) { return false; } int mask = 1 << index; int val = mod ? mask : 0; if ( (this.bitmaps[ 0 ] & mask) != val ) { this.bitmaps[ 0 ] ^= mask; return true; } return false; }; public boolean set( final int index, String mod ) { if ( index < 0 || index >= this.strings.length ) { return false; } if ( mod == null ) { mod = ""; } if ( !mod.equals( this.strings[ index ] ) ) { this.strings[ index ] = mod; return true; } return false; }; public boolean set( final Modifiers mods ) { if ( mods == null ) { return false; } boolean changed = false; this.name = mods.name; double[] copyDoubles = mods.doubles; for ( int index = 0; index < this.doubles.length; ++index ) { if ( this.doubles[ index ] != copyDoubles[ index ] ) { this.doubles[ index ] = copyDoubles[ index ]; changed = true; } } int[] copyBitmaps = mods.bitmaps; for ( int index = 0; index < this.bitmaps.length; ++index ) { if ( this.bitmaps[ index ] != copyBitmaps[ index ] ) { this.bitmaps[ index ] = copyBitmaps[ index ]; changed = true; } } String[] copyStrings = mods.strings; for ( int index = 0; index < this.strings.length; ++index ) { if ( !this.strings[ index ].equals( copyStrings[ index ] ) ) { this.strings[ index ] = copyStrings[ index ]; changed = true; } } return changed; } public void add( final int index, final double mod, final String desc ) { switch ( index ) { case COMBAT_RATE: // Combat Rate has diminishing returns beyond + or - 25% // Assume that all the sources of Combat Rate modifiers are of + or - 5%, // and start by obtaining the current value without the diminishing returns taken into account double rate = this.doubles[ index ]; double extra = Math.abs( rate ) - 25.0; if ( extra > 0.0 ) { rate = ( 25.0 + Math.ceil( extra ) * 5.0 ) * ( rate < 0.0 ? -1.0 : 1.0 ); } // Add mod and calculate the new value with the diminishing returns taken into account rate += mod; extra = Math.abs( rate ) - 25.0; if ( extra > 0.0 ) { rate = ( 25.0 + Math.ceil( extra / 5.0 ) ) * ( rate < 0.0 ? -1.0 : 1.0 ); } this.doubles[ index ] = rate; break; case MANA_COST: // Total Mana Cost reduction cannot exceed 3 this.doubles[ index ] += mod; if ( this.doubles[ index ] < -3 ) { this.doubles[ index ] = -3; } break; case FAMILIAR_WEIGHT_PCT: // The three current sources of -wt% do not stack if ( this.doubles[ index ] > mod ) { this.doubles[ index ] = mod; } break; case MUS_LIMIT: case MYS_LIMIT: case MOX_LIMIT: // Only the lowest limiter applies if ( ( this.doubles[ index ] == 0.0 || this.doubles[ index ] > mod ) && mod > 0.0 ) { this.doubles[ index ] = mod; } break; case ITEMDROP: String type = Modifiers.getTypeFromLookup( desc ); if ( type.equals( "Bjorn" ) || type.equals( "Effect" ) || type.equals( "Item" ) || type.equals( "Outfit" ) || type.equals( "Sign" ) || type.equals( "Skill" ) || type.equals( "Synergy" ) || type.equals( "Throne" ) ) { String name = Modifiers.getNameFromLookup( desc ); if ( !name.equals( "Steely-Eyed Squint" ) ) { this.extras[ index ] += mod; } } this.doubles[ index ] += mod; break; case INITIATIVE: case HOT_DAMAGE: case COLD_DAMAGE: case STENCH_DAMAGE: case SPOOKY_DAMAGE: case SLEAZE_DAMAGE: case HOT_SPELL_DAMAGE: case COLD_SPELL_DAMAGE: case STENCH_SPELL_DAMAGE: case SPOOKY_SPELL_DAMAGE: case SLEAZE_SPELL_DAMAGE: String name = Modifiers.getNameFromLookup( desc ); if ( !name.equals( "Bendin' Hell" ) && !name.equals( "Bow-Legged Swagger" ) ) { this.extras[ index ] += mod; } this.doubles[ index ] += mod; break; default: this.doubles[ index ] += mod; break; } }; public void add( final Modifiers mods ) { if ( mods == null ) { return; } // Make sure the modifiers apply to current class String type = mods.strings[ Modifiers.CLASS ]; if ( type != "" && !type.equals( KoLCharacter.getClassType() ) ) { return; } // Unarmed modifiers apply only if the character has no weapon or offhand boolean unarmed = mods.getBoolean( Modifiers.UNARMED ); if ( unarmed && !Modifiers.unarmed ) { return; } String name = mods.name; // Add in the double modifiers double[] addition = mods.doubles; for ( int i = 0; i < this.doubles.length; ++i ) { if ( addition[ i ] != 0.0 ) { if ( i == Modifiers.ADVENTURES && (mods.bitmaps[ 0 ] & this.bitmaps[ 0 ] & (1 << Modifiers.NONSTACKABLE_WATCH)) != 0 ) { continue; } this.add( i, addition[ i ], name ); } } // Add in string modifiers as appropriate. String val; val = mods.strings[ Modifiers.EQUALIZE ]; if ( !val.equals( "" ) && this.strings[ Modifiers.EQUALIZE ].equals( "" ) ) { this.strings[ Modifiers.EQUALIZE ] = val; } val = mods.strings[ Modifiers.INTRINSIC_EFFECT ]; if ( !val.equals( "" ) ) { String prev = this.strings[ INTRINSIC_EFFECT ]; if ( prev.equals( "" ) ) { this.strings[ Modifiers.INTRINSIC_EFFECT ] = val; } else { this.strings[ Modifiers.INTRINSIC_EFFECT ] = prev + "\t" + val; } } val = mods.strings[ Modifiers.STAT_TUNING ]; if ( !val.equals( "" ) ) { this.strings[ Modifiers.STAT_TUNING ] = val; } val = mods.strings[ Modifiers.EQUALIZE_MUSCLE ]; if ( !val.equals( "" ) ) { this.strings[ Modifiers.EQUALIZE_MUSCLE ] = val; } val = mods.strings[ Modifiers.EQUALIZE_MYST ]; if ( !val.equals( "" ) ) { this.strings[ Modifiers.EQUALIZE_MYST ] = val; } val = mods.strings[ Modifiers.EQUALIZE_MOXIE ]; if ( !val.equals( "" ) ) { this.strings[ Modifiers.EQUALIZE_MOXIE ] = val; } // OR in the bitmap modifiers (including all the boolean modifiers) this.bitmaps[ Modifiers.MUTEX_VIOLATIONS ] |= this.bitmaps[ Modifiers.MUTEX ] & mods.bitmaps[ Modifiers.MUTEX ]; for ( int i = 0; i < this.bitmaps.length; ++i ) { this.bitmaps[ i ] |= mods.bitmaps[ i ]; } } public static final Modifiers getItemModifiers( final int id ) { if ( id <= 0 ) { return null; } String name = "[" + id + "]"; return Modifiers.getModifiers( "Item", name ); } public static final Modifiers getEffectModifiers( final int id ) { if ( id <= 0 ) { return null; } String name = "[" + id + "]"; return Modifiers.getModifiers( "Effect", name ); } public static final Modifiers getModifiers( String type, final String name ) { String changeType = null; if ( name == null || name.equals( "" ) ) { return null; } if ( type.equals( "Bjorn" ) ) { changeType = type; type = "Throne"; } String lookup = Modifiers.getLookupName( type, name ); Object modifier = Modifiers.modifiersByName.get( lookup ); if ( modifier == null ) { return null; } if ( modifier instanceof Modifiers ) { Modifiers mods = (Modifiers) modifier; if ( mods.variable ) { mods.override( lookup ); if ( changeType != null ) { mods.name = changeType + ":" + name; } } return mods; } if ( !( modifier instanceof String ) ) { return null; } Modifiers newMods = Modifiers.parseModifiers( lookup, (String) modifier ); if ( changeType != null ) { newMods.name = changeType + ":" + name; } newMods.variable = newMods.override( lookup ) || type.equals( "Loc" ) || type.equals( "Zone" ); Modifiers.modifiersByName.put( lookup, newMods ); return newMods; } public final static Modifiers parseModifiers( final String lookup, final String string ) { Modifiers newMods = new Modifiers(); double[] newDoubles = newMods.doubles; int[] newBitmaps = newMods.bitmaps; String[] newStrings = newMods.strings; newMods.name = lookup; String name = Modifiers.getNameFromLookup( lookup ); for ( int i = 0; i < newDoubles.length; ++i ) { Pattern pattern = Modifiers.modifierTagPattern( Modifiers.doubleModifiers, i ); if ( pattern == null ) { continue; } Matcher matcher = pattern.matcher( string ); if ( !matcher.find() ) { continue; } if ( matcher.group( 1 ) != null ) { newDoubles[ i ] = Double.parseDouble( matcher.group( 1 ) ); } else { if ( newMods.expressions == null ) { newMods.expressions = new ModifierExpression[ Modifiers.DOUBLE_MODIFIERS ]; } newMods.expressions[ i ] = ModifierExpression.getInstance( matcher.group( 2 ), lookup ); } } for ( int i = 0; i < newBitmaps.length; ++i ) { Pattern pattern = Modifiers.modifierTagPattern( Modifiers.bitmapModifiers, i ); if ( pattern == null ) { continue; } Matcher matcher = pattern.matcher( string ); if ( !matcher.find() ) { continue; } int bitcount = 1; if ( matcher.groupCount() > 0 ) { bitcount = StringUtilities.parseInt( matcher.group( 1 ) ); } int mask = Modifiers.bitmapMasks[ i ]; switch ( bitcount ) { case 1: Modifiers.bitmapMasks[ i ] <<= 1; break; case 2: mask |= mask << 1; Modifiers.bitmapMasks[ i ] <<= 2; break; default: KoLmafia.updateDisplay( "ERROR: invalid count for bitmap modifier in " + lookup ); continue; } if ( Modifiers.bitmapMasks[ i ] == 0 ) { KoLmafia.updateDisplay( "ERROR: too many sources for bitmap modifier " + Modifiers.modifierName( Modifiers.bitmapModifiers, i ) + ", consider using longs." ); } newBitmaps[ i ] |= mask; } for ( int i = 0; i < Modifiers.BOOLEAN_MODIFIERS; ++i ) { Pattern pattern = Modifiers.modifierTagPattern( Modifiers.booleanModifiers, i ); if ( pattern == null ) { continue; } Matcher matcher = pattern.matcher( string ); if ( !matcher.find() ) { continue; } newBitmaps[ 0 ] |= 1 << i; } for ( int i = 0; i < newStrings.length; ++i ) { Pattern pattern = Modifiers.modifierTagPattern( Modifiers.stringModifiers, i ); if ( pattern == null ) { continue; } Matcher matcher = pattern.matcher( string ); if ( !matcher.find() ) { continue; } String key = Modifiers.modifierName( Modifiers.stringModifiers, i ); String value = matcher.group( 1 ); if ( key.equals( "Class" ) ) { value = Modifiers.depluralizeClassName( value ); } newStrings[ i ] = value; } newStrings[ Modifiers.MODIFIERS ] = string; return newMods; } private static final String[][] classStrings = { { KoLCharacter.SEAL_CLUBBER, "Seal Clubbers", "Seal Clubbers", }, { KoLCharacter.TURTLE_TAMER, "Turtle Tamers", "Turtle Tamers", }, { KoLCharacter.PASTAMANCER, "Pastamancers", }, { KoLCharacter.SAUCEROR, "Saucerors", }, { KoLCharacter.DISCO_BANDIT, "Disco Bandits", "Disco Bandits", }, { KoLCharacter.ACCORDION_THIEF, "Accordion Thieves", "Accordion Thieves", }, }; private final static String depluralizeClassName( final String string ) { for ( String [] results : Modifiers.classStrings ) { String result = results[ 0 ]; for ( String candidate : results ) { if ( candidate.equals( string ) ) { return result; } } } return string; } public static class Modifier { private final String name; private String value; public Modifier( final String name, final String value ) { this.name = name; this.value = value; } public String getName() { return this.name; } public String getValue() { return this.value; } public void setValue( final String value ) { this.value = value; } public void eval( final String lookup ) { if ( this.value == null ) { return; } int lb = this.value.indexOf( "[" ); if ( lb == -1 ) { return; } int rb = this.value.indexOf( "]" ); if ( rb == -1 ) { return; } ModifierExpression expr = new ModifierExpression( this.value.substring( lb + 1, rb ), lookup ); if ( expr.hasErrors() ) { return; } int val = (int)expr.eval(); this.value = ( val > 0 ? "+" : "" ) + String.valueOf( val ); } public void toString( final StringBuilder buffer ) { buffer.append( name ); if ( value != null ) { buffer.append( ": " ); buffer.append( value ); } } @Override public String toString() { StringBuilder buffer = new StringBuilder(); this.toString( buffer ); return buffer.toString(); } } public static class ModifierList implements Iterable<Modifier> { private final LinkedList<Modifier> list; public ModifierList() { this.list = new LinkedList<Modifier>(); } public Iterator<Modifier> iterator() { return this.list.iterator(); } public void clear() { this.list.clear(); } public int size() { return this.list.size(); } public void addAll( final ModifierList list ) { this.list.addAll( list.list ); } public void addModifier( final Modifier modifier ) { this.list.add( modifier ); } public void addModifier( final String name, final String value ) { this.list.add( new Modifier( name, value ) ); } public boolean containsModifier( final String name ) { for ( Modifier modifier : this.list ) { if ( name.equals( modifier.name ) ) { return true; } } return false; } public String getModifierValue( final String name ) { for ( Modifier modifier : this.list ) { if ( name.equals( modifier.name ) ) { return modifier.value; } } return null; } public Modifier removeModifier( final String name ) { Iterator<Modifier> iterator = this.iterator(); while ( iterator.hasNext() ) { Modifier modifier = iterator.next(); if ( name.equals( modifier.name ) ) { iterator.remove(); return modifier; } } return null; } @Override public String toString() { StringBuilder buffer = new StringBuilder(); for ( Modifier modifier : this.list ) { if ( buffer.length() > 0 ) { buffer.append( ", " ); } modifier.toString( buffer ); } return buffer.toString(); } } public final static ModifierList getModifierList( final String type, final int id ) { String name = "[" + id + "]"; return Modifiers.getModifierList( type, name ); } public final static ModifierList getModifierList( final String type, final String name ) { Modifiers mods = Modifiers.getModifiers( type, name ); if ( mods == null ) { return new ModifierList(); } return Modifiers.splitModifiers( mods.getString( "Modifiers" ) ); } public final static ModifierList splitModifiers( String modifiers ) { ModifierList list = new ModifierList(); // Iterate over string, pulling off modifiers while ( modifiers != null ) { // Moxie: +5, Muscle: +5, Mysticality: +5, Familiar Effect: "1xPotato, 3xGhuol, cap 18" int comma = modifiers.indexOf( "," ); if ( comma != -1 ) { int bracket1 = modifiers.indexOf( "[" ); if ( bracket1 != -1 && bracket1 < comma ) { int bracket2 = modifiers.indexOf( "]", bracket1 + 1 ); if ( bracket2 != -1 ) { comma = modifiers.indexOf( ",", bracket2 + 1 ); } else { // bogus: no close bracket comma = -1; } } else { int quote1 = modifiers.indexOf( "\"" ); if ( quote1 != -1 && quote1 < comma ) { int quote2 = modifiers.indexOf( "\"", quote1 + 1 ); if ( quote2 != -1 ) { comma = modifiers.indexOf( ",", quote2 + 1 ); } else { // bogus: no close quote comma = -1; } } } } String string; if ( comma == -1 ) { string = modifiers; modifiers = null; } else { string = modifiers.substring( 0, comma ).trim(); modifiers = modifiers.substring( comma + 1 ).trim(); } String key, value; // Every pattern for a modifier with a value separates // the key with a colon and a single space. Therefore, // split on precisely that string and trim neither the // key nor the value. int colon = string.indexOf( ": " ); if ( colon == -1 ) { key = string; value = null; } else { key = string.substring( 0, colon ); value = string.substring( colon + 2 ); } list.addModifier( key, value ); } return list; } public final static String evaluateModifiers( final String lookup, final String modifiers ) { // Nothing to do if no expressions if ( !modifiers.contains( "[" ) ) { return modifiers; } // Otherwise, break apart the string and rebuild it with all // expressions evaluated. ModifierList list = Modifiers.splitModifiers( modifiers ); for ( Modifier modifier : list ) { // Evaluate the modifier expression modifier.eval( lookup ); } return list.toString(); } public final static String trimModifiers( final String modifiers, final String remove ) { ModifierList list = Modifiers.splitModifiers( modifiers ); list.removeModifier( remove ); return list.toString(); } private boolean override( final String lookup ) { if ( this.expressions != null ) { for ( int i = 0; i < this.expressions.length; ++i ) { ModifierExpression expr = this.expressions[ i ]; if ( expr != null ) { this.doubles[ i ] = expr.eval(); } } } // If the object does not require hard-coding, we're done if ( !this.getBoolean( Modifiers.VARIABLE ) ) { return this.expressions != null; } String name = Modifiers.getNameFromLookup( lookup ); // We only hardcode items, for now. int itemId = ItemDatabase.getItemId( name ); switch ( itemId ) { case ItemPool.TUESDAYS_RUBY: { // Reset to defaults this.set( Modifiers.MEATDROP, 0.0 ); this.set( Modifiers.ITEMDROP, 0.0 ); this.set( Modifiers.MOX_PCT, 0.0 ); this.set( Modifiers.MUS_PCT, 0.0 ); this.set( Modifiers.MYS_PCT, 0.0 ); this.set( Modifiers.HP_REGEN_MIN, 0.0 ); this.set( Modifiers.HP_REGEN_MAX, 0.0 ); this.set( Modifiers.MP_REGEN_MIN, 0.0 ); this.set( Modifiers.MP_REGEN_MAX, 0.0 ); // Set modifiers depending on what KoL day of the week it is Calendar date = Calendar.getInstance( TimeZone.getTimeZone( "GMT-0700" ) ); switch ( date.get( Calendar.DAY_OF_WEEK ) ) { case Calendar.SUNDAY: // +5% Meat from Monsters this.set( Modifiers.MEATDROP, 5.0 ); break; case Calendar.MONDAY: // Muscle +5% this.set( Modifiers.MUS_PCT, 5.0 ); break; case Calendar.TUESDAY: // Regenerate 3-7 MP per adventure this.set( Modifiers.MP_REGEN_MIN, 3.0 ); this.set( Modifiers.MP_REGEN_MAX, 7.0 ); break; case Calendar.WEDNESDAY: // +5% Mysticality this.set( Modifiers.MYS_PCT, 5.0 ); break; case Calendar.THURSDAY: // +5% Item Drops from Monsters this.set( Modifiers.ITEMDROP, 5.0 ); break; case Calendar.FRIDAY: // +5% Moxie this.set( Modifiers.MOX_PCT, 5.0 ); break; case Calendar.SATURDAY: // Regenerate 3-7 HP per adventure this.set( Modifiers.HP_REGEN_MIN, 3.0 ); this.set( Modifiers.HP_REGEN_MAX, 7.0 ); break; } return true; } case ItemPool.UNCLE_HOBO_BEARD: case ItemPool.GINGERBEARD: { Calendar date = Calendar.getInstance( TimeZone.getTimeZone( "GMT-0700" ) ); double adventures = date.get( Calendar.MONTH ) == Calendar.DECEMBER ? 9.0 : 6.0; this.set( Modifiers.ADVENTURES, adventures ); return true; } case ItemPool.TIME_TWITCHING_TOOLBELT: { this.set( Modifiers.FREE_PULL, Preferences.getBoolean( "timeTowerAvailable" ) ); return true; } case ItemPool.PATRIOT_SHIELD: { // Muscle classes this.set( Modifiers.HP_REGEN_MIN, 0.0 ); this.set( Modifiers.HP_REGEN_MAX, 0.0 ); // Seal clubber this.set( Modifiers.WEAPON_DAMAGE, 0.0 ); this.set( Modifiers.DAMAGE_REDUCTION, 0.0 ); // Turtle Tamer this.set( Modifiers.FAMILIAR_WEIGHT, 0.0 ); // Disco Bandit this.set( Modifiers.RANGED_DAMAGE, 0.0 ); // Accordion Thief this.set( Modifiers.FOUR_SONGS, false ); // Mysticality classes this.set( Modifiers.MP_REGEN_MIN, 0.0 ); this.set( Modifiers.MP_REGEN_MAX, 0.0 ); // Pastamancer this.set( Modifiers.COMBAT_MANA_COST, 0.0 ); // Sauceror this.set( Modifiers.SPELL_DAMAGE, 0.0 ); // Set modifiers depending on Character class String classType = KoLCharacter.getClassType(); if ( classType == KoLCharacter.SEAL_CLUBBER || classType == KoLCharacter.ZOMBIE_MASTER || classType == KoLCharacter.ED || classType == KoLCharacter.COWPUNCHER || classType == KoLCharacter.BEANSLINGER || classType == KoLCharacter.SNAKE_OILER ) { this.set( Modifiers.HP_REGEN_MIN, 10.0 ); this.set( Modifiers.HP_REGEN_MAX, 12.0 ); this.set( Modifiers.WEAPON_DAMAGE, 15.0 ); this.set( Modifiers.DAMAGE_REDUCTION, 1.0 ); } else if ( classType == KoLCharacter.TURTLE_TAMER ) { this.set( Modifiers.HP_REGEN_MIN, 10.0 ); this.set( Modifiers.HP_REGEN_MAX, 12.0 ); this.set( Modifiers.FAMILIAR_WEIGHT, 5.0 ); } else if ( classType == KoLCharacter.DISCO_BANDIT || classType == KoLCharacter.AVATAR_OF_SNEAKY_PETE ) { this.set( Modifiers.RANGED_DAMAGE, 20.0 ); } else if ( classType == KoLCharacter.ACCORDION_THIEF ) { this.set( Modifiers.FOUR_SONGS, true ); } else if ( classType == KoLCharacter.PASTAMANCER ) { this.set( Modifiers.MP_REGEN_MIN, 5.0 ); this.set( Modifiers.MP_REGEN_MAX, 6.0 ); this.set( Modifiers.COMBAT_MANA_COST, -3.0 ); } else if ( classType == KoLCharacter.SAUCEROR || classType == KoLCharacter.AVATAR_OF_JARLSBERG ) { this.set( Modifiers.MP_REGEN_MIN, 5.0 ); this.set( Modifiers.MP_REGEN_MAX, 6.0 ); this.set( Modifiers.SPELL_DAMAGE, 20.0 ); } return true; } } return false; } public final static double getNumericModifier( final String type, final int id, final String mod ) { String name = "[" + id + "]"; return Modifiers.getNumericModifier( type, name, mod ); } public static final double getNumericModifier( final String type, final String name, final String mod ) { Modifiers mods = Modifiers.getModifiers( type, name ); if ( mods == null ) { return 0.0; } return mods.get( mod ); } public static final double getNumericModifier( final FamiliarData fam, final String mod ) { return Modifiers.getNumericModifier( fam, mod, fam.getModifiedWeight( false ), fam.getItem() ); } public static final double getNumericModifier( final FamiliarData fam, final String mod, final int passedWeight, final AdventureResult item ) { int familiarId = fam != null ? fam.getId() : -1; if ( familiarId == -1 ) { return 0.0; } Modifiers.setFamiliar( fam ); int weight = passedWeight; Modifiers tempMods = new Modifiers(); // Mad Hatrack ... hats do not give their normal modifiers // Fancypants Scarecrow ... pants do not give their normal modifiers int itemId = item.getItemId(); int type = ItemDatabase.getConsumptionType( itemId ); if ( ( familiarId != FamiliarPool.HATRACK || type != KoLConstants.EQUIP_HAT ) && ( familiarId != FamiliarPool.SCARECROW || type != KoLConstants.EQUIP_PANTS ) ) { // Add in all the modifiers bestowed by this item tempMods.add( Modifiers.getItemModifiers( itemId ) ); // Apply weight modifiers right now weight += (int) tempMods.get( Modifiers.FAMILIAR_WEIGHT ); weight += (int) tempMods.get( Modifiers.HIDDEN_FAMILIAR_WEIGHT ); weight += ( fam.getFeasted() ? 10 : 0 ); double percent = tempMods.get( Modifiers.FAMILIAR_WEIGHT_PCT ) / 100.0; if ( percent != 0.0 ) { weight = (int) Math.floor( weight + weight * percent ); } } tempMods.lookupFamiliarModifiers( fam, weight, item ); return tempMods.get( mod ); } public final static boolean getBooleanModifier( final String type, final int id, final String mod ) { String name = "[" + id + "]"; return Modifiers.getBooleanModifier( type, name, mod ); } public static final boolean getBooleanModifier( final String type, final String name, final String mod ) { Modifiers mods = Modifiers.getModifiers( type, name ); if ( mods == null ) { return false; } return mods.getBoolean( mod ); } public final static String getStringModifier( final String type, final int id, final String mod ) { String name = "[" + id + "]"; return Modifiers.getStringModifier( type, name, mod ); } public static final String getStringModifier( final String type, final String name, final String mod ) { Modifiers mods = Modifiers.getModifiers( type, name ); if ( mods == null ) { return ""; } return mods.getString( mod ); } public void applyPassiveModifiers() { // You'd think this could be done at class initialization time, // but no: the SkillDatabase depends on the Mana Cost // modifier being set. if ( Modifiers.passiveSkills.isEmpty() ) { Object[] keys = Modifiers.modifiersByName.keySet().toArray(); for ( int i = 0; i < keys.length; ++i ) { String lookup = (String) keys[ i ]; if ( !Modifiers.getTypeFromLookup( lookup ).equals( "Skill" ) ) { continue; } String skill = Modifiers.getNameFromLookup( lookup ); if ( !SkillDatabase.contains( skill ) ) { continue; } if ( SkillDatabase.isPassive( SkillDatabase.getSkillId( skill ) ) ) { Modifiers.passiveSkills.add( UseSkillRequest.getUnmodifiedInstance( skill ) ); } } } for ( int i = Modifiers.passiveSkills.size() - 1; i >= 0; --i ) { UseSkillRequest skill = Modifiers.passiveSkills.get( i ); if ( KoLCharacter.hasSkill( skill ) ) { String name = skill.getSkillName(); this.add( Modifiers.getModifiers( "Skill", name ) ); } } if ( KoLCharacter.getFamiliar().getId() == FamiliarPool.DODECAPEDE && KoLCharacter.hasAmphibianSympathy() ) { this.add( Modifiers.FAMILIAR_WEIGHT, -10, "Familiar:dodecapede sympathy" ); } } public final void applyFloristModifiers() { if ( !FloristRequest.haveFlorist() ) { return; } if ( Modifiers.currentLocation == null ) { return; } ArrayList<Florist> plants = FloristRequest.getPlants( Modifiers.currentLocation ); if ( plants == null ) { return; } for ( Florist plant : plants ) { this.add( Modifiers.getModifiers( "Florist", plant.toString() ) ); } } public void applySynergies() { int synergetic = this.getRawBitmap( Modifiers.SYNERGETIC ); if ( synergetic == 0 ) return; // nothing possible Iterator i = Modifiers.synergies.iterator(); while ( i.hasNext() ) { String name = (String) i.next(); int mask = ((Integer) i.next()).intValue(); if ( (synergetic & mask) == mask ) { this.add( Modifiers.getModifiers( "Synergy", name ) ); } } } // Returned iterator yields alternating names / bitmaps public static Iterator getSynergies() { return Modifiers.synergies.iterator(); } private static final AdventureResult somePigs = EffectPool.get( EffectPool.SOME_PIGS ); public void applyFamiliarModifiers( final FamiliarData familiar, AdventureResult famItem ) { if ( KoLConstants.activeEffects.contains( Modifiers.somePigs ) ) { // Under the effect of SOME PIGS, familiar gives no modifiers return; } int weight = familiar.getUncappedWeight(); weight += (int) this.get( Modifiers.FAMILIAR_WEIGHT ); weight += (int) this.get( Modifiers.HIDDEN_FAMILIAR_WEIGHT ); weight += ( familiar.getFeasted() ? 10 : 0 ); double percent = this.get( Modifiers.FAMILIAR_WEIGHT_PCT ) / 100.0; if ( percent != 0.0 ) { weight = (int) Math.floor( weight + weight * percent ); } weight = Math.max( 1, weight ); this.lookupFamiliarModifiers( familiar, weight, famItem ); } public void lookupFamiliarModifiers( final FamiliarData familiar, int weight, final AdventureResult famItem ) { int familiarId = familiar.getId(); weight = Math.max( 1, weight ); Modifiers.currentWeight = weight; String race = familiar.getRace(); // Comma Chameleon acts as if it was something else if ( familiarId == FamiliarPool.CHAMELEON ) { String newRace = Preferences.getString( "commaFamiliar" ); if ( newRace != null && !newRace.equals( "" ) ) { race = newRace; familiarId = FamiliarDatabase.getFamiliarId( race ); } } this.add( Modifiers.getModifiers( "Familiar", race ) ); if ( famItem != null ) { // "fameq" modifiers are generated when "Familiar Effect" is parsed // from modifiers.txt this.add( Modifiers.getModifiers( "FamEq", famItem.getName() ) ); } int cap = (int)this.get( Modifiers.FAMILIAR_WEIGHT_CAP ); int cappedWeight = ( cap == 0 ) ? weight : Math.min( weight, cap ); double effective = cappedWeight * this.get( Modifiers.VOLLEYBALL_WEIGHT ); if ( effective == 0.0 && FamiliarDatabase.isVolleyType( familiarId ) ) { effective = weight; } if ( effective != 0.0 ) { double factor = this.get( Modifiers.VOLLEYBALL_EFFECTIVENESS ); // The 0->1 factor for generic familiars conflicts with the JitB if ( factor == 0.0 && familiarId != FamiliarPool.JACK_IN_THE_BOX ) factor = 1.0; factor = factor * ( 2 + effective/5 ); double tuning; if ( ( tuning = this.get( Modifiers.FAMILIAR_TUNING_MUSCLE ) ) > 0 ) { double mainstatFactor = tuning / 100; double offstatFactor = ( 1 - mainstatFactor ) / 2; this.add( Modifiers.MUS_EXPERIENCE, factor * mainstatFactor, "Tuned Volleyball:" + race ); this.add( Modifiers.MYS_EXPERIENCE, factor * offstatFactor, "Tuned Volleyball:" + race ); this.add( Modifiers.MOX_EXPERIENCE, factor * offstatFactor, "Tuned Volleyball:" + race ); } else if ( ( tuning = this.get( Modifiers.FAMILIAR_TUNING_MYSTICALITY ) ) > 0 ) { double mainstatFactor = tuning / 100; double offstatFactor = ( 1 - mainstatFactor ) / 2; this.add( Modifiers.MUS_EXPERIENCE, factor * offstatFactor, "Tuned Volleyball:" + race ); this.add( Modifiers.MYS_EXPERIENCE, factor * mainstatFactor, "Tuned Volleyball:" + race ); this.add( Modifiers.MOX_EXPERIENCE, factor * offstatFactor, "Tuned Volleyball:" + race ); } else if ( ( tuning = this.get( Modifiers.FAMILIAR_TUNING_MOXIE ) ) > 0 ) { double mainstatFactor = tuning / 100; double offstatFactor = ( 1 - mainstatFactor ) / 2; this.add( Modifiers.MUS_EXPERIENCE, factor * offstatFactor, "Tuned Volleyball:" + race ); this.add( Modifiers.MYS_EXPERIENCE, factor * offstatFactor, "Tuned Volleyball:" + race ); this.add( Modifiers.MOX_EXPERIENCE, factor * mainstatFactor, "Tuned Volleyball:" + race ); } else { this.add( Modifiers.EXPERIENCE, factor, "Volleyball:" + race ); } } effective = cappedWeight * this.get( Modifiers.SOMBRERO_WEIGHT ); if ( effective == 0.0 && FamiliarDatabase.isSombreroType( familiarId ) ) { effective = weight; } effective += this.get( Modifiers.SOMBRERO_BONUS ); if ( effective != 0.0 ) { double factor = this.get( Modifiers.SOMBRERO_EFFECTIVENESS ); if ( factor == 0.0 ) factor = 1.0; // currentML is always >= 4, so we don't need to check for negatives int maxStats = 230; this.add( Modifiers.EXPERIENCE, Math.min( Math.max( factor * ( Modifiers.currentML / 4 ) * ( 0.1 + 0.005 * effective ), 1 ), maxStats ), "Familiar:" + race ); } effective = cappedWeight * this.get( Modifiers.LEPRECHAUN_WEIGHT ); if ( effective == 0.0 && FamiliarDatabase.isMeatDropType( familiarId ) ) { effective = weight; } if ( effective != 0.0 ) { double factor = this.get( Modifiers.LEPRECHAUN_EFFECTIVENESS ); if ( factor == 0.0 ) factor = 1.0; this.add( Modifiers.MEATDROP, factor * (Math.sqrt( 220 * effective ) + 2 * effective - 6), "Familiar:" + race ); } effective = cappedWeight * this.get( Modifiers.FAIRY_WEIGHT ); if ( effective == 0.0 && FamiliarDatabase.isFairyType( familiarId ) ) { effective = weight; } if ( effective != 0.0 ) { double factor = this.get( Modifiers.FAIRY_EFFECTIVENESS ); // The 0->1 factor for generic familiars conflicts with the JitB if ( factor == 0.0 && familiarId != FamiliarPool.JACK_IN_THE_BOX ) factor = 1.0; this.add( Modifiers.ITEMDROP, factor * (Math.sqrt( 55 * effective ) + effective - 3), "Familiar:" + race ); } switch ( familiarId ) { case FamiliarPool.HATRACK: if ( famItem == EquipmentRequest.UNEQUIP ) { this.add( Modifiers.HATDROP, 50.0, "Familiar:naked hatrack" ); } break; case FamiliarPool.SCARECROW: if ( famItem == EquipmentRequest.UNEQUIP ) { this.add( Modifiers.PANTSDROP, 50.0, "Familiar:naked scarecrow" ); } break; } } public static final String getFamiliarEffect( final String itemName ) { return (String) Modifiers.familiarEffectByName.get( itemName ); } public void applyMinstrelModifiers( final int level, AdventureResult instrument ) { String name = instrument.getName(); Modifiers imods = Modifiers.getModifiers( "Clancy", name ); double effective = imods.get( Modifiers.VOLLEYBALL_WEIGHT ); if ( effective != 0.0 ) { double factor = 2 + effective / 5; this.add( Modifiers.EXPERIENCE, factor, name ); } effective = imods.get( Modifiers.FAIRY_WEIGHT ); if ( effective != 0.0 ) { double factor = Math.sqrt( 55 * effective ) + effective - 3; this.add( Modifiers.ITEMDROP, factor, name ); } this.add( Modifiers.HP_REGEN_MIN, imods.get( Modifiers.HP_REGEN_MIN ), name ); this.add( Modifiers.HP_REGEN_MAX, imods.get( Modifiers.HP_REGEN_MAX ), name ); this.add( Modifiers.MP_REGEN_MIN, imods.get( Modifiers.MP_REGEN_MIN ), name ); this.add( Modifiers.MP_REGEN_MAX, imods.get( Modifiers.MP_REGEN_MAX ), name ); } public void applyCompanionModifiers( Companion companion ) { double multiplier = 1.0; if ( KoLCharacter.hasSkill( "Working Lunch" ) ) { multiplier = 1.5; } switch ( companion ) { case EGGMAN: this.add( Modifiers.ITEMDROP, 50 * multiplier, "Eggman" ); break; case RADISH: this.add( Modifiers.INITIATIVE, 50 * multiplier, "Radish Horse" ); break; case HIPPO: this.add( Modifiers.EXPERIENCE, 3 * multiplier, "Hippotatomous" ); break; case CREAM: this.add( Modifiers.MONSTER_LEVEL, 20 * multiplier, "Cream Puff" ); break; } } public void applyServantModifiers( EdServantData servant ) { int id = servant.getId(); int level = servant.getLevel(); switch ( id ) { case 1: // Cat if ( servant.getLevel() >= 7 ) { this.add( Modifiers.ITEMDROP, Math.sqrt( 55 * level ) + level - 3, "Servant: Cat" ); } break; case 3: // Maid this.add( Modifiers.MEATDROP, Math.sqrt( 220 * level ) + 2 * level - 6, "Servant: Maid" ); break; case 5: // Scribe this.add( Modifiers.EXPERIENCE, 2 + level/5, "Servant: Scribe" ); break; } } public void applyCompanionModifiers( VYKEACompanionData companion ) { int type = companion.getType(); int level = companion.getLevel(); switch ( type ) { case VYKEACompanionData.LAMP: this.add( Modifiers.ITEMDROP, level * 10, "VYKEA Companion: Lamp" ); break; case VYKEACompanionData.COUCH: this.add( Modifiers.MEATDROP, level * 10, "VYKEA Companion: Couch" ); break; } } // Parsing item enchantments into KoLmafia modifiers private static final Pattern SKILL_PATTERN = Pattern.compile( "Grants Skill:.*?<b>(.*?)</b>" ); public static final String parseSkill( final String text ) { Matcher matcher = Modifiers.SKILL_PATTERN.matcher( text ); if ( matcher.find() ) { return Modifiers.modifierTag( Modifiers.stringModifiers, Modifiers.SKILL ) + ": \"" + matcher.group( 1 ) + "\""; } return null; } private static final Pattern DR_PATTERN = Pattern.compile( "Damage Reduction: (<b>)?([+-]?\\d+)(</b>)?" ); public static final String parseDamageReduction( final String text ) { if ( !text.contains( "Damage Reduction:" ) ) { return null; } Matcher matcher = Modifiers.DR_PATTERN.matcher( text ); int dr = 0; while ( matcher.find() ) { dr += StringUtilities.parseInt( matcher.group( 2 ) ); } return Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.DAMAGE_REDUCTION ) + ": " + dr; } private static final Pattern SINGLE_PATTERN = Pattern.compile( "You may not equip more than one of these at a time" ); public static final String parseSingleEquip( final String text ) { Matcher matcher = Modifiers.SINGLE_PATTERN.matcher( text ); if ( matcher.find() ) { return Modifiers.modifierTag( Modifiers.booleanModifiers, Modifiers.SINGLE ); } return null; } private static final Pattern SOFTCORE_PATTERN = Pattern.compile( "This item cannot be equipped while in Hardcore" ); public static final String parseSoftcoreOnly( final String text ) { Matcher matcher = Modifiers.SOFTCORE_PATTERN.matcher( text ); if ( matcher.find() ) { return Modifiers.modifierTag( Modifiers.booleanModifiers, Modifiers.SOFTCORE ); } return null; } private static final Pattern LASTS_ONE_DAY_PATTERN = Pattern.compile( "This item will disappear at the end of the day" ); public static final String parseLastsOneDay( final String text ) { Matcher matcher = Modifiers.LASTS_ONE_DAY_PATTERN.matcher( text ); if ( matcher.find() ) { return Modifiers.modifierTag( Modifiers.booleanModifiers, Modifiers.LASTS_ONE_DAY ); } return null; } private static final Pattern FREE_PULL_PATTERN = Pattern.compile( "Free pull from Hagnk's" ); public static final String parseFreePull( final String text ) { Matcher matcher = Modifiers.FREE_PULL_PATTERN.matcher( text ); if ( matcher.find() ) { return Modifiers.modifierTag( Modifiers.booleanModifiers, Modifiers.FREE_PULL ); } return null; } private static final Pattern EFFECT_PATTERN = Pattern.compile( "Effect: <b><a[^>]*>([^<]*)</a></b>" ); public static final String parseEffect( final String text ) { Matcher matcher = Modifiers.EFFECT_PATTERN.matcher( text ); if ( matcher.find() ) { return Modifiers.modifierTag( Modifiers.stringModifiers, Modifiers.EFFECT ) + ": \"" + matcher.group( 1 ) + "\""; } return null; } private static final Pattern EFFECT_DURATION_PATTERN = Pattern.compile( "</a></b> \\(([\\d]*) Adventures?\\)" ); public static final String parseEffectDuration( final String text ) { Matcher matcher = Modifiers.EFFECT_DURATION_PATTERN.matcher( text ); if ( matcher.find() ) { return Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.EFFECT_DURATION ) + ": " + matcher.group( 1 ); } return null; } private static final Pattern SONG_DURATION_PATTERN = Pattern.compile( "Song Duration: <b>([\\d]*) Adventures</b>" ); public static final String parseSongDuration( final String text ) { Matcher matcher = Modifiers.SONG_DURATION_PATTERN.matcher( text ); if ( matcher.find() ) { return Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.SONG_DURATION ) + ": " + matcher.group( 1 ); } return null; } private static final Pattern ALL_ATTR_PATTERN = Pattern.compile( "^All Attributes ([+-]\\d+)$" ); private static final Pattern ALL_ATTR_PCT_PATTERN = Pattern.compile( "^All Attributes ([+-]\\d+)%$" ); private static final Pattern CLASS_PATTERN = Pattern.compile( "Bonus for (.*) only" ); private static final Pattern COMBAT_PATTERN = Pattern.compile( "Monsters (?:are|will be) (.*) attracted to you" ); private static final Pattern HP_MP_PATTERN = Pattern.compile( "^Maximum HP/MP ([+-]\\d+)$" ); public static final String parseModifier( final String enchantment ) { String result; // Search the double modifiers first result = Modifiers.parseModifier( Modifiers.doubleModifiers, enchantment, false ); if ( result != null ) { return result; } // Then the boolean modifiers result = Modifiers.parseModifier( Modifiers.booleanModifiers, enchantment, false ); if ( result != null ) { return result; } // Then the string modifiers result = Modifiers.parseModifier( Modifiers.stringModifiers, enchantment, true ); if ( result != null ) { return result; } // Special handling needed Matcher matcher; matcher = Modifiers.ALL_ATTR_PATTERN.matcher( enchantment ); if ( matcher.find() ) { String mod = matcher.group( 1 ); return Modifiers.MUSCLE + mod + ", " + Modifiers.MYSTICALITY + mod + ", " + Modifiers.MOXIE + mod; } matcher = Modifiers.ALL_ATTR_PCT_PATTERN.matcher( enchantment ); if ( matcher.find() ) { String mod = matcher.group( 1 ); return Modifiers.MUSCLE_PCT + mod + ", " + Modifiers.MYSTICALITY_PCT + mod + ", " + Modifiers.MOXIE_PCT + mod; } matcher = Modifiers.CLASS_PATTERN.matcher( enchantment ); if ( matcher.find() ) { String plural = matcher.group( 1 ); String cls = "none"; if ( plural.equals( "Accordion Thieves" ) ) { cls = KoLCharacter.ACCORDION_THIEF; } else if ( plural.equals( "Disco Bandits" ) ) { cls = KoLCharacter.DISCO_BANDIT; } else if ( plural.equals( "Pastamancers" ) ) { cls = KoLCharacter.PASTAMANCER; } else if ( plural.equals( "Saucerors" ) ) { cls = KoLCharacter.SAUCEROR; } else if ( plural.equals( "Seal Clubbers" ) ) { cls = KoLCharacter.SEAL_CLUBBER; } else if ( plural.equals( "Turtle Tamers" ) ) { cls = KoLCharacter.TURTLE_TAMER; } else { return null; } return Modifiers.modifierTag( Modifiers.stringModifiers, Modifiers.CLASS ) + ": \"" + cls + "\""; } matcher = Modifiers.COMBAT_PATTERN.matcher( enchantment ); if ( matcher.find() ) { String tag = !enchantment.contains( "Underwater only" ) ? Modifiers.modifierTag( Modifiers.doubleModifiers, Modifiers.COMBAT_RATE ) : "Combat Rate (Underwater)"; String level = matcher.group( 1 ); String rate = level.equals( "more" ) ? "+5" : level.equals( "significantly more" ) ? "+15" : level.equals( "<i>way</i> more" ) ? "+20" : level.equals( "less" ) ? "-5" : level.equals( "significantly less" ) ? "-15" : level.equals( "<i>way</i> less" ) ? "-20" : "+0"; return tag + ": " + rate; } matcher = Modifiers.HP_MP_PATTERN.matcher( enchantment ); if ( matcher.find() ) { String mod = matcher.group( 1 ); return Modifiers.HP_TAG + mod + ", " + Modifiers.MP_TAG + mod; } if ( enchantment.contains( "Regenerate" ) ) { return Modifiers.parseRegeneration( enchantment ); } if ( enchantment.contains( "Resistance" ) ) { return Modifiers.parseResistance( enchantment ); } return null; } public static final String parseStringModifier( final String enchantment ) { return Modifiers.parseModifier( Modifiers.stringModifiers, enchantment, true ); } public static final String parseDoubleModifier( final String enchantment ) { return Modifiers.parseModifier( Modifiers.doubleModifiers, enchantment, false ); } private static final String parseModifier( final Object[][] table, final String enchantment, final boolean quoted ) { String quote = quoted ? "\"" : ""; for ( int i = 0; i < table.length; ++i ) { Object object = Modifiers.modifierDescPattern( table, i ); if ( object == null ) { continue; } Object [] patterns; if ( object instanceof Pattern ) { patterns = new Pattern[1]; patterns[0] = (Pattern) object; } else { patterns = (Object[]) object; } for ( int j = 0; j < patterns.length; ++j ) { Pattern pattern = (Pattern) patterns[ j ]; Matcher matcher = pattern.matcher( enchantment ); if ( !matcher.find() ) { continue; } if ( matcher.groupCount() == 0 ) { return Modifiers.modifierTag( table, i ); } String tag = Modifiers.modifierTag( table, i ); // Kludge for Slime (Really) Hates it if ( tag.equals( "Slime Hates It" ) ) { return matcher.group( 1 ) == null ? "Slime Hates It: +1" : "Slime Hates It: +2"; } String value = matcher.group( 1 ); if ( tag.equals( "Class" ) ) { value = Modifiers.depluralizeClassName( value ); } return tag + ": " + quote + value + quote; } } return null; } private static final Pattern REGEN_PATTERN = Pattern.compile( "Regenerate (\\d*)-?(\\d*)? ([HM]P)( and .*)? per [aA]dventure$" ); private static final String parseRegeneration( final String enchantment ) { Matcher matcher = Modifiers.REGEN_PATTERN.matcher( enchantment ); if ( !matcher.find() ) { return null; } String min = matcher.group( 1 ); String max = matcher.group( 2 ) == null ? min : matcher.group( 2 ); boolean hp = matcher.group( 3 ).equals( "HP" ); boolean both = matcher.group( 4 ) != null; if ( max.equals( "" ) ) { max = min; } if ( both ) { return Modifiers.HP_REGEN_MIN_TAG + min + ", " + Modifiers.HP_REGEN_MAX_TAG + max + ", " + Modifiers.MP_REGEN_MIN_TAG + min + ", " + Modifiers.MP_REGEN_MAX_TAG + max; } if ( hp ) { return Modifiers.HP_REGEN_MIN_TAG + min + ", " + Modifiers.HP_REGEN_MAX_TAG + max; } return Modifiers.MP_REGEN_MIN_TAG + min + ", " + Modifiers.MP_REGEN_MAX_TAG + max; } private static final Pattern RESISTANCE_PATTERN = Pattern.compile( "Resistance \\(([+-]\\d+)\\)" ); private static final String parseResistance( final String enchantment ) { String level = ""; Matcher matcher = RESISTANCE_PATTERN.matcher( enchantment ); if ( matcher.find() ) { level = matcher.group( 1 ); } else if ( enchantment.contains( "Slight" ) ) { level = "+1"; } else if ( enchantment.contains( "So-So" ) ) { level = "+2"; } else if ( enchantment.contains( "Serious" ) ) { level = "+3"; } else if ( enchantment.contains( "Stupendous" ) ) { level = "+4"; } else if ( enchantment.contains( "Superhuman" ) ) { level = "+5"; } else if ( enchantment.contains( "Stunning" ) ) { level = "+7"; } else if ( enchantment.contains( "Sublime" ) ) { level = "+9"; } if ( enchantment.contains( "All Elements" ) ) { return Modifiers.SPOOKY + level + ", " + Modifiers.STENCH + level + ", " + Modifiers.HOT + level + ", " + Modifiers.COLD + level + ", " + Modifiers.SLEAZE + level; } if ( enchantment.contains( "Spooky" ) ) { return Modifiers.SPOOKY + level; } if ( enchantment.contains( "Stench" ) ) { return Modifiers.STENCH + level; } if ( enchantment.contains( "Hot" ) ) { return Modifiers.HOT + level; } if ( enchantment.contains( "Cold" ) ) { return Modifiers.COLD + level; } if ( enchantment.contains( "Sleaze" ) ) { return Modifiers.SLEAZE + level; } if ( enchantment.contains( "Slime" ) ) { return Modifiers.SLIME + level; } if ( enchantment.contains( "Supercold" ) ) { return Modifiers.SUPERCOLD + level; } return null; } private static final boolean findModifier( final Object[][] table, final String tag ) { for ( int i = 0; i < table.length; ++i ) { Pattern pattern = Modifiers.modifierTagPattern( table, i ); if ( pattern == null ) { continue; } Matcher matcher = pattern.matcher( tag ); if ( matcher.find() ) { return true; } } return false; } public static final void checkModifiers() { for ( String lookup : Modifiers.modifiersByName.keySet() ) { Object modifiers = Modifiers.modifiersByName.get( lookup ); if ( modifiers == null ) { RequestLogger.printLine( "Key \"" + lookup + "\" has no modifiers" ); continue; } String modifierString = ( modifiers instanceof Modifiers ) ? ((Modifiers) modifiers).getString( Modifiers.MODIFIERS ) : ( modifiers instanceof String ) ? (String) modifiers : null; if ( modifierString == null ) { RequestLogger.printLine( "Key \"" + lookup + "\" has bogus modifiers of class " + modifiers.getClass().toString() ); continue; } ModifierList list = Modifiers.splitModifiers( modifierString ); for ( Modifier modifier : list ) { String mod = modifier.toString(); if ( Modifiers.findModifier( Modifiers.doubleModifiers, mod ) ) { continue; } if ( Modifiers.findModifier( Modifiers.bitmapModifiers, mod ) ) { continue; } if ( Modifiers.findModifier( Modifiers.booleanModifiers, mod ) ) { continue; } if ( Modifiers.findModifier( Modifiers.stringModifiers, mod ) ) { continue; } if ( lookup.startsWith( "FamEq:" ) ) { continue; // these may contain freeform text } RequestLogger.printLine( "Key \"" + lookup + "\" has unknown modifier: \"" + mod + "\"" ); } } } public static void setLocation( KoLAdventure location ) { if ( location == null ) { Modifiers.currentLocation = ""; Modifiers.currentZone = ""; Modifiers.currentML = 4.0; return; } Modifiers.currentLocation = location.getAdventureName(); Modifiers.currentZone = location.getZone(); Modifiers.currentEnvironment = location.getEnvironment(); AreaCombatData data = location.getAreaSummary(); Modifiers.currentML = Math.max( 4.0, data == null ? 0.0 : data.getAverageML() ); } public static double getCurrentML() { return Modifiers.currentML; } public static void setFamiliar( FamiliarData fam ) { Modifiers.currentFamiliar = fam == null ? "" : fam.getRace(); } public static String getLookupName( final String type, final String name ) { if ( type.equals( "Item" ) ) { int itemId = ItemDatabase.getItemId( name ); if ( itemId >= 0 ) { return "Item:[" + itemId + "]"; } } if ( type.equals( "Effect" ) ) { int effectId = EffectDatabase.getEffectId( name ); if ( effectId >= 0 ) { return "Effect:[" + effectId + "]"; } } return type + ":" + name; } public static String getTypeFromLookup( final String lookup ) { int index = lookup.indexOf( ":" ); if ( index != -1 ) { return lookup.substring( 0, index ); } return ""; } public static String getNameFromLookup( final String lookup ) { int index = lookup.indexOf( ":" ); if ( index != -1 ) { return lookup.substring( index + 1 ); } return lookup; } static { BufferedReader reader = FileUtilities.getVersionedReader( "modifiers.txt", KoLConstants.MODIFIERS_VERSION ); String[] data; loop: while ( ( data = FileUtilities.readData( reader ) ) != null ) { if ( data.length != 3 ) { continue; } String type = data[ 0 ]; String name = data[ 1 ]; String lookup = Modifiers.getLookupName( type, name ); if ( Modifiers.modifiersByName.containsKey( lookup ) ) { KoLmafia.updateDisplay( "Duplicate modifiers for: " + lookup ); } String modifiers = new String( data[ 2 ] ); Modifiers.modifiersByName.put( lookup, modifiers ); Matcher matcher = FAMILIAR_EFFECT_PATTERN.matcher( modifiers ); if ( matcher.find() ) { String effect = matcher.group( 1 ); Modifiers.familiarEffectByName.put( name, effect ); matcher = FAMILIAR_EFFECT_TRANSLATE_PATTERN.matcher( effect ); if ( matcher.find() ) { effect = matcher.replaceAll( FAMILIAR_EFFECT_TRANSLATE_REPLACEMENT ); } matcher = FAMILIAR_EFFECT_TRANSLATE_PATTERN2.matcher( effect ); if ( matcher.find() ) { effect = matcher.replaceAll( FAMILIAR_EFFECT_TRANSLATE_REPLACEMENT2 ); } Modifiers.modifiersByName.put( "FamEq:" + name, effect ); } if ( type.equals( "Synergy" ) ) { String[] pieces = name.split( "/" ); if ( pieces.length < 2 ) { KoLmafia.updateDisplay( name + " contain less than 2 elements." ); continue loop; } int mask = 0; for ( int i = 0; i < pieces.length; ++i ) { Modifiers mods = Modifiers.getModifiers( "Item", pieces[ i ] ); if ( mods == null ) { KoLmafia.updateDisplay( name + " contains element " + pieces[ i ] + " with no modifiers." ); continue loop; } int emask = mods.bitmaps[ Modifiers.SYNERGETIC ]; if ( emask == 0 ) { KoLmafia.updateDisplay( name + " contains element " + pieces[ i ] + " that isn't Synergetic." ); continue loop; } mask |= emask; } Modifiers.synergies.add( name ); Modifiers.synergies.add( IntegerPool.get( mask ) ); } else if ( type.startsWith( "Mutex" ) ) { String[] pieces = name.split( "/" ); if ( pieces.length < 2 ) { KoLmafia.updateDisplay( name + " contain less than 2 elements." ); continue loop; } int bit = 1 << Modifiers.mutexes.size(); for ( int i = 0; i < pieces.length; ++i ) { Modifiers mods = null; if ( type.equals( "MutexI" ) ) { mods = Modifiers.getModifiers( "Item", pieces[ i ] ); } else if ( type.equals( "MutexE" ) ) { mods = Modifiers.getModifiers( "Effect", pieces[ i ] ); } if ( mods == null ) { KoLmafia.updateDisplay( name + " contains element " + pieces[ i ] + " with no modifiers." ); continue loop; } mods.bitmaps[ Modifiers.MUTEX ] |= bit; } Modifiers.mutexes.add( name ); } } try { reader.close(); } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); } } public static void writeModifiers( final File output ) { RequestLogger.printLine( "Writing data override: " + output ); // One map per equipment category Map hats = new TreeMap(); Map weapons = new TreeMap(); Map offhands = new TreeMap(); Map shirts = new TreeMap(); Map pants = new TreeMap(); Map accessories = new TreeMap(); Map containers = new TreeMap(); Map famitems = new TreeMap(); Map sixguns = new TreeMap(); Map bedazzlements = new TreeMap(); Map cards = new TreeMap(); Map folders = new TreeMap(); Map freepulls = new TreeMap(); Map potions = new TreeMap(); Map wikiname = new TreeMap(); // Iterate over all items and assign item id to category Iterator it = ItemDatabase.dataNameEntrySet().iterator(); while ( it.hasNext() ) { Entry entry = (Entry) it.next(); Integer key = (Integer) entry.getKey(); String name = (String) entry.getValue(); int type = ItemDatabase.getConsumptionType( key.intValue() ); switch ( type ) { case KoLConstants.EQUIP_HAT: hats.put( name, null ); break; case KoLConstants.EQUIP_PANTS: pants.put( name, null ); break; case KoLConstants.EQUIP_SHIRT: shirts.put( name, null ); break; case KoLConstants.EQUIP_WEAPON: weapons.put( name, null ); break; case KoLConstants.EQUIP_OFFHAND: offhands.put( name, null ); break; case KoLConstants.EQUIP_ACCESSORY: accessories.put( name, null ); break; case KoLConstants.EQUIP_CONTAINER: containers.put( name, null ); break; case KoLConstants.EQUIP_FAMILIAR: famitems.put( name, null ); break; case KoLConstants.CONSUME_SIXGUN: sixguns.put( name, null ); break; case KoLConstants.CONSUME_STICKER: bedazzlements.put( name, null ); break; case KoLConstants.CONSUME_CARD: cards.put( name, null ); break; case KoLConstants.CONSUME_FOLDER: folders.put( name, null ); break; default: Modifiers mods = Modifiers.getModifiers( "Item", name ); if ( mods == null ) { break; } if ( !mods.getString( Modifiers.EFFECT ).equals( "" ) ) { potions.put( name, null ); } else if ( mods.getBoolean( Modifiers.FREE_PULL ) ) { freepulls.put( name, null ); } else if ( !mods.getString( Modifiers.WIKI_NAME).equals( "" ) ) { wikiname.put( name, null ); } break; } } // Make a map of familiars Map familiars = new TreeMap(); familiars.put( "Familiar:(none)", null ); it = FamiliarDatabase.entrySet().iterator(); while ( it.hasNext() ) { Entry entry = (Entry) it.next(); String name = (String) entry.getValue(); if ( Modifiers.getModifiers( "Familiar", name ) != null ) { familiars.put( name, null ); } } // Make a map of campground items Map campground = new TreeMap(); for ( int i = 0; i < CampgroundRequest.campgroundItems.length; ++i ) { int itemId = CampgroundRequest.campgroundItems[i]; String name = ItemDatabase.getItemDataName( itemId ); // Sanity check: if the user has an old override file // which we didn't delete for some reason, we may have // an unknown item on the list of campground items. if ( name == null ) { KoLmafia.updateDisplay( "Campground item #" + itemId + " not found in data file. Do 'update clear' to remove stale override!" ); } // Skip toilet paper, since we want that in the free // pull section else if ( itemId != ItemPool.TOILET_PAPER ) { campground.put( name, null ); } } // Make a map of status effects Map effects = new TreeMap(); it = EffectDatabase.entrySet().iterator(); while ( it.hasNext() ) { Entry entry = (Entry) it.next(); Integer key = (Integer) entry.getKey(); String name = (String) entry.getValue(); // Skip effect which is also an item effects.put( name, null ); } // Make a map of passive skills Map passives = new TreeMap(); it = SkillDatabase.entrySet().iterator(); while ( it.hasNext() ) { Entry entry = (Entry) it.next(); Integer key = (Integer) entry.getKey(); String name = (String) entry.getValue(); if ( SkillDatabase.isPassive( key.intValue() ) ) { passives.put( name, null ); } } // Make a map of outfits Map outfits = new TreeMap(); int outfitCount = EquipmentDatabase.getOutfitCount(); for ( int i = 1; i <= outfitCount; ++i ) { SpecialOutfit outfit = EquipmentDatabase.getOutfit( i ); if ( outfit != null ) { outfits.put( outfit.getName(), null ); } } // Make a map of zodiac signs Map zodiacs = new TreeMap(); int signCount = KoLCharacter.ZODIACS.length; for ( int i = 0; i < signCount; ++i ) { String key = KoLCharacter.ZODIACS[ i ]; String name = key; zodiacs.put( name, null ); } // Make a map of stat days Map statdays = new TreeMap(); statdays.put( "Muscle Day", null ); statdays.put( "Mysticality Day", null ); statdays.put( "Moxie Day", null ); // Make a map of zones Map zones = new TreeMap(); it = AdventureDatabase.ZONE_DESCRIPTIONS.keySet().iterator(); while ( it.hasNext() ) { String key = (String) it.next(); String name = key; if ( Modifiers.getModifiers( "Zone", name ) != null ) { zones.put( name, null ); } } // Make a map of locations Map locations = new TreeMap(); it = AdventureDatabase.getAsLockableListModel().iterator(); while ( it.hasNext() ) { KoLAdventure key = (KoLAdventure) it.next(); String name = key.getAdventureName(); if ( Modifiers.getModifiers( "Loc", name ) != null ) { locations.put( name, null ); } } // Make a map of synergies Map synergies = new TreeMap(); it = Modifiers.synergies.iterator(); while ( it.hasNext() ) { String name = (String) it.next(); int mask = ((Integer) it.next()).intValue(); synergies.put( name, null ); } // Make a map of mutexes Map mutexes = new TreeMap(); it = Modifiers.mutexes.iterator(); while ( it.hasNext() ) { String name = (String) it.next(); mutexes.put( name, null ); } // Make a map of maximization categories Map maximization = new TreeMap(); int maximizationCount = Maximizer.maximizationCategories.length; for ( int i = 0; i < maximizationCount; ++i ) { maximization.put( Maximizer.maximizationCategories[i], null ); } // Open the output file PrintStream writer = LogStream.openStream( output, true ); writer.println( KoLConstants.EQUIPMENT_VERSION ); // For each equipment category, write the map entries Modifiers.writeModifierCategory( writer, hats, "Item", "Hats" ); writer.println(); Modifiers.writeModifierCategory( writer, pants, "Item", "Pants" ); writer.println(); Modifiers.writeModifierCategory( writer, shirts, "Item", "Shirts" ); writer.println(); Modifiers.writeModifierCategory( writer, weapons, "Item", "Weapons" ); writer.println(); Modifiers.writeModifierCategory( writer, offhands, "Item", "Off-hand" ); writer.println(); Modifiers.writeModifierCategory( writer, accessories, "Item", "Accessories" ); writer.println(); Modifiers.writeModifierCategory( writer, containers, "Item", "Containers" ); writer.println(); Modifiers.writeModifierCategory( writer, famitems, "Item", "Familiar Items" ); writer.println(); Modifiers.writeModifierCategory( writer, sixguns, "Item", "Sixguns" ); writer.println(); Modifiers.writeModifierCategory( writer, familiars, "Familiar", "Familiars" ); writer.println(); Modifiers.writeModifierCategory( writer, bedazzlements, "Item", "Bedazzlements" ); writer.println(); Modifiers.writeModifierCategory( writer, cards, "Item", "Alice's Army" ); writer.println(); Modifiers.writeModifierCategory( writer, folders, "Item", "Folder" ); writer.println(); Modifiers.writeModifierCategory( writer, campground, "Campground", "Campground equipment" ); writer.println(); Modifiers.writeModifierCategory( writer, effects, "Effect", "Status Effects" ); writer.println(); Modifiers.writeModifierCategory( writer, passives, "Skill", "Passive Skills" ); writer.println(); Modifiers.writeModifierCategory( writer, outfits, "Outfit", "Outfits" ); writer.println(); Modifiers.writeModifierCategory( writer, zodiacs, "Sign", "Zodiac Sign" ); writer.println(); Modifiers.writeModifierCategory( writer, statdays, "StatDay", "Stat Day" ); writer.println(); Modifiers.writeModifierCategory( writer, zones, "Zone", "Zone-specific" ); writer.println(); Modifiers.writeModifierCategory( writer, locations, "Loc", "Location-specific" ); writer.println(); Modifiers.writeModifierCategory( writer, synergies, "Synergy", "Synergies" ); writer.println(); Modifiers.writeModifierCategory( writer, mutexes, "Mutex", "Mutual exclusions" ); writer.println(); Modifiers.writeModifierCategory( writer, maximization, "MaxCat", "Maximization categories" ); writer.println(); Modifiers.writeModifierCategory( writer, potions, "Item", "Everything Else" ); Modifiers.writeModifierCategory( writer, freepulls, "Item" ); Modifiers.writeModifierCategory( writer, wikiname, "Item" ); writer.close(); } private static void writeModifierCategory( final PrintStream writer, final Map map, final String type, final String tag ) { writer.println( "# " + tag + " section of modifiers.txt" ); Modifiers.writeModifierCategory( writer, map, type ); } private static void writeModifierCategory( final PrintStream writer, final Map map, final String type ) { writer.println(); Object[] keys = map.keySet().toArray(); for ( int i = 0; i < keys.length; ++i ) { String name = (String) keys[ i ]; String lookup = Modifiers.getLookupName( type, name ); Object modifiers = Modifiers.modifiersByName.get( lookup ); Modifiers.writeModifierItem( writer, type, name, modifiers ); } } public static void writeModifierItem( final PrintStream writer, final String type, final String name, Object modifiers ) { if ( modifiers == null ) { Modifiers.writeModifierComment( writer, type, name ); return; } if ( modifiers instanceof Modifiers ) { modifiers = ((Modifiers) modifiers).getString( Modifiers.MODIFIERS ); } Modifiers.writeModifierString( writer, type, name, (String) modifiers ); } public static void writeModifierString( final PrintStream writer, final String type, final String name, final String modifiers ) { writer.println( Modifiers.modifierString( type, name, modifiers ) ); } public static String modifierString( final String type, final String name, final String modifiers ) { return type + "\t" + name + "\t" + modifiers; } public static String modifierCommentString( final String type, final String name, final String value ) { return "# " + ( type == null ? "" : type + " " ) + name + ": " + value; } public static void writeModifierComment( final PrintStream writer, final String type, final String name, final String value ) { writer.println( Modifiers.modifierCommentString( type, name, value ) ); } public static String modifierCommentString( final String type, final String name ) { return "# " + ( type == null ? "" : type + " " ) + name; } public static void writeModifierComment( final PrintStream writer, final String type, final String name ) { writer.println( Modifiers.modifierCommentString( type, name ) ); } public static final void registerItem( final String name, final String text, final int type ) { // Examine the item description and decide what it is. ArrayList<String> unknown = new ArrayList<String>(); String known = DebugDatabase.parseItemEnchantments( text, unknown, type ); DebugDatabase.parseRestores( name, text ); Modifiers.registerObject( "Item", name, unknown, known ); } public static final void registerEffect( final String name, final String text ) { // Examine the effect description and decide what it is. ArrayList<String> unknown = new ArrayList<String>(); String known = DebugDatabase.parseEffectEnchantments( text, unknown ); Modifiers.registerObject( "Effect", name, unknown, known ); } public static final void registerOutfit( final String name, final String text ) { // Examine the outfit description and decide what it is. ArrayList<String> unknown = new ArrayList<String>(); String known = DebugDatabase.parseOutfitEnchantments( text, unknown ); Modifiers.registerObject( "Outfit", name, unknown, known ); } private static final void registerObject( final String type, final String name, final ArrayList<String> unknown, final String known ) { for ( String value : unknown ) { String printMe = Modifiers.modifierCommentString( type, name, value ); RequestLogger.printLine( printMe ); RequestLogger.updateSessionLog( printMe ); } if ( known.equals( "" ) ) { if ( unknown.size() == 0 ) { String printMe = Modifiers.modifierCommentString( type, name ); RequestLogger.printLine( printMe ); RequestLogger.updateSessionLog( printMe ); } } else { String printMe = Modifiers.modifierString( type, name, known ); RequestLogger.printLine( printMe ); RequestLogger.updateSessionLog( printMe ); String lookup = Modifiers.getLookupName( type, name ); if ( !Modifiers.modifiersByName.containsKey( lookup ) ) { Modifiers.modifiersByName.put( lookup, known ); } } } }