# This rcfile is elliptic's DCSS bot "qw", the first (and thus far only) bot # to win DCSS with no human assistance. A substantial amount of code here was # contributed by elliott or borrowed from N78291's bot "xw", and many others # have contributed as well. # For brief instructions and the most up-to-date version of qw, see # https://github.com/elliptic/qw. ## qw's settings: # # Set this to true when playing online. : DELAYED = true # Delay per action in milliseconds. # Set this to at least 100 or so when playing online. : DELAY_TIME = 125 # whether to start playing immediately when a new game is started # unfortunately this doesn't work if the game starts with a -more- : AUTO_START = false # experimental: do second lair rune branch before depths : EARLY_SECOND_RUNE = true # experimental: wait to do Orc until after D:15 : LATE_ORC = true # lair rune preferences, current options are: # * random - no preference, chooses randomly # * nowater - does Snake/Spider first # * smart - currently prefers Spider > Snake/Swamp > Shoals # * dsmart - Swamp/Spider > Snake > Shoals (DsBe data) : RUNE_PREFERENCE = "random" # plan for post-V:5, should end with zot # maximal example: "tso,tomb,hells,pan,slime,abyss,zig,zot" : ENDGAME_PLAN = "tso,tomb,hells,pan,slime,abyss,zig,zot" # depth to attempt to dive in ziggurats : ZIG_DIVE = 27 # burn books with Trog, may theoretically cause navigation problems : BURN_BOOKS = true # enable certain somewhat spammy notes : SPAM = false # tab just takes a single action (for testing) : SINGLE_STEP = false # panic (stop) at full inventory : FULL_INVENTORY_PANIC = true # quit after this number of turns while stuck : QUIT_TURNS = 1000 # use a shield at all cost : SHIELD_CRAZY = true # acceptable gods (join whichever one we find first) # gods who have been at least partially implemented: BCHLMOQRTUXY1 # gods who are actually pretty decent on qw: CMORT : GOD_LIST = { "Trog", } # use faded altar, abandoning if we don't get one of the gods in GOD_LIST : FADED_ALTAR = false # cycle through combos using c_persist.options : COMBO_CYCLE = false # combos to cycle through - uses the same syntax as the regular combo option # with an optional "^" suffix to overwrite the GOD_LIST # list above. Here are some sample choices: : COMBO_CYCLE_LIST = "DDFi.waraxe^M, DDBe.handaxe^T, GrBe.handaxe^T, GrFi.waraxe^O, MiFi.waraxe^O" # Choose randomly between these combos. This will not apply if the COMBO_CYCLE # option was on in your _previous_ game. combo = GrBe.handaxe # For random berserkers, use these combos: #combo = CeBe.handaxe, DDBe.handaxe, DEBe.handaxe, DrBe.handaxe, DsBe.handaxe #combo += FeBe.claws, FoBe.handaxe, GrBe.handaxe, GhBe.claws, HaBe.falchion #combo += HEBe.falchion, HOBe.handaxe, HuBe.handaxe, KoBe.mace, MfBe.spear #combo += MiBe.handaxe, MuBe.handaxe, NaBe.handaxe, OgBe.mace, OpBe.handaxe #combo += SpBe.shortsword, TeBe.handaxe, TrBe.claws, VSBe.handaxe, VpBe.handaxe # For a totally random combo (with chosen weapon type), hyperqwcombo.rc can # be created using hyperqwcombogen.sh and used: #combo = #include += hyperqwcombo.rc ## The accomplishments of qw: # online wins: CeFi^O DDBe DDFi^C DDFi^O DDFi^Q DDFi^Y DDGl^M DDGl^R DrFi^O # DsBe FoFi^O GhFi^O GrBe HOBe HuBe MfBe MiBe MiFi^C MiFi^O # MiFi^Q MiFi^R MiFi^Y MuFi^O NaBe OgFi^O TeBe TrBe VSBe # offline wins: HaFi^O HEBe HEFi^O VpBe VpFi^O # (offline) runes: DgFi FeFi^O KoBe KoFi^O OpBe OpFi^O ##################################### # enum values :/ : ENUM_MONS_PANDEMONIUM_LORD = 344 : ATT_FRIENDLY = 4 : ATT_NEUTRAL = 1 : ATT_HOSTILE = 0 : LOS = 7 : if you.race() == "Barachi" then : LOS = 8 : end ##################################### # miscellaneous simple options name = qw restart_after_game = false equip_bar = true ability_menu = false view_delay = 0 use_animations = darken_beyond_range = false clear_messages = true travel_delay = -1 explore_delay = -1 rest_delay = -1 travel_key_stop = false default_manual_training = true autopickup_no_burden = false auto_exclude = hp_warning = 0 show_more = false show_newturn_mark = false list_rotten = false force_more_message = show_travel_trail = false skill_focus = false autoinscribe += slay:mikee autoinscribe += (inedible|mutagenic|forbidden).*(chunk|corpse):noeat flush.failure = false char_set = ascii cset = cloud:xa4 cset = item_orb:0 use_fake_player_cursor = true equip_unequip = true dump_order = header,hiscore,stats,misc,mutations,skills,spells,inventory dump_order += overview dump_order += messages,screenshot,monlist,kills,notes,vaults,skill_gains,action_counts ood_interesting = 6 note_hp_percent = 25 note_skill_levels = 1,3,6,9,12,15,18,21,24,27 note_all_spells = true fire_order = launcher, rock, javelin, tomahawk auto_eat_chunks = false read_persist_options = true message_colour ^= mute:Unknown command #################################### # not sure exactly how important or correct these settings are explore_stop = explore_stop += items,branches,portals,stairs,altars explore_stop += greedy_visited_item_stack,greedy_pickup_smart stop := runrest_stop_message ignore := runrest_ignore_message stop = ignore = ignore += .* runrest_ignore_poison = 3:15 runrest_ignore_monster += butterfly:1 runrest_ignore_monster += orb of destruction:1 #################################### # These keys are useful to answer prompts and aren't critical for manual play bindkey = [Y] CMD_NO_CMD_DEFAULT bindkey = [N] CMD_NO_CMD_DEFAULT bindkey = [B] CMD_NO_CMD_DEFAULT bindkey = [C] CMD_NO_CMD_DEFAULT bindkey = [.] CMD_NO_CMD_DEFAULT bindkey = [^D] CMD_LUA_CONSOLE #################################### # Don't get interrupted! : chk_interrupt_activity["blurry vision"] = function (iname, cause, extra) : return nil : end #################################### # autopickup is all handled via lua autopickup = autopickup_exceptions = ################################################################ # now the lua, beginning with global variables { -- some global variables: local initialized = false local time_passed local update_coroutine local do_dummy_action local dump_count = you.turns() + 100 - (you.turns() % 100) local skill_count = you.turns() - (you.turns() % 5) local danger local immediate_danger local cloudy local where local where_shafted_from = nil local expect_new_location local expect_portal local automatic = false local ignore_list = { } local failed_move = { } local invisi_count = 0 local next_delay = 100 local dd_hw_meter = 0 local sigmund_dx = 0 local sigmund_dy = 0 local invisi_sigmund = false local sgd_timer = -200 local stuck_turns = 0 local stepped_on_lair = false local stepped_on_tomb = false local lair_step_mode = false -- are these still necessary? local did_move = false local move_count = 0 local did_move_towards_monster = 0 local target_memory_x local target_memory_y local last_wait = 0 local wait_count = 0 local old_turn_count = you.turns()-1 local hiding_turn_count = -100 local travel_destination = nil local game_status = "normal" local have_message = false local read_message = true local monster_array local enemy_list local upgrade_phase = false local tactical_step local tactical_reason local is_waiting local did_first_turn = false local stairdance_count = {} local clear_exclusion_count = {} local v5_entry_turn local last_swamp_fail_count = -1 local swamp_rune_reachable = false local offlevel_travel = true local last_min_delay_skill = 18 local only_linear_resists = false local no_spells = false local level_map local stair_dists local waypoint_parity local cur_where local prev_where local did_waypoint = false local good_stair_list local target_stair local last_flee_turn = -100 local ABYSSAL_RUNE = false local SLIMY_RUNE = false local PAN_RUNE = false local HELL_RUNE = false local GOLDEN_RUNE = false local TSO_CONVERSION = false local LUGONU_CONVERSION = false local MIGHT_BE_GOOD = false local endgame_plan_list = {} local which_endgame_plan = 1 local dislike_pan_level = false local prev_hatch_dist = 1000 local prev_hatch_x local prev_hatch_y -- options to set while qw is running -- maybe should add more mutes for watchability function set_options() crawl.setopt("confirm_butcher = always") crawl.setopt("pickup_mode = multi") crawl.setopt("message_colour += mute:Search for what") crawl.setopt("message_colour += mute:Can't find anything") crawl.setopt("message_colour += mute:Drop what") crawl.setopt("message_colour += mute:Okay, then") crawl.setopt("message_colour += mute:Use which ability") crawl.setopt("message_colour += mute:Read which item") crawl.setopt("message_colour += mute:Drink which item") crawl.setopt("message_colour += mute:not good enough") crawl.setopt("message_colour += mute:Attack whom") crawl.setopt("message_colour += mute:move target cursor") crawl.setopt("message_colour += mute:Aim:") crawl.setopt("message_colour += mute:You reach to attack") crawl.enable_more(false) end function unset_options() crawl.setopt("always_confirm_butcher = auto") crawl.setopt("pickup_mode = auto") crawl.setopt("message_colour -= mute:Search for what") crawl.setopt("message_colour -= mute:Can't find anything") crawl.setopt("message_colour -= mute:Drop what") crawl.setopt("message_colour -= mute:Okay, then") crawl.setopt("message_colour -= mute:Use which ability") crawl.setopt("message_colour -= mute:Read which item") crawl.setopt("message_colour -= mute:Drink which item") crawl.setopt("message_colour -= mute:not good enough") crawl.setopt("message_colour -= mute:Attack whom") crawl.setopt("message_colour -= mute:move target cursor") crawl.setopt("message_colour -= mute:Aim:") crawl.setopt("message_colour -= mute:You reach to attack") crawl.enable_more(true) end ------------------------------------- -- equipment valuation and autopickup -- We assign a numerical value to all armour/weapon/jewellery, which -- is used both for autopickup (so it has to work for unIDed items) and -- for equipment selection. A negative value means we prefer an empty slot. -- The valuation functions either return a pair of numbers - minimum -- minimum and maximum potential value - or the current value. Here -- value should be viewed as utility relative to not wearing anything in -- that slot. For the current value calculation, we can specify an equipped -- item and try to simulate not wearing it (for resist values). -- We pick up an item if its max value is greater than our currently equipped -- item's min value. We swap to an item if it has a greater cur value. -- if cur, return the current value instead of minmax -- if it2, pretend we aren't equipping it2 -- if sit = "hydra", assume we are fighting a hydra at lowish XL -- = "extended", assume we are in (or about to enter) Pan -- if TSO_CONVERSION, we need this weapon to be TSO-friendly -- = "bless", assume we want to bless the weapon with TSO eventually function equip_value(it, cur, it2, sit) if not it then return 0,0 end local class = it.class(true) if class == "armour" then return armour_value(it, cur, it2) elseif class == "weapon" then return weapon_value(it, cur, it2, sit) elseif class == "jewellery" then if equip_slot(it) == "Amulet" then return amulet_value(it, cur, it2) else return ring_value(it, cur, it2) end end return -1,-1 end -- Returns the amount of an artprop granted by an item - not all artprops are -- currently handled here. function item_resist(str, it) if not it then return 0 end if it.artefact and it.artprops and it.artprops[str] then return it.artprops[str] else local name = it.name() local ego = it.ego() local subtype = it.subtype() if str == "rF" then if name:find("fire dragon") then return 2 elseif ego == "fire resistance" or ego == "resistance" or subtype == "ring of protection from fire" or name:find("gold dragon") or subtype == "ring of fire" then return 1 elseif name:find("ice dragon") or subtype == "ring of ice" then return -1 else return 0 end elseif str == "rC" then if name:find("ice dragon") then return 2 elseif ego == "cold resistance" or ego == "resistance" or subtype == "ring of protection from cold" or name:find("gold dragon") or subtype == "ring of ice" then return 1 elseif name:find("fire dragon") or subtype == "ring of fire" then return -1 else return 0 end elseif str == "rElec" then if name:find("storm dragon") then return 1 else return 0 end elseif str == "rPois" then if ego == "poison resistance" or subtype == "ring of poison resistance" or name:find("swamp dragon") or name:find("gold dragon") then return 1 else return 0 end elseif str == "rN" then if ego == "positive energy" or subtype == "ring of positive energy" or name:find("pearl dragon") then return 1 else return 0 end elseif str == "MR" then if ego == "magic resistance" or subtype == "ring of protection from magic" or name:find("quicksilver dragon") then return 1 else return 0 end elseif str == "rCorr" then if subtype == "ring of resist corrosion" or name:find("acid dragon") then return 1 else return 0 end elseif str == "SInv" then if ego == "see invisible" or subtype == "ring of see invisible" then return 1 else return 0 end elseif str == "Spirit" then if ego == "spirit shield" or subtype == "amulet of guardian spirit" then return 1 else return 0 end elseif str == "Str" then if subtype == "ring of strength" then return it.plus or 0 elseif ego == "strength" then return 3 end elseif str == "Dex" then if subtype == "ring of dexterity" then return it.plus or 0 elseif ego == "dexterity" then return 3 end elseif str == "Int" then if subtype == "ring of intelligence" then return it.plus or 0 elseif ego == "intelligence" then return 3 end elseif str == "Slay" then if subtype == "ring of slaying" then return it.plus or 0 end elseif str == "AC" then if subtype == "ring of protection" then return it.plus or 0 elseif ego == "protection" then return 3 -- wrong for weapons but we scale things differently for weapons end elseif str == "EV" then if subtype == "ring of evasion" then return it.plus or 0 end elseif str == "SH" then if subtype == "amulet of reflection" then return it.plus or 0 end end end return 0 end -- Returns the player's intrinsic level of an artprop string. function intrinsic_resist(str) if str == "rF" then return you.mutation("fire resistance") elseif str == "rC" then return you.mutation("cold resistance") elseif str == "rElec" then return you.mutation("electricity resistance") elseif str == "rPois" then if intrinsic_rpois() or (you.mutation("poison resistance") > 0) then return 1 else return 0 end elseif str == "rN" then local val = you.mutation("negative energy resistance") if you.god() == "the Shining One" then val = val + math.floor(you.piety_rank() / 3) end return val elseif str == "MR" then return you.mutation("magic resistance") + ((you.god() == "Trog") and 1 or 0) elseif str == "rCorr" then return 0 elseif str == "SInv" then if intrinsic_sinv() or (you.mutation("see invisible") > 0) then return 1 else return 0 end elseif str == "Spirit" then return ((you.race() == "Vine Stalker") and 1 or 0) end return 0 end -- Returns the current level of "resistance" to an artprop string. If an -- item is provided, assume the item is equipped and try to pretend that -- it is unequipped. Does not include some temporary effects. function player_resist(str, it) local it_res = it and it.equipped and item_resist(str, it) or 0 if str == "Str" then stat,_ = you.strength() return stat-it_res elseif str == "Dex" then stat,_ = you.dexterity() return stat-it_res elseif str == "Int" then stat,_ = you.intelligence() return stat-it_res end local other_res = intrinsic_resist(str) for it2 in inventory() do if it2.equipped and slot(it2) ~= slot(it) then other_res = other_res + item_resist(str, it2) end end if str == "rF" or str == "rC" or str == "rN" or str == "MR" then return other_res else return ((other_res > 0) and 1 or 0) end end function equip_slot(it) local class = it.class(true) if class == "armour" then return good_slots[it.subtype()] elseif class == "weapon" then return "Weapon" elseif class == "jewellery" then local sub = it.subtype() if sub and sub:find("amulet") or not sub and it.name():find("amulet") then return "Amulet" else return "Ring" -- not the actual slot name end end return end -- The current utility of having a given amount of an artprop. function absolute_resist_value(str, n) if str == "Str" or str == "Int" or str == "Dex" then if n > 4 then return 0 -- handled by linear_resist_value() elseif n > 2 then return -100 elseif n > 0 then return -250 else return -10000 end end if n == 0 then return 0 end if slime_soon() and (str == "rF" or str == "rElec" or str == "rPois" or str == "rN" or str == "MR" or str == "SInv") then return 0 end local val = 0 if str == "rF" or str == "rC" then if n < 0 then val = -150 elseif n == 1 then val = 125 elseif n == 2 then val = 200 elseif n >= 3 then val = 250 end if str == "rF" and zot_soon() then val = val * 2.5 elseif str == "rC" and slime_soon() then val = val * 1.5 end return val elseif str == "rElec" then return 75 elseif str == "rPois" then return ((easy_runes() < 2) and 225 or 75) elseif str == "rN" then return 25*n elseif str == "MR" then if n <= 2 then return 75*n else return 200 end elseif str == "rCorr" then return (slime_soon() and 1200 or 50) elseif str == "SInv" then return 200 elseif str == "Spirit" then return ((you.race() == "Deep Dwarf") and -10000 or 75) end return 0 end function max_resist_value(str, d) if d <= 0 then return 0 end local val = 0 local ires = intrinsic_resist(str) if str == "rF" or str == "rC" then if d == 1 then val = 150 elseif d == 2 then val = 275 elseif d == 3 then val = 350 end if str == "rF" then val = val * 2.5 elseif str == "rC" and SLIMY_RUNE then val = val * 1.5 end return val elseif str == "rElec" then return ((ires < 1) and 75 or 0) elseif str == "rPois" then return ((ires < 1) and ((easy_runes() < 2) and 225 or 75) or 0) elseif str == "rN" then return ((ires < 3) and 25*d or 0) elseif str == "MR" then return 75*d elseif str == "rCorr" then return (ires < 1 and (SLIMY_RUNE and 1200 or 50) or 0) elseif str == "SInv" then return ((ires < 1) and 200 or 0) elseif str == "Spirit" then if ires > 0 then return 0 elseif you.race() == "Deep Dwarf" then return -10000 else return 75 end end return 0 end function min_resist_value(str, d) if str == "Spirit" and you.race() == "Deep Dwarf" then return -10000 end if d >= 0 then return 0 end if str == "rF" then return -375 elseif str == "rC" then return (SLIMY_RUNE and -225 or -150) elseif str == "MR" then return 75*d end return 0 end function resist_value(str, it, cur, it2) local d = item_resist(str, it) if d == 0 then return 0,0 end if cur then local c = player_resist(str, it2) local diff = absolute_resist_value(str,c+d) - absolute_resist_value(str,c) return diff,diff else return min_resist_value(str,d),max_resist_value(str,d) end end function linear_resist_value(str) if str == "Slay" or str == "AC" or str == "EV" then return 50 elseif str == "SH" then return 40 elseif str == "Str" then return 30 elseif str == "Dex" then return 20 end return 0 end function total_resist_value(it, cur, it2) resistlist = { "rF", "rC", "rElec", "rPois", "rN", "MR", "rCorr", "SInv", "Spirit", "Str", "Dex", "Int" } linearlist = { "Str", "Dex", "Slay", "AC", "EV", "SH" } local val = 0 for _,str in ipairs(linearlist) do val = val + item_resist(str, it)*linear_resist_value(str) end local val1, val2 = val,val if not only_linear_resists then for _,str in ipairs(resistlist) do local a,b = resist_value(str, it, cur, it2) val1 = val1 + a val2 = val2 + b end end return val1,val2 end function resist_vec(it) local resistlist = { "rF", "rC", "rElec", "rPois", "rN", "MR", "rCorr", "SInv", "Spirit" } local vec = { } for _,str in ipairs(resistlist) do local a,b = resist_value(str, it) table.insert(vec, b > 0 and b or a) end return vec end function base_equip_value(it) only_linear_resists = true local val1, val2 = equip_value(it) only_linear_resists = false return val1,val2 end -- complicated check: -- is it going to be worse than it2 no matter what other resists we have? function resist_dominated(it,it2) local bmin, bmax = base_equip_value(it) local bmin2, bmax2 = base_equip_value(it2) local diff = bmin2 - bmax if diff < 0 then return false end local vec = resist_vec(it) local vec2 = resist_vec(it2) local l = #vec for i = 1,l do if vec[i] > vec2[i] then diff = diff - (vec[i] - vec2[i]) end end return (diff >= 0) end function rune_goal() return 3 + (ABYSSAL_RUNE and 1 or 0) + (SLIMY_RUNE and 1 or 0) + (PAN_RUNE and 5 or 0) + (HELL_RUNE and 4 or 0) + (GOLDEN_RUNE and 1 or 0) end function easy_runes() return (you.have_rune("decaying") and 1 or 0) + (you.have_rune("serpentine") and 1 or 0) + (you.have_rune("barnacled") and 1 or 0) + (you.have_rune("gossamer") and 1 or 0) end function update_game_status() if you.have_orb() then game_status = "orbrun" return end if game_status == "normal" and you.have_rune("silver") and not where:find("Vaults") and not where:find("Abyss") then game_status = "shopping" end if game_status == "shopping" and c_persist.done_shopping then game_status = endgame_plan_list[1] end while (game_status == "slime" and you.have_rune("slimy") and not where:find("Slime") or game_status == "pan" and have_pan_runes() and where ~= "Pan" and not where:find("Abyss") or game_status == "abyss" and you.have_rune("abyssal") and not where:find("Abyss")) or game_status == "hells" and have_hell_runes() and where ~= "Hell" and not where:find("Dis") and not where:find("Geh") and not where:find("Coc") and not where:find("Tar") and not where:find("Abyss") or game_status == "tomb" and you.have_rune("golden") and not where:find("Tomb") and not where:find("Abyss") or game_status == "tso" and you.god() == "the Shining One" or game_status == "zig" and c_persist.entered_zig and not where:find("Zig") do which_endgame_plan = which_endgame_plan + 1 game_status = endgame_plan_list[which_endgame_plan] end end function zot_soon() return (game_status == "zot") end function slime_soon() return (game_status == "slime") end function in_extended() return (game_status == "pan" or game_status == "hells" or game_status == "tomb") end -- list of armour slots, this is used to normalize names for them and also -- to iterate over the slots good_slots = {cloak="Cloak", helmet="Helmet", gloves="Gloves", boots="Boots", body="Armour", shield="Shield"} function armour_value(it, cur, it2) local name = it.name() local value = 0 local val1,val2 = total_resist_value(it, cur, it2) local ego = it.ego() if it.artefact then if not it.fully_identified then -- could be good or bad val2 = val2 + 400 val1 = val1 + (cur and 400 or -400) end ap = it.artprops if ap and (ap["-Tele"] or ap["*Tele"]) and you.race() ~= "Formicid" then return -1,-1 end if ap and ap["*Rage"] and you.race() ~= "Mummy" and you.race() ~= "Ghoul" and you.race() ~= "Formicid" then return -1,-1 end if name:find("Pondering") or name:find("hauberk") then return -1,-1 end if ap and ap["Fragile"] then return -1,-1 end if ap and ap["*Slow"] and you.race() ~= "Formicid" then value = value - 100 end if ap and ap["*Corrode"] then value = value - 100 end elseif name:find("runed") or name:find("glowing") or name:find("dyed") or name:find("embroidered") or name:find("shiny") then val2 = val2 + 400 val1 = val1 + (cur and 400 or -200) elseif ego then -- names in armour_ego_name() if ego == "running" then value = value + 25 if you.god() == "Cheibriados" then return -1,-1 end elseif ego == "flying" and not intrinsic_flight() then if not intrinsic_flight() then value = value + 200 end elseif ego == "ponderousness" then return -1,-1 elseif ego == "repulsion" then value = value + 200 end end value = value + 50*expected_armour_multiplier()*it.ac if it.plus then value = value + 50*it.plus end st, _ = it.subtype() if good_slots[st] == "Shield" then if it.encumbrance == 0 then if not want_buckler() then return -1,-1 end elseif (not want_shield()) and (have_two_hander() or you.base_skill("Shields") == 0) then return -1,-1 end end -- name always starts with {boots armour} here -- ^ is no longer true I think? if good_slots[st] == "Boots" then if you.race() == "Centaur" then if not (name:find("centaur barding") or name:find("horse barding")) then return -1,-1 end elseif you.race() == "Naga" then if not (name:find("naga barding") or name:find("lightning scales")) then return -1,-1 end else if name:find("barding") then return -1,-1 end end end if good_slots[st] == "Armour" then if unfitting_armour() then value = value - 25*it.ac end evp = it.encumbrance ap = armour_plan() if ap == "heavy" or ap == "large" then if evp >= 20 then value = value - 100 elseif name:find("pearl dragon") then value = value + 100 end elseif ap == "dodgy" then if evp > 11 then return -1,-1 elseif evp > 7 then value = value - 100 end else if evp > 7 then return -1,-1 elseif evp > 4 then value = value - 100 end end end return value+val1,value+val2 end function weapon_value(it, cur, it2, sit) local hydra_swap = (sit == "hydra") local extended = (sit == "extended") local tso = you.god() == "the Shining One" or extended and TSO_CONVERSION or you.god() == "Elyvilon" or you.god() == "Zin" or you.god() == "No God" and MIGHT_BE_GOOD if it.class(true) ~= "weapon" then return -1,-1 end local name = it.name() local value = 1000 if it.weap_skill ~= wskill() then weap = items.equipped_at("Weapon") if weap and weap.weap_skill == wskill() or wskill() == "Unarmed Combat" or it.weap_skill == "Crossbows" or it.weap_skill == "Bows" or it.weap_skill == "Slings" then if not (hydra_swap and (it.weap_skill == "Maces & Flails" and wskill() == "Axes" or it.weap_skill == "Short Blades" and wskill() == "Long Blades")) then return -1,-1 end end end if it.hands == 2 and want_buckler() then return -1,-1 end if sit == "bless" then local val1, val2 = 0, 0 if it.artefact then return -1,-1 elseif name:find("runed") or name:find("glowing") or name:find("enchanted") or it.ego() and not it.fully_identified then val2 = val2 + 150 val1 = val1 + (cur and 150 or -150) end if it.plus then value = value + 30*it.plus end delay_estimate = min(7,math.floor(it.delay / 2)) if it.weap_skill == "Short Blades" and delay_estimate > 5 then delay_estimate = 5 end value = value + 1200*it.damage/delay_estimate return value+val1, value+val2 end if you.god() == "Cheibriados" and name:find("quick blade") then return -1,-1 end if tso and name:find("demon") and not name:find("eudemon") then return -1,-1 end if it.artefact then ap = it.artprops if ap and (ap["-Tele"] or ap["*Tele"]) and you.race() ~= "Formicid" then return -1,-1 end if ap and ap["*Rage"] and you.race() ~= "Mummy" and you.race() ~= "Ghoul" and you.race() ~= "Formicid" then return -1,-1 end if ap and ap["Fragile"] then return -1,-1 end if ap and ap["*Slow"] and you.race() ~= "Formicid" then value = value - 100 end if ap and ap["*Corrode"] then value = value - 100 end if (intrinsic_evil() or you.god() == "Yredelemnul") and name:find("holy") then return -1,-1 end if name:find("obsidian axe") then value = value + 300 if tso then return -1,-1 end end end local val1,val2 = total_resist_value(it, cur, it2) if it.artefact and not it.fully_identified or name:find("runed") or name:find("glowing") then val2 = val2 + 500 val1 = val1 + (cur and 500 or -250) end local ego = it.ego() if hydra_swap then if ego == "vampirism" and not intrinsic_undead() then return -1,-1 else local hydra_quality = hydra_weapon_status(it) if hydra_quality == -1 then return -1,-1 elseif hydra_quality == 1 then value = value + 500 end end end if ego then -- names are mostly in weapon_brands_verbose[] if ego == "distortion" then return -1,-1 elseif ego == "holy wrath" then if intrinsic_evil() or you.god() == "Yredelemnul" then return -1,-1 end if extended then value = value + 500 end elseif ego == "vampirism" then value = value + 500 -- this is what we want if tso then return -1,-1 end if extended then value = value - 400 end elseif ego == "speed" then value = value + 300 -- this is good too if you.god() == "Cheibriados" then return -1,-1 end elseif ego == "electrocution" then value = value + 150 -- not bad elseif ego == "draining" then if not extended then value = value + 75 end if tso then return -1,-1 end elseif ego == "flaming" or ego == "freezing" or ego == "crushing" or ego == "slicing" or ego == "piercing" or ego == "chopping" or ego == "slashing" then value = value + 75 elseif ego == "venom" then if not extended then value = value + 50 end elseif ego == "antimagic" then if you.race() == "Vine Stalker" then value = value - 300 else value = value + 75 end elseif ego == "pain" and tso then return -1,-1 elseif ego == "chaos" and (tso or you.god() == "Cheibriados") then return -1,-1 end end if it.plus then value = value + 30*it.plus end delay_estimate = min(7,math.floor(it.delay / 2)) if it.weap_skill == "Short Blades" and delay_estimate > 5 then delay_estimate = 5 end -- we might be delayed by a shield or not yet at min delay, so add a little delay_estimate = delay_estimate + 1 value = value + 1200*it.damage/delay_estimate -- subtract a bit for very slow weapons because of how much skill they require to reach min delay if it.delay > 17 then value = value - 120*(it.delay - 17) end if it.weap_skill ~= wskill() then value = value / 10 val1 = val1 / 10 val2 = val2 / 10 end return value+val1,value+val2 end function amulet_value(it, cur, it2) local name = it.name() local subtype = it.subtype() local value = 0 if it.artefact then ap = it.artprops if ap and (ap["-Tele"] or ap["*Tele"]) and you.race() ~= "Formicid" then return -1,-1 end if ap and ap["*Rage"] and you.race() ~= "Mummy" and you.race() ~= "Ghoul" and you.race() ~= "Formicid" then return -1,-1 end if name:find("macabre finger necklace") then return -1,-1 end if ap and ap["Fragile"] then return -1,-1 end if ap and ap["*Slow"] and you.race() ~= "Formicid" then value = value - 100 end if ap and ap["*Corrode"] then value = value - 100 end end if subtype == "amulet of faith" then if you.god() == "Cheibriados" or you.god() == "Beogh" or you.god() == "Qazlal" or you.god() == "Hepliaklqana" then return -1,-1 -- we don't use piety much on these gods at the moment elseif you.god() ~= "Ru" and you.god() ~= "Xom" then return 1000,1000 -- fixed value so we don't unequip for a randart one end end if it.artefact and not it.fully_identified or not (it.artefact or name:find("amulet of")) then if cur then return 800,800 else return -1,1000 end end local val1,val2 = total_resist_value(it, cur, it2) if subtype == "amulet of reflection" then value = value + 20 -- for reflection if not it.artefact and not it.plus then value = value + 6*40 if not cur then val1 = val1 - 4*40 end end elseif subtype == "amulet of regeneration" and you.race() ~= "Deep Dwarf" then value = value + 50 elseif subtype == "amulet of inaccuracy" then value = value - 250 elseif subtype == "amulet of harm" then value = value - 150 end return value+val1,value+val2 end function ring_value(it, cur, it2) local name = it.name() local subtype = it.subtype() local value = 0 if it.artefact then ap = it.artprops if ap and (ap["-Tele"] or ap["*Tele"]) and you.race() ~= "Formicid" then return -1,-1 end if ap and ap["*Rage"] and you.race() ~= "Mummy" and you.race() ~= "Ghoul" and you.race() ~= "Formicid" then return -1,-1 end if ap and ap["Fragile"] then return -1,-1 end if ap and ap["*Slow"] and you.race() ~= "Formicid" then value = value - 100 end if ap and ap["*Corrode"] then value = value - 100 end end if subtype == "ring of teleportation" and you.race() ~= "Formicid" then return -1,-1 end if it.artefact and not it.fully_identified or not (it.artefact or name:find("ring of")) then if cur then return 5000,5000 else return -1,5000 end end local val1,val2 = total_resist_value(it, cur, it2) if not it.artefact and not it.plus then local linval = 0 if subtype == "ring of slaying" or subtype == "ring of protection" or subtype == "ring of evasion" then linval = 50 elseif subtype == "ring of strength" then linval = 30 elseif subtype == "ring of dexterity" then linval = 20 end value = value + 6*linval if not cur then val1 = val1 - 12*linval end end return value+val1,value+val2 end function count_charges(wand_type, ignore_it) count = 0 for it in inventory() do if it.class(true) == "wand" and (ignore_it == nil or slot(it) ~= slot(ignore_it)) and it.subtype() == wand_type then if it.plus then count = count + it.plus elseif it.plus == nil and not it.name():find("empty") then if it.name():find("zapped") then count = count + 3 else count = count + 6 end end end end return count end function want_wand(it) if you.mutation("inability to use devices") > 0 then return false end only_wand = true for it2 in inventory() do if only_wand and it2.class(true) == "wand" and slot(it2) ~= slot(it) then only_wand = false end end if only_wand then return true -- for Evo training end sub = it.subtype() if sub == nil then return true end if sub ~= "digging" then return false end if it.name():find("empty") or it.plus == 0 then for it2 in inventory() do if it2.class(true) == "wand" and slot(it2) ~= slot(it) and it2.subtype() == sub then return false end end elseif sub == "digging" then return (count_charges("digging", it) < 18) end return true end function want_potion(it) sub = it.subtype() if sub == nil then return true end if sub == "blood" and you.race() == "Vampire" then return true end if sub ~= "curing" and sub ~= "heal wounds" and sub ~= "haste" and sub ~= "resistance" and sub ~= "experience" and sub ~= "might" and sub ~= "mutation" then return false end return true end function want_scroll(it) sub = it.subtype() if sub == nil then return true end if sub ~= "identify" and sub ~= "teleportation" and sub ~= "remove curse" and sub ~= "enchant weapon" and sub ~= "enchant armour" and sub ~= "acquirement" and sub ~= "recharging" and sub ~= "brand weapon" then return false end return true end -- This doesn't handle rings correctly at the moment, but right now we are -- only using this for weapons anyway. -- Also maybe this should check resist_dominated too? function item_is_sit_dominated(it,sit) local slotname = equip_slot(it) local minv,maxv = equip_value(it, nil, nil, sit) if maxv <= 0 then return true end for it2 in inventory() do if equip_slot(it2) == slotname and slot(it2) ~= slot(it) then local minv2,maxv2 = weapon_value(it2, nil, nil, sit) if minv2 >= maxv and not (slotname == "Weapon" and you.base_skill("Shields") > 0 and it.hands == 1 and it2.hands == 2) then return true end end end return false end function item_is_dominated(it) local slotname = equip_slot(it) if slotname == "Weapon" and you.xl() < 18 and not item_is_sit_dominated(it, "hydra") then return false elseif (PAN_RUNE or HELL_RUNE or GOLDEN_RUNE) and slotname == "Weapon" and not item_is_sit_dominated(it, "extended") then return false elseif slotname == "Weapon" and (you.god() == "the Shining One" and not you.one_time_ability_used() or you.god() ~= "the Shining One" and TSO_CONVERSION) and not item_is_sit_dominated(it, "bless") then return false end local minv,maxv = equip_value(it) if maxv <= 0 then return true end local num_slots = 1 if slotname == "Ring" then num_slots = max_rings() end for it2 in inventory() do if equip_slot(it2) == slotname and slot(it2) ~= slot(it) then local minv2,maxv2 = equip_value(it2) if minv2 >= maxv or minv2 >= minv and maxv2 >= maxv and resist_dominated(it,it2) then num_slots = num_slots - 1 if num_slots == 0 then return true end end end end return false end function should_drop(it) return item_is_dominated(it) end -- assumes old_it is equipped function should_upgrade(it,old_it,sit) if not old_it then return should_equip(it,sit) end if not it.fully_identified and not should_drop(it) then if equip_slot(it) == "Weapon" and it.weap_skill ~= wskill() then return (count_item("scroll", "remove curse") > 0) end return (old_it.subtype() ~= "amulet of faith") end return (equip_value(it,true,old_it,sit) > equip_value(old_it,true,old_it,sit)) end -- assumes it is not equipped and an empty slot is available function should_equip(it,sit) return (equip_value(it,true,nil,sit) > 0) end -- assumes it is equipped function should_remove(it) return (equip_value(it,true,it) <= 0) end function want_missile(it) st = it.subtype() if not no_spells and st == "stone" and starting_spell() == "Sandblast" then return true end return (st == "large rock" and (you.race() == "Troll" or you.race() == "Ogre") or st == "javelin" and you.xl() < 21 or st == "tomahawk" and you.xl() < 15) end function autopickup(it, name) if name:find("of Zot") then return true end if it.is_useless then return false end local class = it.class(true) old_value = 0 new_value = 0 ring = false if class == "armour" or class == "weapon" or class == "jewellery" then return not item_is_dominated(it) elseif class == "food" or class == "gold" then return true elseif class == "potion" then return want_potion(it) elseif class == "scroll" then return want_scroll(it) elseif class == "wand" then return want_wand(it) elseif class == "missile" then return want_missile(it) else return false end end clear_autopickup_funcs() add_autopickup_func(autopickup) ------------------------------ -- some tables with hardcoded data about branches/portals/monsters: -- branch data: interlevel travel code, where name local branch_data = { {"T", "Temple"}, {"O", "Orc"}, --{"E", "Elf"}, {"L", "Lair"}, {"S", "Swamp"}, {"A", "Shoals"}, {"P", "Snake"}, {"N", "Spider"}, {"M", "Slime"}, {"V", "Vaults"}, {"C", "Crypt"}, {"W", "Tomb"}, {"D", "D:"}, {"U", "Depths"}, {"H", "Hell"}, {"I", "Dis"}, {"G", "Geh"}, {"X", "Coc"}, {"Y", "Tar"}, {"Z", "Zot"}, } -- hack -- portal data: where name, full name, feature name local portal_data = { --{"Bailey", "a flagged portal", "bailey"}, {"Bazaar", "gateway to a bazaar", "bazaar"}, --{"IceCv", "a frozen archway", "ice_cave"}, {"Ossuary", "covered staircase", "ossuary"}, {"Sewer", "a glowing drain", "sewer"}, --{"Volcano", "a dark tunnel", "volcano"}, --{"WizLab", "a magical portal", "wizlab"}, --{"Desolation", "ruined gateway", "desolation"}, --{"Lab", "labyrinth entrance", "labyrinth"}, } -- hack -- monster lists: XL cutoff (>= to ignore), monster name (or function) function in_desc(str) return function (m) return m:desc():find(str) end end function pan_lord(m) return (m:type() == ENUM_MONS_PANDEMONIUM_LORD) end local res_func_table = { rF=you.res_fire, rC=you.res_cold, rPois=you.res_poison, rElec=you.res_shock, rN=you.res_draining, rCorr=(function() return you.res_corr() and 1 or 0 end), -- returns a boolean --MR=you.res_magic, -- (doesn't exist yet...) } --hack function check_resist(name,resist,value) return function (m) return (m:name() == name and res_func_table[resist]() < value) end end function slow_berserk(name) return function (m) return (m:name() == name and count_nearby(0,0,1) > 0) end end function hydra_weapon_status(weap) if not weap then return 0 end local sk = weap.weap_skill if sk == "Maces & Flails" or sk == "Short Blades" or sk == "Polearms" and weap.hands == 1 then return 0 elseif weap.ego() == "flaming" then return 1 else return -1 end end function hydra_check_flaming(m) if m:desc():find("hydra") then return (hydra_weapon_status(items.equipped_at("Weapon")) ~= 1) end return false end -- berserk these local scary_monsters = { {3, "Terence"}, {4, slow_berserk("worm")}, {5, "gnoll"}, {5, "ice beast"}, {5, "iguana"}, {5, "Natasha"}, {5, "Robin"}, {7, "Ijyb"}, {7, check_resist("ice beast","rC",1)}, {7, "orc wizard"}, {7, "Grinder"}, {7, "Dowan"}, {7, "Duvessa"}, {7, "Menkaure"}, {7, "Edmund"}, {7, "Blork the orc"}, {7, "Eustachio"}, {7, "gnoll sergeant"}, {10, "Prince Ribbit"}, {10, "Pikel"}, {10, "Crazy Yiuf"}, {10, "Sigmund"}, {10, "ogre"}, {10, "decayed bog body"}, {12, "two-headed ogre"}, {12, "orc priest"}, {12, "orc warrior"}, {12, "troll"}, {12, "cyclops"}, {12, "spiny frog"}, {12, "black mamba"}, {12, "Snorg"}, {12, "Harold"}, {12, "komodo dragon"}, {12, "Gastronok"}, {12, "snapping turtle"}, {12, "Urug"}, {12, "Grum"}, {12, "electric eel"}, {12, "Nergalle"}, {12, "jelly"}, {12, "manticore"}, {12, "guardian mummy"}, {12, "Psyche"}, {12, "oklob sapling"}, {13, "blink frog"}, {15, "death yak"}, {15, "Erica"}, {15, "catoblepas"}, {15, "orc knight"}, {15, "swamp worm"}, {15, in_desc("hydra")}, {17, "fire dragon"}, {17, "ice dragon"}, {17, "storm dragon"}, {17, "ogre mage"}, {17, "orc sorcerer"}, {17, "orc high priest"}, {17, "orc warlord"}, {17, "dire elephant"}, {17, "very large slime creature"}, {17, "skeletal warrior"}, {17, "Arachne"}, {17, "deep troll"}, {17, "thorn hunter"}, {17, "sun demon"}, {17, check_resist("white ugly thing","rC",1)}, {17, check_resist("white very ugly thing","rC",1)}, {20, "nagaraja"}, {20, "naga sharpshooter"}, {20, "emperor scorpion"}, {20, "Donald"}, {20, "Rupert"}, {20, "Aizul"}, {20, hydra_check_flaming}, {20, "Azrael"}, {20, "Frances"}, {20, "Saint Roka"}, {20, "Agnes"}, {20, "Jory"}, {20, "Nikola"}, {20, "stone giant"}, {20, "fire giant"}, {20, "frost giant"}, {20, "acid blob"}, {20, "azure jelly"}, {20, "Asterion"}, {20, "spriggan defender"}, {20, "spriggan air mage"}, {20, "spriggan druid"}, {20, "deep troll shaman"}, {20, "Xtahua"}, {20, "tengu reaver"}, {20, "ettin"}, {20, "Polyphemus"}, {20, "Bai Suzhen"}, {24, check_resist("storm dragon","rElec",1)}, {24, check_resist("fire giant","rF",1)}, {24, check_resist("frost giant","rC",1)}, {24, check_resist("azure jelly","rC",1)}, {100, check_resist("Xtahua","rF",1)}, {100, "deep elf annihilator"}, {100, "deep elf sorcerer"}, {100, "the Enchantress"}, {100, "Vashnia"}, {100, "Sojobo"}, {100, in_desc("berserk[^e]")}, {100, "Roxanne"}, {100, "Erolcha"}, {100, in_desc("statue")}, {100, "Nessos"}, {100, "Sonja"}, {100, "Louise"}, {100, "Mennas"}, {100, check_resist("Margery","rF",2)}, {100, "Frederick"}, {100, "Boris"}, {100, "Mara"}, {100, "boggart"}, {100, "lich"}, {100, "ancient lich"}, {100, in_desc("'s ghost")}, {100, in_desc("' ghost")}, {100, in_desc("'s illusion")}, {100, in_desc("' illusion")}, {100, "oklob plant"}, {100, "hellion"}, {100, "tormentor"}, {100, "Hell Sentinel"}, {100, "Ice Fiend"}, {100, "Tzitzimitl"}, {100, "Brimstone Fiend"}, {100, "curse toe"}, {100, "curse skull"}, {100, "Tiamat"}, {100, "titanic slime creature"}, {100, "enormous slime creature"}, {100, "titan"}, {100, "orb of fire"}, {100, "caustic shrike"}, {100, "seraph"}, {100, "juggernaut"}, {100, "Royal Jelly"}, {100, "iron giant"}, {100, "Cerebov"}, {100, "Gloorx Vloq"}, {100, "Lom Lobon"}, {100, "Mnoleg"}, {100, pan_lord}, {100, "Dispater"}, {100, "Asmodeus"}, {100, "Antaeus"}, {100, "Ereshkigal"}, {100, "greater mummy"}, {100, "Khufu"}, } -- hack -- BiA these even at low piety local bia_necessary_monsters = { {15, hydra_check_flaming}, {20, "orb spider"}, {100, in_desc("statue")}, } -- hack -- BiA these local bia_monsters = { {15, "Rupert"}, {15, "Azrael"}, {15, "fire dragon"}, {15, "ice dragon"}, {15, "Snorg"}, {15, "death yak"}, {15, "red devil"}, {17, hydra_check_flaming}, {17, "Nikola"}, {20, "orc warlord"}, {20, "Aizul"}, {20, "Frances"}, {20, "Saint Roka"}, {20, "Agnes"}, {20, "Jory"}, {20, "Arachne"}, {20, check_resist("Nikola","rElec",1)}, {20, "Vashnia"}, {20, "Asterion"}, {20, "orb spider"}, {20, "thorn hunter"}, {20, check_resist("sun demon","rF",1)}, {20, "Polyphemus"}, {20, "Ilsuiw"}, {20, "Bai Suzhen"}, {20, "merfolk avatar"}, {100, "deep troll shaman"}, {100, check_resist("spriggan air mage","rElec",1)}, {100, "the Enchantress"}, {100, "Sojobo"}, {100, "Roxanne"}, {100, "Erolcha"}, {100, in_desc("statue")}, {100, "Nessos"}, {100, "Sonja"}, {100, "Louise"}, {100, "Mennas"}, {100, "Margery"}, {100, "Frederick"}, {100, "Boris"}, {100, "Mara"}, {100, "boggart"}, {100, "lich"}, {100, "ancient lich"}, {100, in_desc("'s ghost")}, {100, in_desc("' ghost")}, {100, "oklob plant"}, {100, "Hell Sentinel"}, {100, "Ice Fiend"}, {100, "Brimstone Fiend"}, {100, "Tzitzimitl"}, {100, "Tiamat"}, {100, "orb of fire"}, {100, "caustic shrike"}, {100, "seraph"}, {100, "Royal Jelly"}, {100, check_resist("spark wasp","rElec",1)}, {100, "juggernaut"}, {100, "iron giant"}, {100, check_resist("entropy weaver", "rCorr", 1)}, {100, "Cerebov"}, {100, "Gloorx Vloq"}, {100, "Lom Lobon"}, {100, "Mnoleg"}, {100, pan_lord}, {100, "Dispater"}, {100, "Asmodeus"}, {100, "Antaeus"}, {100, "Ereshkigal"}, {100, "greater mummy"}, {100, "Khufu"}, } -- hack -- Use haste/might on these few local ridiculous_uniques = { {100, "Antaeus"}, {100, "Asmodeus"}, {100, "Lom Lobon"}, } -- hack -- Trog's Hand these local hand_monsters = { {10, "Grinder"}, {17, "orc sorcerer"}, {17, "wizard"}, {100, "ogre mage"}, {100, "Rupert"}, {100, "Xtahua"}, {100, "Aizul"}, {100, "Erolcha"}, {100, "Louise"}, {100, "lich"}, {100, "ancient lich"}, {100, "Kirke"}, {100, "golden eye"}, {100, "deep elf sorcerer"}, {100, "deep elf demonologist"}, {100, "sphinx"}, {100, "great orb of eyes"}, {100, "vault sentinel"}, {100, "the Enchantress"}, {100, "satyr"}, {100, "vampire knight"}, {100, "siren"}, {100, "merfolk avatar"}, } -- hack -- potion of resistance these local fire_resistance_monsters = { {100, check_resist("Margery","rF",2)}, {100, "orb of fire"}, {100, check_resist("hellephant","rF",2)}, {100, check_resist("Xtahua","rF",2)}, {100, "Cerebov"}, {100, check_resist("Asmodeus","rF",2)}, } -- hack local cold_resistance_monsters = { {100, "Ice Fiend"}, {100, "Antaeus"}, } -- hack local elec_resistance_monsters = { {20, "storm dragon"}, {20, in_desc("black draconian")}, {100, "electric golem"}, {100, "spark wasp"}, {100, "Antaeus"}, } -- hack local pois_resistance_monsters = { {100, "swamp drake"}, } -- hack ----------------------------------------- -- player functions -- "intrinsics" that shouldn't change over the course of the game: function intrinsic_rpois() local sp = you.race() if sp == "Gargoyle" or sp == "Naga" or sp == "Ghoul" or sp == "Mummy" then return true end return false end function intrinsic_relec() local sp = you.race() if sp == "Gargoyle" then return true end return false end function intrinsic_sinv() local sp = you.race() if sp == "Naga" or sp == "Felid" or sp == "Formicid" or sp == "Vampire" then return true end -- we assume TSO piety won't drop below 2* and that we won't change gods -- away from TSO if you.god() == "the Shining One" and you.piety_rank() >= 2 then return true end return false end function intrinsic_flight() -- or swimming local sp = you.race() if sp == "Gargoyle" or sp == "Tengu" or sp == "Black Draconian" or sp == "Merfolk" or sp == "Octopode" or sp == "Barachi" then return true end return false end function intrinsic_fumble() local sp = you.race() if sp == "Merfolk" or sp == "Octopode" or sp == "Grey Draconian" or sp == "Centaur" or sp == "Naga" or sp == "Troll" or sp == "Ogre" or sp == "Barachi" then return false end return true end function intrinsic_evil() local sp = you.race() if sp == "Demonspawn" or sp == "Mummy" or sp == "Ghoul" or sp == "Vampire" then return true end return false end -- not exactly gourmand, but close enough function intrinsic_gourmand() return (you.race() == "Kobold" or you.race() == "Troll" or you.race() == "Felid") end function intrinsic_undead() return (you.race() == "Ghoul" or you.race() == "Mummy" or you.race() == "Vampire") end -- We group all species into four categories: -- heavy: species that can use arbitrary armour and aren't particularly great -- at dodging -- dodgy: species that can use arbitrary armour but are very good at dodging -- large: species with armour restrictions that want heavy dragon scales -- light: species with no body armour or who don't want anything heavier than -- 7 encumbrance function armour_plan() local sp = you.race() if sp == "Ogre" or sp == "Troll" then return "large" elseif sp == "Deep Elf" or sp == "Halfling" or sp == "Kobold" or sp == "Merfolk" then return "dodgy" elseif sp:find("Draconian") or sp == "Felid" or sp == "Octopode" or sp == "Spriggan" then return "light" else return "heavy" end end function expected_armour_multiplier() local ap = armour_plan() if ap == "heavy" then return 2 elseif ap == "large" or ap == "dodgy" then return 1.5 else return 1.25 end end function unfitting_armour() local sp = you.race() return (armour_plan() == "large" or sp == "Centaur" or sp == "Naga") end function want_buckler() if you.race() == "Felid" then return false end if SHIELD_CRAZY then return true end if wskill() == "Short Blades" or wskill() == "Unarmed Combat" then return true end if you.race() == "Formicid" or you.race() == "Halfling" or you.race() == "Kobold" then return true end return false end function want_shield() if not want_buckler() then return false end if SHIELD_CRAZY then return true end return (you.race() == "Troll" or you.race() == "Formicid") end -- used for backgrounds who don't get to choose a weapon function weapon_choice() sp = you.race() if sp == "Felid" or sp == "Troll" or sp == "Ghoul" then return "Unarmed Combat" elseif sp == "Halfling" then return "Long Blades" elseif sp == "Ogre" or sp == "Kobold" then return "Maces & Flails" elseif sp == "Merfolk" then return "Polearms" elseif sp == "Spriggan" then return "Short Blades" else return "Axes" end end function wskill() -- cache in case you unwield a weapon somehow if c_persist.cached_wskill then return c_persist.cached_wskill end weap = items.equipped_at("Weapon") if weap and weap.class(true) == "weapon" and weap.weap_skill ~= "Short Blades" and you.class() ~= "Wanderer" then c_persist.cached_wskill = weap.weap_skill else c_persist.cached_wskill = weapon_choice() end return c_persist.cached_wskill end function max_rings() if you.race() == "Octopode" then return 8 else return 2 end end -- other player functions function hp_is_low(percentage) local hp, mhp = you.hp() return (100*hp <= percentage*mhp) end function chp() local hp, mhp = you.hp() return hp end function cmp() local mp, mmp = you.mp() return mp end function meph_immune() -- should also check clarity and unbreathing return (you.res_poison() >= 1) end function miasma_immune() -- this isn't all the cases, I know return (you.race() == "Gargoyle" or you.race() == "Vine Stalker" or you.race() == "Ghoul" or you.race() == "Mummy") end function is_waypointable(loc) return (not is_portal_location(loc) and not loc:find("Abyss") and loc ~= "Pan" and not loc:find("Zig")) end function is_portal_location(loc) for _, value in ipairs(portal_data) do if value[1] == loc then return true end end return false end function in_portal() return is_portal_location(where) end function get_feat_name(where_name) for _, value in ipairs(portal_data) do if where_name == value[1] then return value[3] end end end function cur_branch() for _, value in ipairs(branch_data) do if where:find(value[2]) then return value[1] end end end function found_branch(br) if br == "D" then return true end for _, value in ipairs(branch_data) do if value[1] == br then if travel.find_deepest_explored(value[2]) > 0 then return true else return false end end end return false end function in_branch(br) for _, value in ipairs(branch_data) do if value[1] == br then if where:find(value[2]) then return true else return false end end end return false end function is_traversable(x,y) local feat = view.feature_at(x,y) return feat ~= "unseen" and travel.feature_traversable(feat) end function is_cornerish(x,y) if is_traversable(x+1,y+1) or is_traversable(x+1,y-1) or is_traversable(x-1,y+1) or is_traversable(x-1,y-1) then return false end return ((is_traversable(x+1,y) or is_traversable(x-1,y)) and (is_traversable(x,y+1) or is_traversable(x,y-1))) end function is_solid(x,y) local feat = view.feature_at(x,y) return feat == "unseen" or travel.feature_solid(feat) end function dangerous_to_rest() if danger then return true end for x = -1,1 do for y = -1,1 do if view.feature_at(x,y) == "slimy_wall" then return true end end end return false end function starving() return (you.hunger_name() == "starving" or you.hunger_name() == "fainting") end function transformed() return (you.transform() ~= "") end function can_read() if you.berserk() or you.confused() or you.silenced() or you.status("engulfed (cannot breathe)") then return false end return true end function can_drink() if you.berserk() or you.race() == "Mummy" or you.transform() == "bat" or you.transform() == "lich" or you.status("no potions") then return false end return true end function can_zap() if you.berserk() or you.confused() or transformed() then return false end if you.mutation("inability to use devices") > 0 then return false end local x = you.mutation("MP-powered wands") if x > 0 then if cmp() < 3*x then return false end end return true end function can_berserk() return (not (you.berserk() or you.confused() or you.silenced() or you.status("berserk cooldown") or you.mesmerised() or too_hungry_to_berserk() or you.piety_rank() < 1 or you.god() ~= "Trog" or you.transform() == "tree" or you.transform() == "wisp" or you.transform() == "lich" or you.status("afraid") or you.race() == "Mummy" or you.race() == "Ghoul" or you.race() == "Formicid")) end function can_heroism() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.god() ~= "Okawaru" or you.piety_rank() < 1 or you.status("heroism") or cmp() < 2 or you.under_penance())) end function can_recall() if (you.god() ~= "Yredelemnul" or you.piety_rank() < 2) and (you.god() ~= "Beogh" or you.piety_rank() < 4) then return false end return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.status("recalling") or cmp() < 2)) end function can_recall_ancestor() if you.god() ~= "Hepliaklqana" then return false end return (not (you.berserk() or you.confused() or you.silenced() or starving() or cmp() < 2)) end function can_finesse() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.god() ~= "Okawaru" or you.piety_rank() < 5 or you.status("finesse") or cmp() < 5 or you.under_penance())) end function can_slouch() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.god() ~= "Cheibriados" or you.piety_rank() < 4 or cmp() < 5)) end function can_drain_life() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.god() ~= "Yredelemnul" or you.piety_rank() < 4 or cmp() < 6)) end function can_hand() return (not (you.berserk() or you.confused() or you.silenced() or you.regenerating() or starving() or you.piety_rank() < 2 or you.god() ~= "Trog")) end function dd_heal_ability() if you.race() == "Deep Dwarf" and you.base_mp() > 0 and not (you.berserk() or you.confused() or starving()) then use_ability("Heal Wounds") return true end return false end function can_ely_healing() return (not (you.berserk() or you.silenced() or starving() or you.piety_rank() < 4 or you.god() ~= "Elyvilon" or cmp() < 2)) end function can_purification() return (not (you.berserk() or you.silenced() or starving() or you.piety_rank() < 3 or you.god() ~= "Elyvilon" or cmp() < 3)) end function can_recite() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.status("reciting") or you.piety_rank() < 1 or you.god() ~= "Zin")) end function can_ru_healing() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.exhausted() or you.piety_rank() < 3 or you.god() ~= "Ru")) end function can_apocalypse() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.exhausted() or you.piety_rank() < 5 or cmp() < 8 or you.god() ~= "Ru")) end function can_grand_finale() -- using berserk hunger check as a hack for now return (not (you.berserk() or you.confused() or you.silenced() or too_hungry_to_berserk() or you.piety_rank() < 5 or cmp() < 8 or you.god() ~= "Uskayaw")) end function can_bia() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.piety_rank() < 4 or you.god() ~= "Trog")) end function can_sgd() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.piety_rank() < 5 or chp() <= 10 or you.god() ~= "Makhleb")) end function can_divine_warrior() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.piety_rank() < 5 or cmp() < 8 or you.god() ~= "the Shining One")) end function can_destruction() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.piety_rank() < 4 or chp() <= 6 or you.god() ~= "Makhleb")) end function can_teleport() return (not you.berserk() and not you.teleporting() and not you.anchored() and not you.confused() and you.transform() ~= "tree" and you.race() ~= "Formicid") end function too_hungry_to_berserk() if you.race() == "Vampire" then return (you.hunger_name() ~= "almost alive" and you.hunger_name() ~= "very full" and you.hunger_name() ~= "full") end return (starving() or you.hunger_name() == "near starving" or you.hunger_name() == "very hungry") end function player_speed_num() local num = 3 if you.god() == "Cheibriados" then num = 1 elseif you.race() == "Spriggan" or you.race() == "Centaur" then num = 4 elseif you.race() == "Naga" then num = 2 end if you.hasted() or you.berserk() then num = num + 1 end if you.slowed() then num = num - 1 end return num end ----------------------------------------- -- monster functions function mon_speed_num(m) local sdesc = m:speed_description() local num if sdesc == "extremely fast" then num = 6 elseif sdesc == "very fast" then num = 5 elseif sdesc == "fast" then num = 4 elseif sdesc == "normal" then num = 3 elseif sdesc == "slow" then num = 2 elseif sdesc == "very slow" then num = 1 end if m:status("fast") then num = num + 1 end if m:status("slow") then num = num - 1 end if m:name():find("spriggan") or m:name() == "the Enchantress" then num = num + 1 elseif m:name():find("naga") or m:name() == "Vashnia" then num = num - 1 end return num end function is_fast(m) return (mon_speed_num(m) > player_speed_num()) end function is_ranged(m) local name = m:name() if name:find("kraken") then return false end if m:has_known_ranged_attack() then return true end if name == "Maurice" or name == "Ijyb" or name == "crimson imp" then return true end return false end function sense_immediate_danger() local e for _,e in ipairs(enemy_list) do if supdist(e.x,e.y) <= 2 then return true elseif supdist(e.x,e.y) <= 3 and e.m:reach_range() >= 2 then return true elseif is_ranged(e.m) then return true end end return false end function sense_danger(r, no_ignored) local e for _,e in ipairs(enemy_list) do if supdist(e.x,e.y) <= r then if (not no_ignored) or is_candidate_for_attack(e.x,e.y,no_ignored) then return true end end end return false end function sense_sigmund() local e for _,e in ipairs(enemy_list) do if e.m:name() == "Sigmund" then sigmund_dx = e.x sigmund_dy = e.y return end end end function initialize_monster_array() monster_array = {} local x for x = -LOS,LOS do monster_array[x] = {} end end function update_monster_array() local x,y enemy_list = {} --c_persist.mlist = {} for x = -LOS,LOS do for y = -LOS,LOS do monster_array[x][y] = monster.get_monster_at(x, y) if is_candidate_for_attack(x, y) then entry = {} entry.x = x entry.y = y entry.m = monster_array[x][y] table.insert(enemy_list, entry) --table.insert(c_persist.mlist, entry.m:name()) end end end end function check_monsters(r, mlist) local e local xl = you.xl() for _,e in ipairs(enemy_list) do if supdist(e.x,e.y) <= r then if not contains_string_in(e.m:name(), {"skeleton", "zombie", "simulacrum", "spectral"}) then local name = e.m:name() local desc = e.m:desc() for _, value in ipairs(mlist) do if xl < value[1] then if type(value[2]) == "string" then if value[2] == name then return true end elseif value[2](e.m) then return true end end end end end end return false end function count_bia(r) if you.god() ~= "Trog" then return 0 end local x, y local i = 0 for x = -r,r do for y = -r,r do m = monster_array[x][y] if m and m:is_safe() and m:is("berserk") and contains_string_in(m:name(), {"ogre","giant","bear","troll"}) then i = i+1 end end end return i end function count_elliptic(r) if you.god() ~= "Hepliaklqana" then return 0 end local x, y local i = 0 for x = -r,r do for y = -r,r do m = monster_array[x][y] if m and m:is_safe() and contains_string_in(m:name(), {"elliptic"}) then i = i+1 end end end return i end function count_sgd(r) if you.god() ~= "Makhleb" then return 0 end local x, y local i = 0 for x = -r,r do for y = -r,r do m = monster_array[x][y] if m and m:is_safe() and contains_string_in(m:name(), {"Executioner","green death","blizzard demon","balrug","cacodemon"}) then i = i+1 end end end return i end function count_divine_warrior(r) if you.god() ~= "the Shining One" then return 0 end local x, y local i = 0 for x = -r,r do for y = -r,r do m = monster_array[x][y] if m and m:is_safe() and contains_string_in(m:name(), {"angel","daeva"}) then i = i+1 end end end return i end function count_hostile_sgd(r) if you.god() ~= "Makhleb" then return 0 end local x, y local i = 0 for x = -r,r do for y = -r,r do m = monster_array[x][y] if m and not m:is_safe() and m:is("summoned") and contains_string_in(m:name(), {"Executioner","green death","blizzard demon","balrug","cacodemon"}) then i = i+1 end end end return i end function count_big_slimes(r) local x, y local i = 0 for x = -r,r do for y = -r,r do m = monster_array[x][y] if m and not m:is_safe() and contains_string_in(m:name(), {"enormous slime creature","titanic slime creature"}) then i = i+1 end end end return i end function count_hellions(r) local x, y local i = 0 for x = -r,r do for y = -r,r do m = monster_array[x][y] if m and not m:is_safe() and m:name() == "hellion" then i = i+1 end end end return i end function count_daevas(r) local x, y local i = 0 for x = -r,r do for y = -r,r do m = monster_array[x][y] if m and not m:is_safe() and m:name() == "daeva" then i = i+1 end end end return i end function count_pan_lords(r) local e local i = 0 for _,e in ipairs(enemy_list) do if e.m:type() == ENUM_MONS_PANDEMONIUM_LORD then i = i+1 end end return i end -- should only be called for adjacent squares function monster_in_way(dx,dy) m = monster_array[dx][dy] return (m and (m:attitude() <= ATT_NEUTRAL and not lair_step_mode or m:attitude() > ATT_NEUTRAL and (m:is_constricted() or m:is_caught() or m:status("petrified") or m:status("paralysed") or m:desc():find("sleeping") or view.feature_at(0,0) == "deep_water" or view.feature_at(0,0) == "lava" or view.feature_at(0,0) == "trap_zot"))) end function tabbable_square(x,y) if view.feature_at(x,y) ~= "unseen" and view.is_safe_square(x,y) then local m = monster_array[x][y] if not m or not m:is_firewood() then return true end end return false end function mons_tabbable_square(x,y) local feat = view.feature_at(x,y) return (feat ~= "deep_water" and feat ~= "lava" and not is_solid(x,y)) end function try_move(dx, dy) if view.is_safe_square(dx, dy) and not view.withheld(dx, dy) and not monster_in_way(dx,dy) then return delta_to_vi(dx, dy) else return nil end end function will_tab(cx, cy, ex, ey, square_func) local dx = ex - cx local dy = ey - cy if abs(dx) <= 1 and abs(dy) <= 1 then return true end local function attempt_move(fx, fy) if fx == 0 and fy == 0 then return end if supdist(cx+fx,cy+fy) > LOS then return end if square_func(cx+fx, cy+fy) then return will_tab(cx+fx, cy+fy, ex, ey, square_func) end end local move = nil if abs(dx) > abs(dy) then if abs(dy) == 1 then move = attempt_move(sign(dx), 0) end if move == nil then move = attempt_move(sign(dx), sign(dy)) end if move == nil then move = attempt_move(sign(dx), 0) end if move == nil and abs(dx) > abs(dy)+1 then move = attempt_move(sign(dx), 1) end if move == nil and abs(dx) > abs(dy)+1 then move = attempt_move(sign(dx), -1) end if move == nil then move = attempt_move(0, sign(dy)) end elseif abs(dx) == abs(dy) then move = attempt_move(sign(dx), sign(dy)) if move == nil then move = attempt_move(sign(dx), 0) end if move == nil then move = attempt_move(0, sign(dy)) end else if abs(dx) == 1 then move = attempt_move(0, sign(dy)) end if move == nil then move = attempt_move(sign(dx), sign(dy)) end if move == nil then move = attempt_move(0, sign(dy)) end if move == nil and abs(dy) > abs(dx)+1 then move = attempt_move(1, sign(dy)) end if move == nil and abs(dy) > abs(dx)+1 then move = attempt_move(-1, sign(dy)) end if move == nil then move = attempt_move(sign(dx), 0) end end if move == nil then return false end return move end function get_monster_info(dx, dy) m = monster_array[dx][dy] if not m then return nil end name = m:name() info = {} info.distance = (abs(dx) > abs(dy)) and -abs(dx) or -abs(dy) if not have_reaching() then info.attack_type = (-info.distance < 2) and 2 or 0 else if -info.distance > 2 then info.attack_type = 0 elseif -info.distance < 2 then info.attack_type = 2 elseif you.caught() or you.confused() then info.attack_type = 0 else info.attack_type = view.can_reach(dx, dy) and 1 or 0 end end info.can_attack = (info.attack_type > 0) and 1 or 0 info.safe = m:is_safe() and -1 or 0 info.constricting_you = m:is_constricting_you() and 1 or 0 info.very_stabbable = (m:stabbability() >= 1) and 1 or 0 -- info.stabbable = m:is(0) and 1 or 0 info.injury = m:damage_level() info.threat = m:threat() info.orc_priest_wizard = (name == "orc priest" or name == "orc wizard") and 1 or 0 return info end function compare_monster_info(m1, m2) flag_order = {"can_attack", "safe", "distance", "constricting_you", "very_stabbable", "injury", "threat", "orc_priest_wizard"} for i, flag in ipairs(flag_order) do if m1[flag] > m2[flag] then return true end if m1[flag] < m2[flag] then return false end end return false end function is_candidate_for_attack(x, y, no_untabbable) if supdist(x,y) > LOS then return false end m = monster_array[x][y] if not m or m:attitude() > ATT_NEUTRAL then return false end if m:name() == "butterfly" or m:name() == "orb of destruction" then return false end if m:is_firewood() then if not string.find(m:name(), "ballistomycete") then return false end end if no_untabbable then if will_tab(0,0,x,y,tabbable_square) then remove_ignore(x,y) else add_ignore(x,y) return false end end return true end function count_ranged(cx, cy, r) local e local i = 0 for _,e in ipairs(enemy_list) do local dist = supdist(cx-e.x,cy-e.y) if dist > 1 and dist <= r then if dist == 2 and is_fast(e.m) or (is_ranged(e.m) or dist == 2 and e.m:reach_range() >= 2) and view.cell_see_cell(cx,cy,e.x,e.y) then i = i+1 end end end return i end function count_longranged(cx, cy, r) local e local i = 0 for _,e in ipairs(enemy_list) do local dist = supdist(cx-e.x,cy-e.y) if dist >= 4 and dist <= r then if is_ranged(e.m) and view.cell_see_cell(cx,cy,e.x,e.y) and not (e.m:desc():find("wandering") or e.m:desc():find("sleeping") or e.m:desc():find("dormant") or e.m:name() == "electric eel" or e.m:name() == "lava snake" or e.m:is_stationary() or e.m:desc():find("stupefied")) then if will_tab(e.x,e.y,0,0,mons_tabbable_square) then i = i+1 end end end end return i end function count_shortranged(cx, cy, r) local e local i = 0 for _,e in ipairs(enemy_list) do local dist = supdist(cx-e.x,cy-e.y) if dist <= r and is_ranged(e.m) then i = i+1 end end return i end function count_unalert(cx, cy, r) local e local i = 0 for _,e in ipairs(enemy_list) do local dist = supdist(cx-e.x,cy-e.y) if dist <= r and dist > 1 and view.cell_see_cell(cx,cy,e.x,e.y) then if e.m:desc():find("wandering") and not e.m:desc():find("mushroom") or e.m:desc():find("sleeping") or e.m:desc():find("dormant") then i = i+1 end end end return i end function estimate_slouch_damage() local e local count = 0 local s,v for _,e in ipairs(enemy_list) do if you.see_cell_no_trans(e.x,e.y) then s = mon_speed_num(e.m) v = 0 if s >= 6 then v = 3 elseif s == 5 then v = 2.5 elseif s == 4 then v = 1.5 elseif s == 3 then v = 1 end if e.m:name() == "orb of fire" then v = v + 1 elseif v > 0 and e.m:threat() <= 1 then v = 0.5 end count = count + v end end return count end function count_drainable() local e local count = 0 for _,e in ipairs(enemy_list) do if you.see_cell_no_trans(e.x,e.y) and e.m:res_draining() == 0 then count = count + 1 end end return count end function count_nearby(cx, cy, r) local x, y local i = 0 for x = -r,r do for y = -r,r do if supdist(x,y) > 0 and is_candidate_for_attack(cx+x, cy+y) then i = i+1 end end end return i end function count_slow_nearby(cx, cy, r) local x, y local i = 0 for x = -r,r do for y = -r,r do if supdist(x,y) > 0 and is_candidate_for_attack(cx+x, cy+y) then m = monster_array[cx+x][cy+y] if mon_speed_num(m) < player_speed_num() and not is_ranged(m) and m:reach_range() < 2 then i = i+1 end end end end return i end function distance_to_enemy(cx, cy) local dist = 10 local e for _,e in ipairs(enemy_list) do if supdist(cx-e.x,cy-e.y) < dist then dist = supdist(cx-e.x,cy-e.y) end end return dist end function distance_to_tabbable_enemy(cx, cy) local dist = 10 local e for _,e in ipairs(enemy_list) do if supdist(cx-e.x,cy-e.y) < dist then if will_tab(e.x,e.y,0,0,mons_tabbable_square) then dist = supdist(cx-e.x,cy-e.y) end end end return dist end ----------------------------------------- -- item functions function inventory() return iter.invent_iterator:new(items.inventory()) end function at_feet() return iter.invent_iterator:new(you.floor_items()) end function free_inventory_slots() local slots = 52 for _ in inventory() do slots = slots - 1 end return slots end function slot(x) if type(x) == "userdata" then return x.slot elseif type(x) == "string" then return items.letter_to_index(x) else return x end end function letter(x) if type(x) == "userdata" then return items.index_to_letter(x.slot) elseif type(x) == "number" then return items.index_to_letter(x) else return x end end function item(x) if type(x) == "number" then return items.inslot(x) elseif type(x) == "string" then return items.inslot(items.letter_to_index(x)) else return x end end function ring_list() rings = {} if you.race() ~= "Octopode" then if items.equipped_at("Left Ring") then table.insert(rings, items.equipped_at("Left Ring")) end if items.equipped_at("Right Ring") then table.insert(rings, items.equipped_at("Right Ring")) end return rings end for it in inventory() do if it.equipped and equip_slot(it) == "Ring" then table.insert(rings, it) end end return rings end function empty_ring_slots() return max_rings() - table.getn(ring_list()) end function have_two_hander() for it in inventory() do if it.class(true) == "weapon" and it.weap_skill == wskill() and it.hands == 2 then return true end end return false end function find_item(cls,name) if cls == "wand" then return find_wand(name) end for it in inventory() do if it.class(true) == cls and it.name():find(name) then return items.index_to_letter(it.slot) end end end function have_item(cls,name) for it in inventory() do if it.class(true) == cls and it.name():find(name) then return true end end end function find_wand(name) for it in inventory() do if it.class(true) == "wand" and it.name():find(name) and not it.name():find("empty") and (it.plus == nil or it.plus > 0) then return items.index_to_letter(it.slot) end end end function count_item(cls,name) f = find_item(cls, name) if f then return item(f).quantity end return 0 end function have_reaching() local wp = items.equipped_at("weapon") return wp and wp.reach_range == 2 and not wp.is_melded end function body_size() if you.race() == "Kobold" or you.race() == "Halfling" then return -1 elseif you.race() == "Spriggan" or you.race() == "Felid" then return -2 elseif you.race() == "Troll" or you.race() == "Ogre" or you.race() == "Naga" or you.race() == "Centaur" then return 1 else return 0 end end function target_shield_skill() local shield = items.equipped_at("Shield") if not shield then return 0 end if shield.encumbrance == 0 and not want_buckler() or shield.encumbrance ~= 0 and not want_shield() then return 0 end enc = shield.encumbrance > 0 and shield.encumbrance or 0.8 local size_factor = 5 - 2 * body_size() if you.race() == "Formicid" then size_factor = size_factor - 2 end return enc * size_factor end function at_target_shield_skill() return (you.base_skill("Shields") >= min(27, target_shield_skill() + (you.god() == "Ru" and 1 or 0))) end function min_delay_skill() weap = items.equipped_at("Weapon") if not weap then return 27 end if weap.weap_skill ~= wskill() then return last_min_delay_skill end if weap.weap_skill == "Short Blades" and weap.delay == 12 then last_min_delay_skill = 14 return 14 end local mindelay = math.floor(weap.delay / 2) if mindelay > 7 then mindelay = 7 end last_min_delay_skill = 2 * (weap.delay - mindelay) return last_min_delay_skill end function at_min_delay() return (you.base_skill(wskill()) >= min(27, min_delay_skill() + (you.god() == "Ru" and 1 or 0))) end function cleaving() weap = items.equipped_at("Weapon") if weap and weap.weap_skill == "Axes" then return true end return false end function armour_ac() arm = items.equipped_at("Armour") if arm then return arm.ac else return 0 end end function base_ac() local total = 0 for _,slotname in pairs(good_slots) do if slotname ~= "Shield" then it = items.equipped_at(slotname) if it then total = total + it.ac end end end return total end function armour_evp() arm = items.equipped_at("Armour") if arm then return arm.encumbrance else return 0 end end function on_corpses() for it in at_feet() do if string.find(it.name(), "corpse") then return true end end return false end function on_bottleable_corpses() for it in at_feet() do if string.find(it.name(), "corpse") and food.bottleable(it) then return true end end return false end function on_dangerous_corpse() for it in at_feet() do if string.find(it.name(), "corpse") then return food.dangerous(it) end end return false end function on_edible_corpse() for it in at_feet() do if it.name():find("corpse") and (not it.name():find("noeat")) and (you.god() ~= "Beogh" or not it.name():find("orc ")) then return true end end return false end function bad_food(it) return food.dangerous(it) end function can_swap(equip_slot) local it = items.equipped_at(equip_slot) if it and it.cursed then return false end if it and it.ego() == "flying" and (view.feature_at(0,0) == "deep_water" or view.feature_at(0,0) == "lava") then return false end return true end -- plural form, e.g. "Scrolls" -- or invoke with item_class="name_callback" and provide callback for name function see_item(item_class, r, name_callback) for x = -r,r do for y = -r,r do -- crawl.mpr("(" .. x .. ", " .. y .. "): " .. view.feature_at(x,y) .."\r") local is = items.get_items_at(x, y) if (is ~= nil) and (#is > 0) and (you.see_cell(x,y)) then for ind,i in pairs(is) do local iname = i.name() if (i:class(true) == item_class) or ((item_class == "name_callback") and name_callback(iname)) then return true end end end end end return false end -- Matches all unindentified books function is_spellbook(book_name) return (book_name ~= nil) and (book_name:find("book") ~= nil) end function see_spellbooks_to_burn() return see_item("name_callback", LOS, is_spellbook) and not see_item("name_callback", 0, is_spellbook) end ----------------------------------------- -- "plans" - functions that take actions, and logic to determine which actions -- to take. -- Every function that might take an action should return as follows: -- true if tried to do something -- false if didn't do anything -- nil if should be rerun (currently only used by cascades, be careful -- of loops... this is poorly tested) function get_target() local e, bestx, besty, best_info, new_info bestx = 0 besty = 0 best_info = nil for _,e in ipairs(enemy_list) do if not util.contains(failed_move, 20*e.x+e.y) then if is_candidate_for_attack(e.x, e.y, true) then new_info = get_monster_info(e.x, e.y) if (not best_info) or compare_monster_info(new_info, best_info) then bestx = e.x besty = e.y best_info = new_info end end end end return bestx, besty, best_info end function should_rest() if you.confused() or you.berserk() or transformed() then return true end if dangerous_to_rest() then return false end if you.turns() < hiding_turn_count + 10 then if SPAM then say("Waiting for ranged monster.") end return true end return (reason_to_rest(99.9) or you.status("barbed spikes") or you.god() == "Makhleb" and you.turns() <= sgd_timer + 100 or should_ally_rest()) end -- check statuses to see whether there is something to rest off, -- does not include some things in should_rest() because they are -- not clearly good to wait out with monsters around function reason_to_rest(percentage) if not no_spells and starting_spell() then local mp,mmp = you.mp() if mp < mmp and (you.race() ~= "Deep Dwarf" or player_resist("Spirit") <= 0) then return true end end if you.god() == "Elyvilon" and you.piety_rank() >= 4 then local mp,mmp = you.mp() if mp < mmp and mp < 10 and (you.race() ~= "Deep Dwarf" or player_resist("Spirit") <= 0) then return true end end return (you.confused() or transformed() or hp_is_low(percentage) and (you.race() ~= "Deep Dwarf" and you.hunger_name() ~= "bloodless" or you.regenerating()) and (you.god() ~= "the Shining One" or hp_is_low(75) or count_divine_warrior(2) == 0) or you.slowed() or you.exhausted() or you.teleporting() or you.status("berserk cooldown") or you.status("marked") or you.silencing() or you.corrosion() > 0) end -- are we significantly stronger than usual thanks to a buff that we used? function buffed() if hp_is_low(50) or transformed() or you.corrosion() >= 2 then return false end if you.god() == "Okawaru" and (you.status("heroism") or you.status("finesse")) then return true end if you.extra_resistant() then return true end return false end function should_ally_rest() if you.god() ~= "Yredelemnul" and you.god() ~= "Beogh" then return false end if dangerous_to_rest() then return false end local x,y for x = -3,3 do for y = -3,3 do m = monster_array[x][y] if m and m:attitude() == ATT_FRIENDLY and m:damage_level() > 0 then if SPAM then say("Waiting for " .. m:name() .. " to heal.") end return true end end end return false end function want_permafood() if you.race() == "Mummy" or you.transform() == "lich" then return false end if you.race() == "Vampire" then return (you.hunger_name() ~= "almost alive" and you.hunger_name() ~= "very full" and you.hunger_name() ~= "full") end if you.race() == "Spriggan" then return (you.hunger_name() ~= "completely stuffed" and you.hunger_name() ~= "very full") end return (you.hunger_name() == "near starving" or you.hunger_name() == "very hungry" and (where == "Snake:4" or where == "Spider:4" or where == "Swamp:4" or where == "Shoals:4" or you.xl() >= 18 or intrinsic_gourmand()) or starving()) end function want_chunk() if you.race() == "Spriggan" or you.race() == "Mummy" or you.race() == "Vampire" or you.transform() == "lich" then return false end if you.race() == "Ghoul" and you.rot() > 0 then return true end if intrinsic_gourmand() and (you.hunger_name() == "not hungry" or you.hunger_name() == "full" or you.hunger_name() == "very full") then return true end return you.hunger_name() == "hungry" or you.hunger_name() == "very hungry" or want_permafood() end function rest() magic("s") next_delay = 5 end function attack() local success = false failed_move = { } while not success do bestx, besty, best_info = get_target() if best_info == nil then return false end success = make_attack(bestx, besty, best_info) end return true end function chop() magic("cYq") end function berserk() use_ability("Berserk") end function heroism() use_ability("Heroism") end function recall() if you.god() == "Yredelemnul" then use_ability("Recall Undead Slaves", "", true) else use_ability("Recall Orcish Followers", "", true) end end function recall_ancestor() use_ability("Recall Ancestor", "", true) end function finesse() use_ability("Finesse") end function slouch() use_ability("Slouch") end function drain_life() use_ability("Drain Life") end function hand() use_ability("Trog's Hand") end function ru_healing() use_ability("Draw Out Power") end function ely_healing() use_ability("Greater Healing") end function purification() use_ability("Purification") end function recite() use_ability("Recite", "", true) end function bia() use_ability("Brothers in Arms") end function sgd() use_ability("Greater Servant of Makhleb") end function divine_warrior() use_ability("Summon Divine Warrior") end function apocalypse() use_ability("Apocalypse") end -- Will fail if the book is in a non-fire cloud already. function burn_spellbooks() use_ability("Burn Spellbooks") end function plan_bia() if can_bia() and want_to_bia() then bia() return true end return false end function plan_sgd() if can_sgd() and want_to_sgd() then sgd() return true end return false end function plan_divine_warrior() if can_divine_warrior() and want_to_divine_warrior() then divine_warrior() return true end return false end function plan_orbrun_divine_warrior() if can_divine_warrior() and want_to_orbrun_divine_warrior() then divine_warrior() return true end return false end function plan_recite() if can_recite() and danger and not (immediate_danger and hp_is_low(33)) then recite() return true end return false end function plan_apocalypse() if can_apocalypse() and want_to_apocalypse() then apocalypse() return true end return false end function plan_grand_finale() if not danger or not can_grand_finale() then return false end local invo = you.skill("Invocations") if invo < 10 or you.piety_rank() < 6 and invo < 15 then -- fail rate potentially too high, need to add ability failure rate lua return false end local e, bestx, besty, best_info, new_info best_info = nil for _,e in ipairs(enemy_list) do if is_traversable(e.x,e.y) and you.see_cell_no_trans(e.x,e.y) and not cloud_is_dangerous(view.cloud_at(e.x,e.y)) then new_info = get_monster_info(e.x, e.y) if new_info.safe == 0 and ((not best_info) or new_info.threat > best_info.threat or new_info.threat == best_info.threat and (new_info.injury < best_info.injury or new_info.injury == best_info.injury and new_info.distance < best_info.distance)) then best_info = new_info bestx = e.x besty = e.y end end end if best_info then use_ability("Grand Finale", "r" .. vector_move(bestx,besty) .. "\rY") return true end return false end function plan_hydra_destruction() if not can_destruction() then return false end if you.skill("Invocations") < 8 then return false end if count_sgd(4) > 0 or (hydra_weapon_status(items.equipped_at("Weapon")) > -1) or you.xl() >= 20 then return false end local x,y for x = -5,5 do for y = -5,5 do m = monster_array[x][y] if m and string.find(m:desc(), "hydra") then say("INVOKING MAJOR DESTRUCTION") for letter, abil in pairs(you.ability_table()) do if abil == "Major Destruction" then magic("a" .. letter .. "r" .. vector_move(x, y) .. "\r") return true end end end end end return false end function plan_resistance() if not you.extra_resistant() and not you.teleporting() and want_resistance() then return drink_by_name("resistance") end return false end function plan_hand() if can_hand() and want_to_hand() and not you.teleporting() then hand() return true end return false end function dd_prefer_hand() if you.god() ~= "Trog" then return false end return (you.piety_rank() == 6 or you.base_mp() < 3) end function dd_prefer_ru_healing() if you.god() ~= "Ru" or you.piety_rank() < 3 then return false end if you.status("very heavily drained") then return false end if you.base_mp() < 3 then return true end if you.status("heavily drained") then return false end if not you.status("drained") then return true end local hp,mhp = you.hp() return (mhp - hp >= 45) end function prefer_ru_healing() if you.race() == "Deep Dwarf" then return dd_prefer_ru_healing() end return (not (you.status("drained") or you.status("heavily drained") or you.status("very heavily drained"))) end function prefer_ely_healing() if you.god() ~= "Elyvilon" or you.piety_rank() < 4 then return false end if you.hunger_name() == "near starving" and not (you.race() == "Deep Dwarf" and you.base_mp() < 10) then return false end return true end function plan_dd_hand_for_healing() if you.race() ~= "Deep Dwarf" then return false end local hp,mhp = you.hp() if mhp - hp >= 30 and can_hand() and dd_prefer_hand() then hand() return true end return false end function plan_abyss_hand() local hp,mhp = you.hp() if mhp - hp >= 30 and can_hand() then hand() return true end return false end function plan_orbrun_hand() local hp,mhp = you.hp() if mhp - hp >= 30 and can_hand() then hand() return true end return false end function plan_cure_bad_poison() if not (danger or you.race() == "Deep Dwarf") then return false end if you.poison_survival() <= chp() - 60 then if drink_by_name("curing") then say("(to cure bad poison)") return true end if can_purification() then purification() return true end end return false end function heal_general() if can_ru_healing() and prefer_ru_healing() then ru_healing() return true end if can_ely_healing() and prefer_ely_healing() then ely_healing() return true end if heal_wounds() then return true end if can_ru_healing() and you.race() ~= "Deep Dwarf" then ru_healing() return true end if can_ely_healing() then ely_healing() return true end return false end function plan_heal_wounds() if want_to_heal_wounds() then return heal_general() end return false end function plan_orbrun_heal_wounds() if want_to_orbrun_heal_wounds() then return heal_general() end return false end function plan_orbrun_haste() if want_to_orbrun_buff() and not you.status("finesse") then return haste() end return false end function plan_orbrun_might() if want_to_orbrun_buff() then return might() end return false end function plan_haste() if want_to_serious_buff() then return haste() end return false end function plan_might() if want_to_serious_buff() then return might() end return false end function plan_orbrun_heroism() if can_heroism() and want_to_orbrun_buff() then return heroism() end return false end function plan_orbrun_finesse() if can_finesse() and want_to_orbrun_buff() then return finesse() end return false end function heal_wounds() if you.race() == "Deep Dwarf" and you.xl() < 15 and not (immediate_danger and (hp_is_low(25) or chp() <= 20)) and dd_heal_ability() then dd_hw_meter = dd_hw_meter + 100 return true end if you.mutation("no potion heal") < 3 and drink_by_name("heal wounds") then dd_hw_meter = dd_hw_meter + 100 return true end if you.race() == "Deep Dwarf" and dd_heal_ability() then dd_hw_meter = dd_hw_meter + 100 return true end return false end function haste() if you.hasted() or you.race() == "Formicid" or you.god() == "Cheibriados" then return false end return drink_by_name("haste") end function might() if you.mighty() then return false end return drink_by_name("might") end function cloud_is_dangerous(cloud) if cloud == "flame" or cloud == "fire" then return (you.res_fire() < 1) elseif cloud == "noxious fumes" then return (not meph_immune()) elseif cloud == "freezing vapour" then return (you.res_cold() < 1) elseif cloud == "poison gas" then return (you.res_poison() < 1) elseif cloud == "calcifying dust" then return (you.race() ~= "Gargoyle") elseif cloud == "foul pestilence" then return (not miasma_immune()) elseif cloud == "seething chaos" or cloud == "mutagenic fog" then return true end return false end function assess_square(x,y) a = {} -- distance to current square a.supdist = supdist(x,y) -- is current square near a BiA/SGD? if a.supdist == 0 then a.near_ally = (count_bia(3) + count_sgd(3) + count_divine_warrior(3) > 0) end -- can we move there? a.can_move = (a.supdist == 0) or not view.withheld(x,y) and not monster_in_way(x,y) and is_traversable(x,y) and not is_solid(x,y) if not a.can_move then return a end cloud = view.cloud_at(x,y) -- nonadjacent monsters who might be able to attack you (ranged/reaching/fast) a.ranged = count_ranged(x,y,LOS) -- alert ranged monsters at distance >= 4 a.longranged = count_longranged(x,y,LOS) -- adjacent monsters a.adjacent = count_nearby(x,y,1) -- wandering or sleeping monsters a.unalert = count_unalert(x,y,LOS) -- ranged monsters at distance <= 5, not necessarily visible a.shortranged = count_shortranged(x,y,5) -- corners - avoid these if possible a.cornerish = is_cornerish(x,y) if have_reaching() then -- slow adjacent nonranged monsters, for kiting a.slow_adjacent = count_slow_nearby(x,y,1) end -- distance to nearest enemy a.enemy_distance = distance_to_enemy(x,y) -- will we fumble if we try to attack from this square? a.fumble = (not you.flying() and view.feature_at(x,y) == "shallow_water" and intrinsic_fumble() and not (you.god() == "Beogh" and you.piety_rank() >= 5)) -- will we be slow if we move into this square? a.slow = (not you.flying() and view.feature_at(x,y) == "shallow_water" and you.race() ~= "Merfolk" and you.race() ~= "Octopode" and you.race() ~= "Barachi" and not (you.god() == "Beogh" and you.piety_rank() >= 5)) -- is the square safe to step in? (checks traps+clouds) a.safe = view.is_safe_square(x,y) cloud = view.cloud_at(x,y) -- would we want to move out of a cloud? note that we don't worry about -- weak clouds if monsters are around a.cloud_safe = (cloud == nil) or a.safe or danger and not cloud_is_dangerous(cloud) -- equal to 10000 if the move is not closer to any stair in good_stair_list, -- otherwise equal to the (min) dist to such a stair a.stair_closer = stair_improvement(x,y) return a end -- returns a string explaining why moving a1->a2 is preferable to not moving -- possibilities are: -- cloud - stepping out of harmful cloud -- water - stepping out of shallow water when it would cause fumbling -- reaching - kiting slower monsters with reaching -- hiding - moving out of sight of alert ranged enemies at distance >= 4 -- stealth - moving out of sight of sleeping or wandering monsters -- outnumbered - stepping away from a square adjacent to multiple monsters -- (when not cleaving) -- fleeing - moving towards stairs function step_reason(a1,a2) if not (a2.can_move and a2.safe and a2.supdist > 0) then return false elseif (a2.fumble or a2.slow) and a1.cloud_safe then return false elseif (not a1.near_ally) and a2.stair_closer < 10000 and a1.stair_closer > 0 and a1.enemy_distance < 10 and reason_to_rest(90) and not buffed() and (no_spells or starting_spell() ~= "Summon Small Mammal") then return "fleeing" elseif (not a1.near_ally) and a2.ranged == 0 and a2.adjacent == 0 and a1.longranged > 0 then return "hiding" elseif (not a1.near_ally) and a2.ranged == 0 and a2.adjacent == 0 and a2.unalert < a1.unalert then return "stealth" elseif not a1.cloud_safe then return "cloud" elseif a1.fumble then if a2.ranged > a1.ranged and a2.enemy_distance > a1.enemy_distance then return false else return "water" end elseif have_reaching() and a1.slow_adjacent > 0 and a2.adjacent == 0 and a2.ranged == 0 then return "reaching" elseif cleaving() then return false elseif a1.adjacent == 1 then return false elseif a2.adjacent + a2.ranged <= a1.adjacent + a1.ranged - 2 then return "outnumbered" else return false end end -- determines whether moving a0->a2 is an improvement over a0->a1 -- assumes that these two moves have already been determined to be better -- than not moving, with given reasons function step_improvement(bestreason,reason,a1,a2) if reason == "fleeing" and bestreason ~= "fleeing" then return true elseif bestreason == "fleeing" and reason ~= "fleeing" then return false elseif reason == "water" and bestreason == "water" and a2.enemy_distance < a1.enemy_distance then return true elseif reason == "water" and bestreason == "water" and a2.enemy_distance > a1.enemy_distance then return false elseif a2.adjacent+a2.ranged < a1.adjacent+a1.ranged then return true elseif a2.adjacent+a2.ranged > a1.adjacent+a1.ranged then return false elseif cleaving() and a2.ranged < a1.ranged then return true elseif cleaving() and a2.ranged > a1.ranged then return false elseif a2.adjacent+a2.ranged == 0 and a2.unalert < a1.unalert then return true elseif a2.adjacent+a2.ranged == 0 and a2.unalert > a1.unalert then return false elseif reason == "fleeing" and a2.stair_closer < a1.stair_closer then return true elseif reason == "fleeing" and a2.stair_closer > a1.stair_closer then return false elseif a2.enemy_distance < a1.enemy_distance then return true elseif a2.enemy_distance > a1.enemy_distance then return false elseif a2.stair_closer < a1.stair_closer then return true elseif a2.stair_closer > a2.stair_closer then return false elseif a1.cornerish and not a2.cornerish then return true else return false end end function choose_tactical_step() tactical_step = nil tactical_reason = "none" if you.confused() or you.berserk() or you.constricted() or you.transform() == "tree" or you.transform() == "fungus" or where:find("Slime") or you.status("barbed spikes") then return end local a0 = assess_square(0,0) if a0.cloud_safe and not (a0.fumble and sense_danger(3)) and (not have_reaching() or a0.slow_adjacent == 0) and (a0.adjacent <= 1 or cleaving()) and (a0.near_ally or a0.enemy_distance == 10) then return end local bestx,besty,bestreason local besta = nil local x,y local a local reason for x = -1,1 do for y = -1,1 do if supdist(x,y) > 0 then a = assess_square(x,y) reason = step_reason(a0,a) if reason then if besta == nil or step_improvement(bestreason,reason,besta,a) then bestx = x besty = y besta = a bestreason = reason end end end end end if besta then tactical_step = delta_to_vi(bestx,besty) tactical_reason = bestreason end end function plan_cloud_step() if tactical_reason == "cloud" then say("Stepping ~*~*~tactically~*~*~ (" .. tactical_reason .. ").") magic(tactical_step .. "Y") return true end return false end function plan_water_step() if tactical_reason == "water" then say("Stepping ~*~*~tactically~*~*~ (" .. tactical_reason .. ").") magic(tactical_step .. "Y") return true end return false end function plan_coward_step() if tactical_reason == "hiding" or tactical_reason == "stealth" then if tactical_reason == "hiding" then hiding_turn_count = you.turns() end if SPAM then say("Stepping ~*~*~tactically~*~*~ (" .. tactical_reason .. ").") end magic(tactical_step .. "Y") return true end return false end function plan_flee_step() if tactical_reason == "fleeing" then if SPAM then say("FLEEEEING.") end set_stair_target(tactical_step) last_flee_turn = you.turns() magic(tactical_step .. "Y") return true end return false end function plan_other_step() if tactical_reason ~= "none" then say("Stepping ~*~*~tactically~*~*~ (" .. tactical_reason .. ").") magic(tactical_step .. "Y") return true end return false end function plan_berserk() if can_berserk() and want_to_berserk() then berserk() return true end return false end function plan_heroism() if can_heroism() and want_to_heroism() then heroism() return true end return false end function plan_recall() if can_recall() and want_to_recall() then recall() return true end return false end function plan_recall_ancestor() if can_recall_ancestor() and want_to_recall_ancestor() then recall_ancestor() return true end return false end function plan_finesse() if can_finesse() and want_to_finesse() then finesse() return true end return false end function plan_slouch() if can_slouch() and want_to_slouch() then slouch() return true end return false end function plan_drain_life() if can_drain_life() and want_to_drain_life() then drain_life() return true end return false end function plan_burn_spellbooks() if want_to_burn_spellbooks() then burn_spellbooks() return true end return false end function want_to_bia() if not danger then return false end if check_monsters(LOS, bia_necessary_monsters) or you.piety_rank() > 4 and ((want_to_berserk() and not can_berserk()) or check_monsters(LOS, bia_monsters)) then if count_bia(4) == 0 and not you.teleporting() then return true end end return false end function want_to_finesse() if danger and check_monsters(LOS, bia_monsters) and not you.teleporting() then return true end return false end function want_to_slouch() if danger and you.piety_rank() == 6 and not you.teleporting() and estimate_slouch_damage() >= 6 then return true end return false end function want_to_drain_life() if not danger then return false end local cd = count_drainable() return (cd >= 8 or cd >= 3 and you.race() == "Deep Dwarf" and hp_is_low(80)) end function want_to_sgd() if you.skill("Invocations") >= 12 and (check_monsters(LOS, bia_monsters) or hp_is_low(50) and immediate_danger) then if count_sgd(4) == 0 and not you.teleporting() then return true end end return false end function want_to_divine_warrior() if you.skill("Invocations") >= 8 and (check_monsters(LOS, bia_monsters) or hp_is_low(50) and immediate_danger) then if count_divine_warrior(4) == 0 and not you.teleporting() then return true end end return false end function want_to_orbrun_divine_warrior() return (danger and count_pan_lords(LOS) > 0 and count_divine_warrior(4) == 0 and not you.teleporting()) end function want_to_apocalypse() if you.race() ~= "Deep Dwarf" and not (you.status("drained") or you.status("heavily drained") or you.status("very heavily drained")) and (check_monsters(LOS, bia_monsters) or hp_is_low(25) and immediate_danger) then return true end return false end function dd_want_to_teleport() --say("HW: " .. dd_hw_meter) return ((immediate_danger and dd_hw_meter > 300 or immediate_danger and bad_corrosion() or you.god() ~= "Trog" and immediate_danger and hp_is_low(25) or you.god() == "Trog" and (you.slowed() or too_hungry_to_berserk()) and want_to_berserk() and not can_berserk()) and count_bia(4) == 0) end function bad_corrosion() if you.corrosion() == 0 then return false elseif where:find("Slime") then return (you.corrosion() >= 6 and hp_is_low(70)) else return (you.corrosion() >= 3 and hp_is_low(50) or you.corrosion() >= 4 and hp_is_low(70)) end end function want_to_teleport() if where:find("Zig") then return false end if count_hostile_sgd(LOS) > 0 and you.xl() < 21 then sgd_timer = you.turns() return true end if where == "Pan" and (count_hellions(LOS) >= 3 or count_daevas(LOS) >= 3) then dislike_pan_level = true return true end if you.xl() <= 17 and not can_berserk() and count_big_slimes(LOS) > 0 then return true end if you.race() == "Deep Dwarf" then return dd_want_to_teleport() end return ((immediate_danger and bad_corrosion() or you.god() ~= "Trog" and immediate_danger and hp_is_low(25) or you.god() == "Trog" and (you.slowed() or too_hungry_to_berserk()) and want_to_berserk() and not can_berserk()) and count_bia(4) == 0) end function want_to_orbrun_teleport() return (hp_is_low(33) and sense_danger(2)) end function need_to_wait_for_ely_healing() local mp,mmp = you.mp() return (you.god() == "Elyvilon" and you.piety_rank() >= 4 and mmp >= 2 and mp < 2) end function dd_want_to_heal_wounds() if (not danger) and (you.regenerating() or dd_prefer_hand() or (dd_prefer_ru_healing() and you.exhausted()) or need_to_wait_for_ely_healing()) then return false end local hp,mhp = you.hp() if immediate_danger then return (hp_is_low(25) or (hp_is_low(50) and mhp - hp >= 30) or (mhp - hp >= 20 and hp <= 20)) else if (dd_prefer_hand() or (dd_prefer_ru_healing() and you.exhausted()) or need_to_wait_for_ely_healing()) and (not you.teleporting()) and not hp_is_low(66) then return false end if (you.god() == "Makhleb" and you.piety_rank() >= 1 or you.god() == "Yredelemnul" and you.piety_rank() >= 4) and not hp_is_low(66) then return false end return (hp_is_low(25) or (mhp - hp >= 30) or (mhp - hp >= 20 and hp <= 20)) end end function want_to_heal_wounds() if you.race() == "Deep Dwarf" then return dd_want_to_heal_wounds() end if danger and can_ely_healing() and hp_is_low(50) and you.piety_rank() >= 5 and you.hunger_name() ~= "near starving" then return true end return (danger and hp_is_low(25)) end function want_to_orbrun_heal_wounds() if danger then return (hp_is_low(25) or hp_is_low(50) and you.teleporting()) elseif you.race() == "Deep Dwarf" and you.god() ~= "Trog" and you.god() ~= "Makhleb" then local hp,mhp = you.hp() return (mhp - hp >= 30) else return (hp_is_low(50)) end end function want_to_orbrun_buff() return ((count_pan_lords(LOS) > 0) or check_monsters(LOS, scary_monsters)) end function want_to_serious_buff() if you.god() == "Okawaru" or you.god() == "Trog" then return false -- these gods have their own buffs end if you.num_runes() < 3 then return false -- none of these uniques exist early end if you.teleporting() then return false -- don't waste a potion if we are already leaving end return (check_monsters(LOS, ridiculous_uniques)) end function want_resistance() return (check_monsters(LOS, fire_resistance_monsters) and you.res_fire() < 3 or check_monsters(LOS, cold_resistance_monsters) and you.res_cold() < 3 or check_monsters(LOS, elec_resistance_monsters) and you.res_shock() < 1 or check_monsters(LOS, pois_resistance_monsters) and you.res_poison() < 1) end function want_to_hand() return check_monsters(LOS, hand_monsters) end function want_to_berserk() return (hp_is_low(50) and sense_danger(2) and (you.race() ~= "Deep Dwarf" or dd_want_to_heal_wounds()) or check_monsters(2, scary_monsters) or (invisi_sigmund and not options.autopick_on)) end function want_to_heroism() return (danger and (hp_is_low(70) or check_monsters(LOS, scary_monsters) or count_nearby(0,0,LOS) >= 4)) end function want_to_recall() if immediate_danger and hp_is_low(66) then return false end local mp,mmp = you.mp() return (mp == mmp) end function want_to_recall_ancestor() return (count_elliptic(LOS) == 0) end function want_to_stay_in_abyss() return (game_status == "abyss" and not you.have_rune("abyssal") and not hp_is_low(50)) end function have_pan_runes() return (you.have_rune("demonic") and you.have_rune("fiery") and you.have_rune("dark") and you.have_rune("magical") and you.have_rune("glowing")) end function have_hell_runes() return (you.have_rune("iron") and you.have_rune("obsidian") and you.have_rune("icy") and you.have_rune("bone")) end function want_to_be_in_pan() return (game_status == "pan" and not have_pan_runes()) end function plan_wait_for_melee() is_waiting = false if sense_danger(1) or (have_reaching() and sense_danger(2)) or (not options.autopick_on) or you.berserk() or you.have_orb() or count_bia(LOS) > 0 or count_sgd(LOS) > 0 or count_divine_warrior(LOS) > 0 or not view.is_safe_square(0,0) or view.feature_at(0,0) == "shallow_water" and not you.flying() or where:find("Abyss") then wait_count = 0 return false end if you.turns() >= last_wait + 10 then wait_count = 0 end if (not danger) or wait_count >= 10 then return false end -- hack to make us wait when we enter v5 so we don't move off stairs if v5_entry_turn and you.turns() <= v5_entry_turn + 2 then is_waiting = true return false end count = 0 sleeping_count = 0 local e for _,e in ipairs(enemy_list) do if is_ranged(e.m) then wait_count = 0 return false end if e.m:reach_range() >= 2 and supdist(e.x,e.y) <= 2 then wait_count = 0 return false end if will_tab(e.x,e.y,0,0,mons_tabbable_square) and not (e.m:name() == "wandering mushroom" or e.m:name():find("ballistomycete") or e.m:name():find("vortex") or e.m:desc():find("fleeing") or e.m:status("paralysed") or e.m:status("confused") or e.m:status("petrified")) then count = count + 1 if e.m:desc():find("sleeping") or e.m:desc():find("dormant") then sleeping_count = sleeping_count + 1 end end end if count == 0 then return false end -- say "Waiting for monsters to approach." if sleeping_count == 0 then wait_count = wait_count + 1 end last_wait = you.turns() if plan_cure_poison() then return true end -- don't actually wait yet, because we might use a ranged attack instead is_waiting = true return false end function plan_wait_spit() if not is_waiting then return false end if you.mutation("spit poison") < 1 then return false end if you.berserk() or you.confused() or you.breath_timeout() then return false end if you.xl() > 11 or starving() or you.hunger_name() == "near starving" or you.hunger_name() == "very hungry" or you.hunger_name() == "hungry" then return false end dist = 10 cur_e = none for _,e in ipairs(enemy_list) do if supdist(e.x,e.y) < dist and e.m:res_poison() < 1 then dist = supdist(e.x,e.y) cur_e = e end end ab_range = 6 ab_name = "Spit Poison" if you.mutation("spit poison") > 2 then ab_range = 7 ab_name = "Breathe Poison Gas" end if dist <= ab_range then if use_ability(ab_name, "r" .. vector_move(cur_e.x, cur_e.y) .. "\r") then return true end end return false end function starting_spell() if you.god() == "Trog" or you.xl() > 9 then no_spells = true return end -- Missing Infusion for now. local spell_list = {"Shock", "Magic Dart", "Sandblast", "Flame Tongue", "Freeze", "Pain", "Summon Small Mammal", "Beastly Appendage", "Sting"} for _,sp in ipairs(spell_list) do if spells.memorised(sp) and spells.fail(sp) <= 25 then return sp end end no_spells = true end function spell_range(sp) if sp == "Summon Small Mammal" then return LOS elseif sp == "Beastly Appendage" then return 4 elseif sp == "Sandblast" then return 3 else return spells.range(sp) end end function spell_castable(sp) if sp == "Beastly Appendage" then if transformed() then return false end elseif sp == "Summon Small Mammal" then local x,y local count = 0 for x = -LOS,LOS do for y = -LOS,LOS do m = monster_array[x][y] if m and m:attitude() == ATT_FRIENDLY then count = count + 1 end end end if count >= 4 then return false end elseif sp == "Sandblast" then if not have_item("missile", "stone") then return false end end return true end function plan_starting_spell() if no_spells then return false end if you.silenced() or you.confused() or you.berserk() or starving() then return false end local sp = starting_spell() if not sp then return false end if cmp() < spells.mana_cost(sp) then return false end if you.xl() > 4 and not is_waiting then return false end local dist = distance_to_tabbable_enemy(0,0) if dist < 2 and wskill() ~= "Unarmed Combat" then local weap = items.equipped_at("Weapon") if weap and weap.weap_skill == wskill() then return false end end if dist > spell_range(sp) then return false end if not spell_castable(sp) then return false end say("CASTING " .. sp) if spells.range(sp) > 0 then magic("z" .. spells.letter(sp) .. "f") else magic("z" .. spells.letter(sp)) end return true end function plan_wait_throw() if not is_waiting then return false end if distance_to_enemy(0,0) < 3 then return false end if items.fired_item() then magic("Q-ff") -- reset quiver before throwing return true else return false end end function plan_wait_wait() if not is_waiting then return false end magic("s") return true end function plan_attack() if danger and attack() then return true end return false end function plan_eat_chunk() if want_chunk() then for it in inventory() do if string.find(it.name(), "chunk") and not bad_food(it) then magic("ee") return true end end end return false end function plan_eat_permafood() if where == "Zot:5" or where:find("Zot") and count_permafood() >= 10 then return plan_orbrun_eat_permafood() end if want_permafood() then if eat_permafood() then return true end end return false end function plan_orbrun_eat_permafood() if you.hunger_name() ~= "completely stuffed" and you.hunger_name() ~= "very full" and you.hunger_name() ~= "almost alive" and (you.race() ~= "Ghoul" or you.hunger_name() ~= "not hungry") and you.transform() ~= "lich" then if eat_permafood() then return true end end return false end function plan_eat_anyway() if you.hunger_name() == "very hungry" then if eat_permafood() then return true end end return false end function drink_blood() if drink_by_name("coagulated blood") then return true end return drink_by_name("blood") end function eat_permafood(prefer_ration) if you.race() == "Vampire" then return drink_blood() end local l local max_prefer = 0 local food_name for it in inventory() do if it.class(true) == "food" and not bad_food(it) then local name = it.name() local prefer if name:find("ration") then if prefer_ration then prefer = 5 else prefer = 1 end elseif name:find("chunk") then prefer = 0 else prefer = 3 end if prefer > max_prefer then l = items.index_to_letter(it.slot) max_prefer = prefer food_name = it.name() end end end if max_prefer > 0 then items.swap_slots(items.letter_to_index(l), items.letter_to_index('e'), false) say("EATING " .. food_name .. ".") magic("ee") return true end return false end function count_permafood() local s = 0 for it in inventory() do if it.class(true) == "food" and not bad_food(it) and not food.ischunk(it) then s = s + it.quantity end end return s end function plan_rest() if should_rest() then rest() return true end return false end function plan_orbrun_rest() if you.confused() or you.slowed() or you.berserk() or you.teleporting() or you.silencing() or transformed() then rest() return true end return false end function plan_abyss_rest() local hp,mhp = you.hp() if you.confused() or you.slowed() or you.berserk() or you.teleporting() or you.silencing() or transformed() or (hp < mhp) and you.regenerating() then rest() return true end return false end function want_to_burn_spellbooks() return BURN_BOOKS and (you.god() == "Trog") and can_read() and see_spellbooks_to_burn() end function want_blood() return (count_item("potion","of blood") < 10) end -- this function could probably be simplified function vp_handle_corpses() if want_blood() and on_bottleable_corpses() then chop() elseif want_permafood() and on_edible_corpse() and not on_dangerous_corpse() then magic("eY") elseif on_bottleable_corpses() then chop() else return false end return true end function plan_handle_corpses() if not on_corpses() then return false end if you.race() == "Vampire" then return vp_handle_corpses() end if on_edible_corpse() then chop() return true end return false end function magicfind(target) -- this will be turned on again in ready() offlevel_travel = false magic(control('f') .. target .. "\ra\r") end function god_options() return c_persist.cur_god_list or GOD_LIST end function plan_find_altar() if not want_altar() then return false end str = "@altar&&<>" magicfind(str) return true end local tried_find_altar_turn = -1 function plan_find_conversion_altar() if unshafting() then return false end if game_status == "tso" and you.god() ~= "the Shining One" then str = "altar of the Shining One" elseif LUGONU_CONVERSION and you.xl() == 12 and you.god() == "Lugonu" then str = "altar of Makhleb" else return false end expect_new_location = true if tried_find_altar_turn ~= you.turns() then tried_find_altar_turn = you.turns() magic(control('f') .. str .. "\ra\r") return nil end magic(control('f') .. str .. "\rb\r") return true end function plan_abandon_god() if you.god() ~= "No God" and you.num_runes() == 0 and not util.contains(god_options(), you.god()) then magic("aXYY") return true end return false end function plan_unwield_weapon() if wskill() ~= "Unarmed Combat" then return false end if not items.equipped_at("Weapon") then return false end magic("w-") return true end function plan_join_beogh() if you.race() ~= "Hill Orc" or not want_altar() or you.confused() then return false end for _,god in ipairs(god_options()) do if god == "Beogh" and use_ability("Convert to Beogh", "YY") then return true end end return false end function plan_convert() if (game_status ~= "tso" or you.god() == "the Shining One" or view.feature_at(0,0) ~= "altar_the_shining_one") and ((not LUGONU_CONVERSION) or you.god() ~= "Lugonu" or view.feature_at(0,0) ~= "altar_makhleb") then return false end if you.silenced() then rest() else if view.feature_at(0,0) == "altar_makhleb" then for i,br in ipairs(c_persist.branches_entered) do if br == "L" then table.remove(c_persist.branches_entered,i) end end end magic(" 1 and not it.fully_identified then return drink(it) end end return false end function plan_read_id() if not can_read() then return false end for it in inventory() do if it.class(true) == "scroll" and not it.fully_identified then items.swap_slots(it.slot, items.letter_to_index('Y'), false) weap = items.equipped_at("Weapon") scroll_letter = 'Y' if weap and not weap.artefact and not brand_is_great(weap.ego()) then scroll_letter = items.index_to_letter(weap.slot) items.swap_slots(weap.slot, items.letter_to_index('Y'), false) end if you.race() ~= "Felid" then return read2(scroll_letter, ".Y" .. string.char(27) .. "YB") else return read2(scroll_letter, ".Y" .. string.char(27) .. "YC") end end end return false end function plan_use_id_scrolls() if not can_read() then return false end local id_scroll for it in inventory() do if it.class(true) == "scroll" and it.name():find("identify") then id_scroll = it end end if not id_scroll then return false end local oldslots = { } local newslots = {[0] = 'B', [1] = 'N', [2] = 'Y'} -- harmless keys local count = 0 for it in inventory() do if it.class(true) == "wand" and not it.fully_identified and (it.name():find("empty") or it.name():find("digging") and (where:find("Depths") or where:find("Zot"))) then oldname = it.name() if read2(id_scroll, letter(it)) then say("IDENTIFYING " .. oldname) return true end end end for it in inventory() do if it.class(true) == "jewellery" and not it.fully_identified then oldname = it.name() if read2(id_scroll, letter(it)) then say("IDENTIFYING " .. oldname) return true end end end if id_scroll.quantity > 1 then for it in inventory() do if it.class(true) == "potion" and not it.fully_identified then oldname = it.name() if read2(id_scroll, letter(it)) then say("IDENTIFYING " .. oldname) return true end end end end return false end function body_armour_is_great(arm) local name = arm.name() local ap = armour_plan() if ap == "heavy" then return (name:find("gold dragon") or name:find("crystal plate") or name:find("plate armour of fire") or name:find("pearl dragon")) elseif ap == "large" then return name:find("dragon scales") elseif ap == "dodgy" then return (arm.encumbrance <= 11 and name:find("dragon scales")) else return (name:find("dragon scales") or name:find("robe of resistance")) end end function body_armour_is_good(arm) if in_branch("Z") then return true end local name = arm.name() local ap = armour_plan() if ap == "heavy" then return (name:find("plate") or name:find("dragon scales")) elseif ap == "large" then return false elseif ap == "dodgy" then return (name:find("ring mail") or name:find("robe of resistance")) else return name:find("robe of fire resistance") end end -- do we want to keep this brand? function brand_is_great(brand) if brand == "speed" then return true elseif brand == "vampirism" then return (you.race() == "Deep Dwarf" or not you.have_orb()) elseif brand == "electrocution" then return (where ~= "Zot:5") elseif brand == "holy wrath" then return (where == "Zot:5" or you.have_orb() or PAN_RUNE or HELL_RUNE or GOLDEN_RUNE) else return false end end function plan_use_good_consumables() for it in inventory() do if it.class(true) == "scroll" and can_read() then if it.name():find("acquirement") then if view.feature_at(0,0) ~= "deep_water" and view.feature_at(0,0) ~= "lava" then if you.race() == "Felid" then return read2(it, " c") else return read2(it, " b") end end elseif it.name():find("enchant weapon") then weapon = items.equipped_at("weapon") if weapon and weapon.class(true) == "weapon" and not weapon.artefact and weapon.plus < 9 then oldname = weapon.name() if read2(it, letter(weapon)) then say("ENCHANTING " .. oldname .. ".") return true end end elseif it.name():find("brand weapon") then weapon = items.equipped_at("weapon") if weapon and weapon.class(true) == "weapon" and not weapon.artefact and not brand_is_great(weapon.ego()) then oldname = weapon.name() if read2(it, letter(weapon)) then say("BRANDING " .. oldname .. ".") return true end end elseif it.name():find("enchant armour") then body = items.equipped_at("Armour") ac = armour_ac() if body and not body.artefact and body.plus < ac and body_armour_is_great(body) and not body.name():find("quicksilver dragon scales") then oldname = body.name() if read2(it, letter(body)) then say("ENCHANTING " .. oldname .. ".") return true end end for _,slotname in pairs(good_slots) do if slotname ~= "Armour" and slotname ~= "Shield" then it2 = items.equipped_at(slotname) if it2 and not it2.artefact and it2.plus < 2 and it2.plus >= 0 and not it2.name():find("scarf") then oldname = it2.name() if read2(it, letter(it2)) then say("ENCHANTING " .. oldname .. ".") return true end end if slotname == "Boots" and it2 and it2.name():find("barding") and not it2.artefact and it2.plus < 4 and it2.plus >= 0 then oldname = it2.name() if read2(it, letter(it2)) then say("ENCHANTING " .. oldname .. ".") return true end end end end if body and not body.artefact and body.plus < ac and body_armour_is_good(body) and not body.name():find("quicksilver dragon scales") then oldname = body.name() if read2(it, letter(body)) then say("ENCHANTING " .. oldname .. ".") return true end end elseif it.name():find("recharging") then for it2 in inventory() do if it2.class(true) == "wand" and it2.name():find("digging") and (it2.name():find("empty") or it2.plus and it2.plus < 10) then oldname = it2.name() if read2(it, letter(it2)) then say("RECHARGING " .. oldname .. ".") return true end end end elseif it.name():find("remove curse") then for it2 in inventory() do if it2.cursed and it2.equipped then return read(it) end end end elseif it.class(true) == "potion" then if it.name():find("experience") then return drink(it) end if it.name():find("mutation") then if base_mutation("inhibited regeneration") > 0 and you.race() ~= "Ghoul" or base_mutation("teleportitis") > 0 or base_mutation("deformed body") > 0 and you.race() ~= "Naga" and you.race() ~= "Centaur" and (armour_plan() == "heavy" or armour_plan() == "large") or base_mutation("berserk") > 0 or base_mutation("deterioration") > 1 or base_mutation("blurry vision") > 0 or base_mutation("frail") > 0 or base_mutation("forlorn") > 0 or base_mutation("no potion heal") > 0 and you.race() ~= "Vine Stalker" then if you.god() ~= "Zin" then return drink(it) elseif you.piety_rank() >= 6 and not you.one_time_ability_used() and use_ability("Cure All Mutations", "Y") then return true end end end end end return false end function base_mutation(str) return you.mutation(str) - you.temp_mutation(str) end function is_melee_weapon(it) return (it and it.class(true) == "weapon" and it.weap_skill ~= "Bows" and it.weap_skill ~= "Crossbows" and it.weap_skill ~= "Slings") end function plan_wield_weapon() local weap = items.equipped_at("Weapon") if is_melee_weapon(weap) or you.berserk() or transformed() then return false end if wskill() == "Unarmed Combat" then return false end for it in inventory() do if it and it.class(true) == "weapon" then if should_equip(it) and (not it.name():find("vamp") or intrinsic_undead()) then l = items.index_to_letter(it.slot) say("Wielding weapon " .. it.name() .. ".") magic("w" .. l .. "YY") -- this might have a 0-turn fail because of unIDed vamp/holy return nil end end end if weap and not is_melee_weapon(weap) then magic("w-") return true end return false end function plan_swap_weapon() if you.race() == "Troll" or you.berserk() or transformed() or not items.equipped_at("Weapon") then return false end local sit local x,y if you.xl() < 18 then for x = -3,3 do for y = -3,3 do m = monster_array[x][y] if m and string.find(m:desc(), "hydra") and will_tab(0,0,x,y,tabbable_square) then sit = "hydra" end end end end if in_extended() then sit = "extended" end twohands = true if items.equipped_at("Shield") and you.race() ~= "Formicid" then twohands = false end it_old = items.equipped_at("Weapon") swappable = can_swap("Weapon") if not swappable or (it_old.ego() == "vampirism" and not intrinsic_undead()) then return false end cur_val = weapon_value(it_old,true,it_old,sit) max_val = cur_val max_it = nil for it in inventory() do if it and it.class(true) == "weapon" and not it.equipped then if (twohands or it.hands < 2) and not (it.name():find("vamp") and not intrinsic_undead()) then val2 = weapon_value(it,true,it_old,sit) if val2 > max_val then max_val = val2 max_it = it end end end end if max_it then l = items.index_to_letter(max_it.slot) say("SWAPPING to " .. max_it.name() .. ".") magic("w" .. l .. "YY") -- this might have a 0-turn fail because of unIDed vamp/holy return nil end return false end function plan_bless_weapon() if you.god() ~= "the Shining One" or you.one_time_ability_used() or you.piety_rank() < 6 or you.silenced() then return false end local bestv = -1 local minv, maxv, bestletter for it in inventory() do if equip_slot(it) == "Weapon" then minv,maxv = equip_value(it, true, nil, "bless") if minv > bestv then bestv = minv bestletter = letter(it) end end end if bestv > 0 then use_ability("Brand Weapon With Holy Wrath", bestletter .. "Y") return true end return false end function plan_upgrade_weapon() if you.race() == "Troll" then return false end local sit if in_extended() then sit = "extended" end twohands = true if items.equipped_at("Shield") and you.race() ~= "Formicid" then twohands = false end it_old = items.equipped_at("Weapon") swappable = can_swap("Weapon") for it in inventory() do if it and it.class(true) == "weapon" and not it.equipped then local equip = false local drop = false if should_upgrade(it,it_old,sit) then equip = true elseif should_drop(it) then drop = true end if equip and swappable and (twohands or it.hands < 2) then if it.name():find("vamp") and not intrinsic_undead() and not (you.hunger_name() == "full" or you.hunger_name() == "very full" or you.hunger_name() == "completely stuffed") then say("Eating in order to wield vampiric weapon.") if eat_permafood() then return true end else l = items.index_to_letter(it.slot) say("UPGRADING to " .. it.name() .. ".") magic("w" .. l .. "YY") -- this might have a 0-turn fail because of unIDed vamp/holy return nil end end if drop then l = items.index_to_letter(it.slot) say("DROPPING " .. it.name() .. ".") magic("d" .. l .. "\r") return true end end end return false end function plan_remove_terrible_jewellery() if you.berserk() or transformed() then return false end for it in inventory() do if it and it.equipped and it.class(true) == "jewellery" and not it.cursed and should_remove(it) then say("REMOVING " .. it.name() .. ".") magic("P" .. letter(it) .. "YY") return true end end return false end function plan_upgrade_amulet() it_old = items.equipped_at("Amulet") swappable = can_swap("Amulet") for it in inventory() do if it and equip_slot(it) == "Amulet" and (it.fully_identified or count_item("scroll", "remove curse") > 1) and not it.equipped then local equip = false local drop = false if should_upgrade(it,it_old) then equip = true elseif should_drop(it) then drop = true end if equip and swappable then l = items.index_to_letter(it.slot) say("UPGRADING to " .. it.name() .. ".") magic("P" .. l .. "YY") return true end if drop then l = items.index_to_letter(it.slot) say("DROPPING " .. it.name() .. ".") magic("d" .. l .. "\r") return true end end end return false end function plan_upgrade_rings() local it_rings = ring_list() local empty = (empty_ring_slots() > 0) for it in inventory() do if it and equip_slot(it) == "Ring" and (it.fully_identified or count_item("scroll", "remove curse") > 1) and not it.equipped then local equip = false local drop = false local swap = nil if empty then if should_equip(it) then equip = true end else for _, it_old in ipairs(it_rings) do if not equip and not it_old.cursed and should_upgrade(it, it_old) then equip = true swap = it_old.slot end end end if not equip and should_drop(it) then drop = true end if equip then l = items.index_to_letter(it.slot) say("UPGRADING to " .. it.name() .. ".") if swap then items.swap_slots(swap, items.letter_to_index('Y'), false) if l == 'Y' then l = items.index_to_letter(swap) end end magic("P" .. l .. "YY") return true end if drop then l = items.index_to_letter(it.slot) say("DROPPING " .. it.name() .. ".") magic("d" .. l .. "\r") return true end end end return false end function plan_maybe_upgrade_armour() if not upgrade_phase then return false end return plan_upgrade_armour() end function plan_upgrade_armour() if cloudy or you.mesmerised() then return false end for it in inventory() do if it and it.class(true) == "armour" and not it.equipped then local st, _ = it.subtype() local equip = false local drop = false local swappable it_old = items.equipped_at(good_slots[st]) swappable = can_swap(good_slots[st]) if should_upgrade(it,it_old) then equip = true elseif should_drop(it) then drop = true end if good_slots[st] == "Helmet" and it.ac == 1 and (you.mutation("horns") > 0 or you.mutation("beak") > 0 or you.mutation("antennae") > 0) then equip = false drop = true end if good_slots[st] == "Helmet" and (you.mutation("horns") >= 3 or you.mutation("antennae") >= 3) then equip = false drop = true end if it.name():find("boots") and (you.mutation("talons") >= 3 or you.mutation("hooves") >= 3) then equip = false drop = true end if it.name():find("boots") and you.race() == "Merfolk" and (view.feature_at(0,0) == "shallow_water" or view.feature_at(0,0) == "deep_water") then equip = false drop = false end if good_slots[st] == "Gloves" and you.mutation("claws") >= 3 then equip = false drop = true end if equip and swappable then l = items.index_to_letter(it.slot) say("UPGRADING to " .. it.name() .. ".") magic("W" .. l .. "YN") upgrade_phase = true return true end if drop then l = items.index_to_letter(it.slot) say("DROPPING " .. it.name() .. ".") magic("d" .. l .. "\r") return true end end end for it in inventory() do if it and it.equipped and it.class(true) == "armour" and (not it.cursed) and should_remove(it) then l = items.index_to_letter(it.slot) say("REMOVING " .. it.name() .. ".") magic("T" .. l .. "YN") return true end end return false end function plan_go_up() local feat = view.feature_at(0,0) if feat:find("stone_stairs_up") or feat == "escape_hatch_up" or feat == "exit_zot" or feat == "exit_dungeon" or feat == "exit_depths" then if you.mesmerised() then return false end expect_new_location = true magic("<") return true end return false end function plan_go_down() local feat = view.feature_at(0,0) if feat:find("stone_stairs_down") then expect_new_location = true magic(">") return true end return false end function ready_for_lair() if you.god() == "Trog" or you.god() == "Cheibriados" or you.god() == "Okawaru" or you.god() == "Qazlal" or you.god() == "the Shining One" or you.god() == "Lugonu" or you.god() == "Uskayaw" or you.god() == "Xom" or you.god() == "Zin" or (you.god() == "Beogh" or you.god() == "Makhleb" or you.god() == "Yredelemnul") and you.piety_rank() >= 4 or (you.god() == "Ru" or you.god() == "Elyvilon") and you.piety_rank() >= 3 or you.god() == "Hepliaklqana" and you.piety_rank() >= 2 then return true end return false end function feat_is_upstairs(feat) return (feat:find("stone_stairs_up") or feat:find("exit_") and (feat == "exit_hell" or feat == "exit_vaults" or feat == "exit_zot" or feat == "exit_slime_pits" or feat == "exit_orcish_mines" or feat == "exit_lair" or feat == "exit_crypt" or feat == "exit_snake_pit" or feat == "exit_elven_halls" or feat == "exit_tomb" or feat == "exit_swamp" or feat == "exit_shoals" or feat == "exit_spider_nest" or feat == "exit_depths")) end function want_to_stairdance_up() local feat = view.feature_at(0,0) if not feat_is_upstairs(feat) then return false end local n = stairdance_count[where] or 0 if n >= 20 then return false end if you.caught() or you.mesmerised() or you.constricted() or you.rooted() or you.transform() == "tree" or you.transform() == "fungus" or count_bia(3) > 0 or count_sgd(3) > 0 or count_divine_warrior(3) > 0 then return false end local only_when_safe = (you.berserk() or hp_is_low(33)) local follow_count = 0 local other_count = 0 local e for _,e in ipairs(enemy_list) do local dist = supdist(e.x,e.y) if dist == 1 and e.m:stabbability() == 0 and can_use_stairs(e.m:name()) then follow_count = follow_count + 1 else other_count = other_count + 1 end end if only_when_safe and follow_count > 0 then return false end if follow_count == 0 and (reason_to_rest(90) or you.status("barbed spikes")) and not buffed() or other_count > 0 and follow_count > 0 then stairdance_count[where] = n + 1 return true end return false end -- adding some clua for this would be better function can_use_stairs(mname) if mname:find("zombie") or mname:find("skeleton") or mname:find("spectral") or mname:find("simulacrum") or mname:find("oklob") or mname:find("statue") or mname:find("ballistomycete") or mname == "burning bush" or mname == "lightning spire" or mname:find("tentacle") or mname == "silent spectre" or mname == "Geryon" or mname == "Royal Jelly" or mname:find("'s ghost") or mname:find("' ghost") or mname == "swamp worm" or mname == "electric eel" or mname == "kraken" or mname == "lava snake" or mname == "bat" or mname == "unseen horror" or mname == "fire vortex" then return false else return true end end function plan_stairdance_up() if want_to_stairdance_up() then expect_new_location = true -- set travel_destination to the current location in case we -- leave the branch while stairdancing if not travel_destination then travel_destination = cur_branch() end say("STAIRDANCE") magic("<") return true end return false end function want_to_buy(it) local class = it.class(true) if class == "food" or class == "missile" then return false elseif class == "scroll" then local sub = it.subtype() if (sub == "identify" or sub == "remove curse") and count_item("scroll",sub) > 9 then return false elseif sub == "recharging" and count_item("scroll", sub) > 0 then return false end end return autopickup(it, it.name()) end function plan_shop() if view.feature_at(0,0) ~= "enter_shop" or free_inventory_slots() == 0 then return false end if you.berserk() or you.caught() or you.mesmerised() then return false end local it, price, on_list for n,e in ipairs(items.shop_inventory()) do it = e[1] price = e[2] on_list = e[3] if want_to_buy(it) then -- We want the item. Can we afford buying it now? local wealth = you.gold() if price <= wealth then --say("BUYING " .. it.name() .. " (" .. price .. " gold).") magic("<" .. letter(n-1) .. "\ry") return -- Should in theory also work in Bazaar, but doesn't make much sense -- (since we won't really return or acquire money and travel back here) elseif not on_list and not you.where():find("Bazaar") and not zot_soon() then say("SHOPLISTING " .. it.name() .. " (" .. price .. " gold" .. ", have " .. wealth .. ").") magic("<" .. string.upper(letter(n-1))) return end elseif on_list then -- We no longer want the item. Remove it from shopping list. magic("<" .. string.upper(letter(n-1))) return end end return false end function plan_shopping_spree() if game_status ~= "shopping" then return false end which_item = can_afford_any_shoplist_item() if not which_item then -- Remove everything on shoplist clear_out_shopping_list() -- Set travel_destination if necessary so that we will return to D. if not in_branch("D") then travel_destination = "D" end -- record that we are done shopping this game c_persist.done_shopping = true update_game_status() return false end say("SHOPPING SPREE") magic("$" .. letter(which_item - 1)) expect_new_location = true return true end -- Usually, this function should return `1` or `false`. function can_afford_any_shoplist_item() local shoplist = items.shopping_list() if not shoplist then return false end local price for n, entry in ipairs(shoplist) do price = entry[2] -- Since the shopping list holds no reference to the item itself, -- we cannot check want_to_buy() until arriving at the shop. if price <= you.gold() then return n end end return false end -- Clear out shopping list if no affordable items are left before entering Zot function clear_out_shopping_list() local shoplist = items.shopping_list() if not shoplist then return false end say("CLEARING SHOPPING LIST") -- Press ! twice to toggle action to 'delete' local clear_shoplist_magic = "$!!" for n, it in ipairs(shoplist) do clear_shoplist_magic = clear_shoplist_magic .. "a" end magic(clear_shoplist_magic) return false end function plan_simple_go_down() if travel_destination or unshafting() then return false end if (found_branch("L") and ready_for_lair() or where == "D:11") and not util.contains(c_persist.branches_entered, "L") then return false end if where == "Vaults:4" and easy_runes() < 2 then return false end if where == "Tomb:1" or where == "Tomb:2" or where == "Tomb:3" then return false end expect_new_location = true magic("G>") return true end function want_altar() return (you.race() ~= "Demigod" and you.num_runes() == 0 and not util.contains(god_options(), you.god())) end function plan_go_to_temple() local c = c_persist.plan_fail_count["try_go_to_temple"] if c and c >= 100 then return false end if found_branch("T") and (want_altar() or TSO_CONVERSION or LUGONU_CONVERSION) and not util.contains(c_persist.branches_entered, "T") and in_branch("D") then expect_new_location = true magic("GTY") return true end return false end function plan_enter_branch() local br if found_branch("L") and ready_for_lair() and not util.contains(c_persist.branches_entered, "L") and in_branch("D") then br = "L" elseif found_branch("O") and not LATE_ORC and not util.contains(c_persist.branches_entered, "O") and in_branch("D") and util.contains(c_persist.branches_entered, "L") then br = "O" end if br then expect_new_location = true magic("G" .. br .. "\rY") return true end return false end function plan_go_to_portal_entrance() for _, por in ipairs(c_persist.portals_found) do for _, val in ipairs(portal_data) do if val[1] == por then magicfind("@" .. val[2]) return true end end end return false end function plan_go_to_zig() if not where:find("Depths") or game_status ~= "zig" or c_persist.entered_zig then return false else expect_new_location = true magicfind("gateway to a ziggurat") return true end end function plan_go_to_portal_exit() if in_portal() then magic("X<\r") return true end return false end function plan_go_to_abyss_portal() if not where:find("Depths") or not want_to_stay_in_abyss() then return false else expect_new_location = true magicfind("one-way gate to the infinite horrors of the Abyss") return true end end function plan_go_to_pan_portal() if not where:find("Depths") or not want_to_be_in_pan() then return false else expect_new_location = true magicfind("halls of Pandemonium") return true end end function plan_go_to_abyss_downstairs() if where:find("Abyss") and want_to_stay_in_abyss() and where ~= "Abyss:3" and where ~= "Abyss:4" and where ~= "Abyss:5" then magic("X>\r") return true end return false end function plan_go_to_pan_downstairs() if where == "Pan" then magic("X>\r") return true end return false end local pan_failed_rune_count = -1 function want_to_dive_pan() return (where == "Pan" and you.num_runes() > pan_failed_rune_count and (you.have_rune("demonic") and not have_pan_runes() or dislike_pan_level)) end function plan_dive_go_to_pan_downstairs() if want_to_dive_pan() then magic("X>\r") return true end return false end function plan_open_runed_doors() if where ~= "Pan" and (where ~= "Depths:3" or not PAN_RUNE) then return false end for x = -1,1 do for y = -1,1 do if view.feature_at(x,y) == "runed_door" then magic(delta_to_vi(x,y) .. "Y") return true end end end return false end function plan_go_to_abyss_exit() if want_to_stay_in_abyss() then return false end magic("X<\r") return true end function plan_go_to_pan_exit() if where == "Pan" and not want_to_be_in_pan() then magic("X<\r") return true end return false end function plan_dive() if (in_branch("M") and where ~= "Slime:5" or game_status == "hells" and (in_branch("I") and where ~= "Dis:7" or in_branch("G") and where ~= "Geh:7" or in_branch("X") and where ~= "Coc:7" or in_branch("Y") and where ~= "Tar:7")) and not travel_destination then expect_new_location = true magic("G>") return true end return false end function plan_early_new_travel() if game_status == "hells" and (where == "Dis:7" and you.have_rune("iron") or where == "Geh:7" and you.have_rune("obsidian") or where == "Coc:7" and you.have_rune("icy") or where == "Tar:7" and you.have_rune("bone")) and not travel_destination then travel_destination = "H" return plan_continue_travel() end return false end function plan_enter_zig() if not where:find("Depths") or game_status ~= "zig" or c_persist.entered_zig then return false end if view.feature_at(0,0) == "enter_ziggurat" then expect_new_location = true c_persist.entered_zig = true magic(">Y") return true end return false end function plan_enter_portal() for _, por in ipairs(c_persist.portals_found) do if string.find(view.feature_at(0,0), "enter_" .. get_feat_name(por)) then expect_portal = true expect_new_location = true magic(">") return true end return false end return false end function plan_exit_portal() if not in_portal() or you.mesmerised() then return false end if string.find(view.feature_at(0,0), "exit_" .. get_feat_name(where)) then expect_new_location = true magic("<") return true end return false end function plan_enter_abyss() if view.feature_at(0,0) == "enter_abyss" and want_to_stay_in_abyss() then expect_new_location = true magic(">Y") return true end return false end function plan_enter_pan() if view.feature_at(0,0) == "enter_pandemonium" and want_to_be_in_pan() then expect_new_location = true magic(">Y") return true end return false end function plan_go_down_abyss() if view.feature_at(0,0) == "abyssal_stair" and want_to_stay_in_abyss() and where ~= "Abyss:3" and where ~= "Abyss:4" and where ~= "Abyss:5" then expect_new_location = true magic(">") return true end return false end local pan_stair_turn = -100 function plan_go_down_pan() if view.feature_at(0,0) == "transit_pandemonium" or view.feature_at(0,0) == "exit_pandemonium" then if pan_stair_turn == you.turns() then magic("X" .. control('f')) return true end expect_new_location = true pan_stair_turn = you.turns() magic(">Y") return nil -- in case we are trying to leave a rune level end return false end function plan_dive_pan() if not want_to_dive_pan() then return false end if view.feature_at(0,0) == "transit_pandemonium" or view.feature_at(0,0) == "exit_pandemonium" then if pan_stair_turn == you.turns() then pan_failed_rune_count = you.num_runes() return false end expect_new_location = true pan_stair_turn = you.turns() dislike_pan_level = false magic(">Y") return nil -- in case we are trying to leave a rune level end return false end function plan_zig_leave_level() if not where:find("Zig") then return false end if where:find(tostring(ZIG_DIVE)) then if view.feature_at(0,0) == "exit_ziggurat" then magic("") expect_new_location = true return true end return false end function plan_lugonu_exit_abyss() if you.god() ~= "Lugonu" then return false end if (you.berserk() or you.confused() or you.silenced() or starving() or you.piety_rank() < 1 or cmp() < 1) then return false end expect_new_location = true use_ability("Depart the Abyss") return true end function plan_exit_abyss() if view.feature_at(0,0) == "exit_abyss" and not want_to_stay_in_abyss() and not you.mesmerised() and you.transform() ~= "tree" then expect_new_location = true magic("<") return true end return false end function plan_exit_pan() if view.feature_at(0,0) == "exit_pandemonium" and not want_to_be_in_pan() and not you.mesmerised() and you.transform() ~= "tree" then expect_new_location = true magic("<") return true end return false end function plan_step_towards_lair() local x, y, feat if (stepped_on_lair or not found_branch("L")) and (where ~= "Crypt:3" or stepped_on_tomb or not found_branch("W")) then return false end for x = -LOS,LOS do for y = -LOS,LOS do feat = view.feature_at(x,y) if (feat == "enter_lair" or feat == "enter_tomb") and you.see_cell_no_trans(x,y) then if x == 0 and y == 0 then if where == "Crypt:3" then stepped_on_tomb = true else stepped_on_lair = true end return false else lair_step_mode = true local result = move_towards(x,y) lair_step_mode = false return result end end end end return false end function plan_continue_travel() if travel_destination then if in_branch(travel_destination) or not found_branch(travel_destination) then travel_destination = nil return false end expect_new_location = true magic("G" .. travel_destination .. "\rY") return true end return false end function choose_lair_rune_branch() if RUNE_PREFERENCE == "smart" then if crawl.random2(2) == 0 then branch_options = { "N", "P", "S", "A" } else branch_options = { "N", "S", "P", "A" } end elseif RUNE_PREFERENCE == "dsmart" then if crawl.random2(2) == 0 then branch_options = { "N", "S", "P", "A" } else branch_options = { "S", "N", "P", "A" } end elseif RUNE_PREFERENCE == "nowater" then branch_options = { "P", "N", "S", "A" } else -- "random" if crawl.random2(2) == 0 then branch_options = { "P", "N", "S", "A" } else branch_options = { "S", "A", "P", "N" } end end for _, branch_code in ipairs(branch_options) do if found_branch(branch_code) and not util.contains(c_persist.branches_entered, branch_code) then return branch_code end end return nil end function choose_hell_rune_branch() local hell_branches = { "I", "G", "X", "Y" } local untried_branches = {} local count = 0 for _, branch_code in ipairs(hell_branches) do if not util.contains(c_persist.branches_entered, branch_code) then table.insert(untried_branches, branch_code) count = count + 1 end end if count == 0 then return "U" -- depths end return untried_branches[crawl.roll_dice(1,count)] end local depths_loop_count = 0 function plan_new_travel() if cloudy then return false end local back_to_D_places = { "Temple", "Orc:2", "Vaults:4"} if util.contains(back_to_D_places, where) then travel_destination = "D" end if where == "D:11" and not util.contains(c_persist.branches_entered, "L") then travel_destination = "L" end if where == "Lair:6" then if travel.find_deepest_explored("D") == 15 and easy_runes() < 2 then travel_destination = choose_lair_rune_branch() elseif game_status == "slime" then travel_destination = "M" else travel_destination = "D" end end if where == "Snake:4" and you.have_rune("serpentine") then travel_destination = "D" end if where == "Swamp:4" and you.have_rune("decaying") then travel_destination = "D" end if where == "Spider:4" and you.have_rune("gossamer") then travel_destination = "D" end if where == "Shoals:4" and you.have_rune("barnacled") then travel_destination = "D" end if where == "Vaults:5" and you.have_rune("silver") then if game_status == "tomb" then travel_destination = "C" else travel_destination = "D" end end if where == "Slime:5" and you.have_rune("slimy") then travel_destination = "D" end if where == "D:15" then if easy_runes() == 1 and not util.contains(c_persist.branches_entered, "V") or easy_runes() == 2 and util.contains(c_persist.branches_entered, "U") and not you.have_rune("silver") then travel_destination = "V" elseif easy_runes() >= 1 and not util.contains(c_persist.branches_entered, "U") and not (EARLY_SECOND_RUNE and easy_runes() == 1) then travel_destination = "U" elseif you.have_rune("silver") then if game_status == "slime" then travel_destination = "L" elseif game_status == "tomb" then travel_destination = "V" else travel_destination = "U" end elseif LATE_ORC and found_branch("O") and not util.contains(c_persist.branches_entered, "O") then travel_destination = "O" else travel_destination = "L" end end if where == "Depths:5" then if game_status == "zot" then travel_destination = "Z" elseif game_status == "hells" then travel_destination = "H" else if game_status == "shopping" then c_persist.done_shopping = true end if depths_loop_count < 20 then travel_destination = "D" depths_loop_count = depths_loop_count + 1 else game_status = "zot" travel_destination = "Z" end end end if where == "Hell" then travel_destination = choose_hell_rune_branch() end -- travel back from hell ends is handled in plan_early_new_travel() if where == "Crypt:3" then if game_status == "tomb" then travel_destination = "W" else travel_destination = "V" end end if where == "Tomb:3" and you.have_rune("golden") then travel_destination = "C" end return plan_continue_travel() end function plan_fly() if you.xl() >= 14 and intrinsic_flight() and not you.flying() then if cmp() >= 3 and not you.rooted() and not starving() then if use_ability("Fly") then say("FLYING.") return true end end end return false end local did_ancestor_identity = false function plan_ancestor_identity() if you.god() ~= "Hepliaklqana" then return false end if not did_ancestor_identity then use_ability("Ancestor Identity","\b\b\b\b\b\b\b\b\b\b\b\b\b\b\belliptic\ra") did_ancestor_identity = true return true end return false end function plan_ancestor_life() if you.god() ~= "Hepliaklqana" then return false end local ancestor_options = {"Knight", "Battlemage", "Hexer"} if use_ability("Ancestor Life: " .. ancestor_options[crawl.roll_dice(1,3)], "Y") then return true end return false end function plan_sacrifice() if you.god() ~= "Ru" or starving() then return false end -- sacrifices that we won't do for now: words, drink, courage, durability, hand, resistance, purity, health, artifice (on DD) good_sacrifices = { "Sacrifice Artifice", -- 70 "Sacrifice Arcana", -- 25 "Sacrifice Love", -- 25 "Sacrifice Stealth", -- 15 "Sacrifice Essence", -- variable "Sacrifice Nimbleness", -- 30 "Sacrifice Skill", -- 35 "Sacrifice an Eye", -- 20 "Sacrifice Experience", -- 30 "Reject Sacrifices", } -- hack for _,sacrifice in ipairs(good_sacrifices) do if sacrifice == "Sacrifice Nimbleness" then for letter, abil in pairs(you.ability_table()) do if abil == sacrifice then you.train_skill("Fighting",1) say("INVOKING " .. sacrifice .. ".") magic("a" .. letter .. "YY") return true end end elseif use_ability(sacrifice, "YY") then return true end end return false end function plan_find_upstairs() magic("X<\r") return true end function plan_gd1() expect_new_location = true magic("GD1\rY") return true end function plan_zig_go_to_stairs() if not where:find("Zig") then return false end if where:find(tostring(ZIG_DIVE)) then magic("X<\r") else magic("X>\r") end return true end function plan_find_downstairs() -- try to avoid branch entrances by going to a random > from them local feat = view.feature_at(0,0) if feat:find("enter_") or feat == "escape_hatch_down" then local i,j local c = "X" j = crawl.roll_dice(1,12) for i = 1,j do c = (c .. ">") end magic(c .. "\r") return true end magic("X>\r") return true end function set_waypoint() magic(control('w') .. waypoint_parity) did_waypoint = true return true end function clear_level_map(num) level_map[num] = {} for i = -100,100 do level_map[num][i] = {} end stair_dists[num] = {} end function update_level_map(num) local dx,dy = travel.waypoint_delta(num) local val,oldval local staircount = #stair_dists[num] local newcount = staircount local mapqueue = {} local distqueue = {} for j = 1,staircount do distqueue[j] = {} end for x = -LOS,LOS do for y = -LOS,LOS do table.insert(mapqueue, {x+dx,y+dy}) end end local first = 1 local last = #mapqueue local x,y local feat while first < last do if first % 1000 == 0 then coroutine.yield() end x = mapqueue[first][1] y = mapqueue[first][2] first = first + 1 feat = view.feature_at(x-dx,y-dy) if feat ~= "unseen" then if level_map[num][x][y] == nil then for ddx = -1,1 do for ddy = -1,1 do if ddx ~= 0 or ddy ~= 0 then last = last + 1 mapqueue[last] = {x+ddx,y+ddy} end end end end if travel.feature_traversable(feat) and not travel.feature_solid(feat) then if level_map[num][x][y] ~= "." then if feat_is_upstairs(feat) then newcount = #stair_dists[num]+1 stair_dists[num][newcount] = {} for i = -100,100 do stair_dists[num][newcount][i] = {} end stair_dists[num][newcount][x][y] = 0 distqueue[newcount] = {{x,y},} end for j = 1,staircount do oldval = stair_dists[num][j][x][y] for ddx = -1,1 do for ddy = -1,1 do if (ddx ~= 0 or ddy ~= 0) then val = stair_dists[num][j][x+ddx][y+ddy] if val ~= nil and (oldval == nil or oldval > val+1) then oldval = val+1 end end end end if stair_dists[num][j][x][y] ~= oldval then stair_dists[num][j][x][y] = oldval table.insert(distqueue[j],{x,y}) end end end level_map[num][x][y] = "." else level_map[num][x][y] = "#" end end end for j = 1,newcount do update_dist_map(stair_dists[num][j],distqueue[j]) end end function update_dist_map(dist_map,queue) local first = 1 local last = #queue local x,y,val while first <= last do if first % 300 == 0 then coroutine.yield() end x = queue[first][1] y = queue[first][2] first = first + 1 val = dist_map[x][y] + 1 for dx = -1,1 do for dy = -1,1 do if (dx ~= 0 or dy ~= 0) and level_map[waypoint_parity][x+dx][y+dy] == "." then oldval = dist_map[x+dx][y+dy] if oldval == nil or oldval > val then dist_map[x+dx][y+dy] = val last = last+1 queue[last] = {x+dx,y+dy} end end end end end end function find_good_stairs() good_stair_list = { } if not is_waypointable(where) then return end local num = waypoint_parity local dx,dy = travel.waypoint_delta(num) local staircount = #(stair_dists[num]) local pdist,mdist,minmdist,speed_diff local pspeed = player_speed_num() for i = 1,staircount do pdist = stair_dists[num][i][dx][dy] if pdist == nil then pdist = 10000 end minmdist = 1000 for _,e in ipairs(enemy_list) do mdist = stair_dists[num][i][dx+e.x][dy+e.y] if mdist == nil then mdist = 10000 end speed_diff = mon_speed_num(e.m) - pspeed if speed_diff > 1 then mdist = mdist / 2 elseif speed_diff > 0 then mdist = mdist / 1.5 end if is_ranged(e.m) then mdist = mdist - 4 end if mdist < minmdist then minmdist = mdist end end if pdist < minmdist then table.insert(good_stair_list, i) end end end function stair_improvement(x,y) if not is_waypointable(where) then return 10000 end if x == 0 and y == 0 then if feat_is_upstairs(view.feature_at(0,0)) then return 0 else return 10000 end end local num = waypoint_parity local dx,dy = travel.waypoint_delta(num) local val local minval = 10000 for _,i in ipairs(good_stair_list) do val = stair_dists[num][i][dx+x][dy+y] if val < stair_dists[num][i][dx][dy] and val < minval then minval = val end end return minval end function set_stair_target(c) local x,y = vi_to_delta(c) local num = waypoint_parity local dx,dy = travel.waypoint_delta(num) local val local minval = 10000 local best_stair for _,i in ipairs(good_stair_list) do val = stair_dists[num][i][dx+x][dy+y] if val < stair_dists[num][i][dx][dy] and val < minval then minval = val best_stair = i end end target_stair = best_stair end function plan_continue_flee() if you.turns() >= last_flee_turn + 10 or not target_stair then return false end if danger or not reason_to_rest(90) or you.transform() == "tree" or count_bia(3) > 0 or count_sgd(3) > 0 or count_divine_warrior(3) > 0 or you.status("barbed spikes") or you.confused() or buffed() then return false end local num = waypoint_parity local dx,dy = travel.waypoint_delta(num) local val for x = -1,1 do for y = -1,1 do if is_traversable(x,y) and not is_solid(x,y) and not monster_in_way(x,y) and view.is_safe_square(x,y) and not view.withheld(x,y) then val = stair_dists[num][target_stair][dx+x][dy+y] if val and val < stair_dists[num][target_stair][dx][dy] then if SPAM then say("STILL FLEEEEING.") end magic(delta_to_vi(x,y) .. "YY") return true end end end end return false end function plan_stuck() if starving() then return random_step("starving") end stuck_turns = stuck_turns + 1 if stuck_turns > QUIT_TURNS then magic(control('q') .. "yes\r") return true end return random_step("stuck") -- panic("Stuck!") end function plan_full_inventory_panic() if FULL_INVENTORY_PANIC and free_inventory_slots() == 0 then panic("Inventory is full!") else return false end end function unshafting() return (where_shafted_from and (where_shafted_from ~= you.where()) and not where:find("Slime")) end function plan_unshaft() if unshafting() and where ~= "Temple" then --say("Trying to unshaft to " .. where_shafted_from .. ".") expect_new_location = true magic("G<") return true end return false end function plan_not_coded() panic("Need to code this!") return true end function random_step(reason) if you.mesmerised() then say("Waiting to end mesmerise (" .. reason .. ").") magic("s") return true end local i,j local dx,dy local count = 0 for i = -1,1 do for j = -1,1 do if not (i == 0 and j == 0) and is_traversable(i,j) and not view.withheld(i,j) and not monster_in_way(i,j) then count = count + 1 if crawl.one_chance_in(count) then dx = i dy = j end end end end if count > 0 then say("Stepping randomly (" .. reason .. ").") magic(delta_to_vi(dx,dy) .. "YY") return true else say("Standing still (" .. reason .. ").") magic("s") return true end -- return false end function plan_disturbance_random_step() if crawl.messages(5):find("There is a strange disturbance nearby!") then return random_step("disturbance") end return false end function plan_wait() rest() return true end function plan_flail_at_invis() if options.autopick_on then invisi_count = 0 invisi_sigmund = false return false end if invisi_count > 100 then say("Invisible monster not found???") invisi_count = 0 invisi_sigmund = false magic(control('a')) return true end invisi_count = invisi_count + 1 local x,y for x = -1,1 do for y = -1,1 do if supdist(x,y) > 0 and view.invisible_monster(x,y) then magic(control(delta_to_vi(x,y))) return true end end end if invisi_sigmund and (sigmund_dx ~= 0 or sigmund_dy ~= 0) then x = sigmund_dx y = sigmund_dy if adjacent(x,y) and is_traversable(x,y) then magic(control(delta_to_vi(x,y))) return true elseif x == 0 and is_traversable(0,sign(y)) then magic(delta_to_vi(0,sign(y))) return true elseif y == 0 and is_traversable(sign(x),0) then magic(delta_to_vi(sign(x),0)) return true end end local success = false local tries = 0 while not success and tries < 100 do x = -1 + crawl.random2(3) y = -1 + crawl.random2(3) tries = tries + 1 if (x ~= 0 or y ~= 0) and is_traversable(x,y) and view.feature_at(x,y) ~= "closed_door" and view.feature_at(x,y) ~= "runed_door" then success = true end end if tries >= 100 then magic("s") else magic(control(delta_to_vi(x,y))) end return true end function plan_cure_confusion() if you.confused() and (danger or not options.autopick_on) then if view.cloud_at(0,0) == "noxious fumes" and not meph_immune() then if you.god() == "Beogh" then magic("s") -- avoid Beogh penance return true end return false end if drink_by_name("curing") then say("(to cure confusion)") return true end if can_purification() then purification() return true end if you.god() == "Beogh" then magic("s") -- avoid Beogh penance return true end end return false end function plan_cure_starving() if you.hunger_name() == "fainting" or you.hunger_name() == "starving" then return eat_permafood(true) end return false end -- curing poison/confusion with purification is handled elsewhere function plan_special_purification() if not can_purification() then return false end if you.slowed() or you.petrifying() then purification() return true end local str, mstr = you.strength() local int, mint = you.intelligence() local dex, mdex = you.dexterity() if str < mstr and (str < mstr-5 or str < 3) or int < mint and int < 3 or dex < mdex and (dex < mdex-8 or dex < 3) then purification() return true end return false end function plan_teleport() if can_teleport() and want_to_teleport() then -- return false return teleport() end return false end function plan_orbrun_teleport() if can_teleport() and want_to_orbrun_teleport() then return teleport() end return false end function plan_tomb_use_hatch() if (where == "Tomb:2" and not you.have_rune("golden") or where == "Tomb:1") and view.feature_at(0,0) == "escape_hatch_down" then expect_new_location = true prev_hatch_dist = 1000 magic(">") return true end if (where == "Tomb:3" and you.have_rune("golden") or where == "Tomb:2") and view.feature_at(0,0) == "escape_hatch_up" then expect_new_location = true prev_hatch_dist = 1000 magic("<") return true end return false end function plan_tomb_go_to_final_hatch() if where == "Tomb:2" and not you.have_rune("golden") and view.feature_at(0,0) ~= "escape_hatch_down" then magic("X>\r") return true end return false end function plan_tomb_go_to_hatch() if where == "Tomb:3" then if you.have_rune("golden") and view.feature_at(0,0) ~= "escape_hatch_up" then magic("X<\r") return true end elseif where == "Tomb:2" then if not you.have_rune("golden") and view.feature_at(0,0) == "escape_hatch_down" then return false end if view.feature_at(0,0) == "escape_hatch_up" then local x,y = travel.waypoint_delta(waypoint_parity) local new_hatch_dist = supdist(x,y) if new_hatch_dist >= prev_hatch_dist and (x ~= prev_hatch_x or y ~= prev_hatch_y) then return false end prev_hatch_dist = new_hatch_dist prev_hatch_x = x prev_hatch_y = y end magic("X<\r") return true elseif where == "Tomb:1" then if view.feature_at(0,0) == "escape_hatch_down" then local x,y = travel.waypoint_delta(waypoint_parity) local new_hatch_dist = supdist(x,y) if new_hatch_dist >= prev_hatch_dist and (x ~= prev_hatch_x or y ~= prev_hatch_y) then return false end prev_hatch_dist = new_hatch_dist prev_hatch_x = x prev_hatch_y = y end magic("X>\r") return true end return false end function plan_stuck_clear_exclusions() local n = clear_exclusion_count[where] or 0 if n > 20 then return false end clear_exclusion_count[where] = n+1 magic("X" .. control('e')) return true end function plan_swamp_clear_exclusions() if where ~= "Swamp:4" then return false end magic("X" .. control('e')) return true end function plan_swamp_go_to_rune() if where ~= "Swamp:4" or you.have_rune("decaying") then return false end if last_swamp_fail_count == c_persist.plan_fail_count.try_swamp_go_to_rune then swamp_rune_reachable = true end last_swamp_fail_count = c_persist.plan_fail_count.try_swamp_go_to_rune magicfind("@decaying rune") return true end function plan_swamp_clouds_hack() if where ~= "Swamp:4" then return false end if you.have_rune("decaying") and can_teleport() and teleport() then return true end if swamp_rune_reachable then say("Waiting for clouds to move.") magic("s") return true end local x,y local bestx,besty local dist local bestdist = 11 for x = -1,1 do for y = -1,1 do if supdist(x,y) > 0 and view.is_safe_square(x,y) and not view.withheld(x,y) and not monster_in_way(x,y) then dist = 11 for x2 = -LOS,LOS do for y2 = -LOS,LOS do if (view.cloud_at(x2,y2) == "freezing vapour" or view.cloud_at(x2,y2) == "foul pestilence") and you.see_cell_no_trans(x2,y2) and (you.god() ~= "Qazlal" or not view.is_safe_square(x2,y2)) then if supdist(x-x2,y-y2) < dist then dist = supdist(x-x2,y-y2) end end end end if dist < bestdist then bestx = x besty = y bestdist = dist end end end end if bestdist < 11 then magic(delta_to_vi(bestx,besty) .. "Y") return true end for x = -LOS,LOS do for y = -LOS,LOS do if (view.cloud_at(x,y) == "freezing vapour" or view.cloud_at(x,y) == "foul pestilence") and you.see_cell_no_trans(x,y) then return random_step("Swamp:4") end end end return plan_stuck_teleport() end function plan_dig_grate() local grate_mon_list local grate_count_needed = 3 if where:find("Zot") then grate_mon_list = {"draconian stormcaller", "draconian scorcher"} elseif where == "Depths:5" then grate_mon_list = {"draconian stormcaller", "draconian scorcher", "angel", "daeva", "lich"} elseif where:find("Depths") then grate_mon_list = {"angel", "daeva", "lich"} elseif where == "Pan" or where == "Geh:7" then grate_mon_list = {"smoke demon"} grate_count_needed = 1 elseif where:find("Zig") then grate_mon_list = {""} grate_count_needed = 1 else return false end local e for _,e in ipairs(enemy_list) do local name = e.m:name() if contains_string_in(name,grate_mon_list) and not will_tab(0,0,e.x,e.y,tabbable_square) then local grate_count = 0 local dx,dy local closest_grate = 20 local gx,gy,cgx,cgy for dx = -1,1 do for dy = -1,1 do gx = e.x + dx gy = e.y + dy if supdist(gx,gy) <= LOS and view.feature_at(gx,gy) == "iron_grate" then grate_count = grate_count + 1 if abs(gx) + abs(gy) < closest_grate and you.see_cell_solid_see(gx,gy) then cgx = gx cgy = gy closest_grate = abs(gx) + abs(gy) end end end end if grate_count >= grate_count_needed and closest_grate < 20 then local c = find_item("wand", "digging") if c and can_zap() then say("ZAPPING " .. item(c).name() .. ".") magic("V" .. letter(c) .. "r" .. vector_move(cgx,cgy) .. "\r") return true end end end end return false end function plan_stuck_dig_grate() local dx,dy local closest_grate = 20 local cx,cy for dx = -LOS,LOS do for dy = -LOS,LOS do if view.feature_at(dx,dy) == "iron_grate" then if abs(dx) + abs(dy) < closest_grate and you.see_cell_solid_see(dx,dy) then cx = dx cy = dy closest_grate = abs(dx) + abs(dy) end end end end if closest_grate < 20 then local c = find_item("wand", "digging") if c and can_zap() then say("ZAPPING " .. item(c).name() .. ".") magic("V" .. letter(c) .. "r" .. vector_move(cx,cy) .. "\r") return true end end return false end function plan_stuck_forget_map() if not cloudy and not danger and (where == "Slime:5" and not you.have_rune("slimy") or where == "Geh:7" and not you.have_rune("obsidian")) then magic("X" .. control('f')) return true end return false end function plan_stuck_cloudy() if cloudy and not hp_is_low(50) and not you.mesmerised() then return random_step("cloudy") end return false end function plan_stuck_teleport() if can_teleport() then return teleport() end return false end function read(c) if not can_read() then return false end say("READING " .. item(c).name() .. ".") magic("r" .. letter(c)) return true end function read2(c,etc) if not can_read() then return false end local int, mint = you.intelligence() if int <= 0 then -- failing to read a scroll due to intzero can make qw unhappy return false end say("READING " .. item(c).name() .. ".") magic("r" .. letter(c) .. etc) return true end function drink(c) if not can_drink() then return false end say("DRINKING " .. item(c).name() .. ".") magic("q" .. letter(c)) return true end function selfzap(c) if not can_zap() then return false end say("ZAPPING " .. item(c).name() .. ".") magic("V" .. letter(c) .. ".") return true end function read_by_name(name) local c = find_item("scroll", name) if (c and read(c)) then return true end return false end function drink_by_name(name) local c = find_item("potion", name) if (c and drink(c)) then return true end return false end function selfzap_by_name(name) local c = find_item("wand", name) if (c and selfzap(c)) then return true end return false end function teleport() if read_by_name("teleportation") then dd_hw_meter = 0 return true end return false end function plan_cure_poison() if you.poison_survival() <= 1 and you.poisoned() or you.race() == "Deep Dwarf" and you.poison_survival() <= chp() - 10 then if drink_by_name("curing") then say("(to cure poison)") return true end end if you.poison_survival() <= 1 and you.poisoned() then if can_hand() then hand() return true end if can_purification() then purification() return true end end return false end function move_towards(dx, dy) if you.transform() == "tree" or you.transform() == "fungus" or you.confused() and (count_bia(1) > 0 or count_sgd(1) > 0 or count_divine_warrior(1) > 0) then magic("s") return true end local move = nil if abs(dx) > abs(dy) then if abs(dy) == 1 then move = try_move(sign(dx), 0) end if move == nil then move = try_move(sign(dx), sign(dy)) end if move == nil then move = try_move(sign(dx), 0) end if move == nil and abs(dx) > abs(dy)+1 then move = try_move(sign(dx), 1) end if move == nil and abs(dx) > abs(dy)+1 then move = try_move(sign(dx), -1) end if move == nil then move = try_move(0, sign(dy)) end elseif abs(dx) == abs(dy) then move = try_move(sign(dx), sign(dy)) if move == nil then move = try_move(sign(dx), 0) end if move == nil then move = try_move(0, sign(dy)) end else if abs(dx) == 1 then move = try_move(0, sign(dy)) end if move == nil then move = try_move(sign(dx), sign(dy)) end if move == nil then move = try_move(0, sign(dy)) end if move == nil and abs(dy) > abs(dx)+1 then move = try_move(1, sign(dy)) end if move == nil and abs(dy) > abs(dx)+1 then move = try_move(-1, sign(dy)) end if move == nil then move = try_move(sign(dx), 0) end end if move == nil or move_count >= 10 then add_ignore(dx,dy) table.insert(failed_move, 20*dx+dy) return false else if (abs(dx) > 1 or abs(dy) > 1) and not lair_step_mode and view.feature_at(dx,dy) ~= "closed_door" then did_move = true if monster_array[dx][dy] or did_move_towards_monster > 0 then local move_x, move_y = vi_to_delta(move) target_memory_x = dx - move_x target_memory_y = dy - move_y did_move_towards_monster = 2 end end if lair_step_mode then local move_x, move_y = vi_to_delta(move) if view.feature_at(move_x, move_y) == "shallow_water" then return false end end magic(move .. "Y") return true end end function plan_continue_tab() if did_move_towards_monster == 0 then return false end if supdist(target_memory_x, target_memory_y) == 0 then return false end if not options.autopick_on then return false end return move_towards(target_memory_x, target_memory_y) end function add_ignore(dx,dy) m = monster_array[dx][dy] if not m then return end name = m:name() if not util.contains(ignore_list, name) then table.insert(ignore_list, name) crawl.setopt("runrest_ignore_monster ^= " .. name .. ":1") --say("Ignoring " .. name .. ".") end end function remove_ignore(dx,dy) m = monster_array[dx][dy] name = m:name() for i,mname in ipairs(ignore_list) do if mname == name then table.remove(ignore_list, i) crawl.setopt("runrest_ignore_monster -= " .. name .. ":1") --say("Unignoring " .. name .. ".") return end end end function clear_ignores() local size = #ignore_list local mname local i if size > 0 then for i = 1, size do mname = table.remove(ignore_list) crawl.setopt("runrest_ignore_monster -= " .. mname .. ":1") --say("Unignoring " .. mname .. ".") end end end -- this gets stuck if netted, confused, etc function attack_reach(x, y) magic('vr' .. vector_move(x, y) .. '.') end function attack_melee(x, y) if you.confused() then if count_bia(1) > 0 or count_sgd(1) > 0 or count_divine_warrior(1) > 0 then magic("s") return elseif you.transform() == "tree" then magic(control(delta_to_vi(x, y)) .. "Y") return end end if monster_array[x][y]:attitude() == ATT_NEUTRAL then if you.god() == "the Shining One" or you.god() == "Elyvilon" or you.god() == "Zin" then magic("s") else magic(control(delta_to_vi(x, y))) end end magic(delta_to_vi(x, y) .. "Y") end function make_attack(x, y, info) if info.attack_type == 2 then attack_melee(x, y) elseif info.attack_type == 1 then attack_reach(x, y) else return move_towards(x, y) end return true end function use_ability(name, extra, mute) for letter, abil in pairs(you.ability_table()) do if abil == name then if not mute or SPAM then say("INVOKING " .. name .. ".") end magic("a" .. letter .. (extra or "")) return true end end end function note(x) crawl.take_note(you.turns() .. " ||| " .. x) end function say(x) crawl.mpr(you.turns() .. " ||| " .. x) note(x) end -- these few functions are called directly from ready() function record_portal_found(por) if not util.contains(c_persist.portals_found, por) then say("Found " .. por .. ".") table.insert(c_persist.portals_found, por) end end function check_messages() local recent_messages = crawl.messages(20) local very_recent_messages = crawl.messages(5) if very_recent_messages:find("Sigmund flickers and vanishes") then invisi_sigmund = true end if very_recent_messages:find("Your surroundings suddenly seem different") then invisi_sigmund = false end str1 = "Your pager goes off" str2 = "qwqwqw" if recent_messages:find(str1) then a = recent_messages:reverse():find(str1:reverse()) b = recent_messages:reverse():find(str2:reverse()) if (not b) or a < b then have_message = true end end if in_portal() then return false end if recent_messages:find("Found") then for _, value in ipairs(portal_data) do if recent_messages:find(value[2]) then record_portal_found(value[1]) end end end end function plan_message() if read_message then crawl.setopt("clear_messages = false") magic("_") read_message = false else crawl.setopt("clear_messages = true") magic(":qwqwqw\r") read_message = true have_message = false crawl.delay(2500) end end ---------------------------------------- -- cascading plans: this is the bot's flowchart for using the above plans function cascade(plans) local plan_turns = {} local plan_result = {} return function () for i, plandata in ipairs(plans) do plan = plandata[1] if you.turns() ~= plan_turns[plan] or plan_result[plan] == nil then --say(plandata[2]) result = plan() if not automatic then return true end plan_turns[plan] = you.turns() plan_result[plan] = result if result == nil or result == true then if DELAYED and result == true then crawl.delay(next_delay) end next_delay = DELAY_TIME return nil end elseif plan_turns[plan] and plan_result[plan] == true then if not plandata[2]:find("^try") then panic(plandata[2] .. " failed despite returning true.") end if not c_persist.plan_fail_count[plandata[2]] then c_persist.plan_fail_count[plandata[2]] = 0 end c_persist.plan_fail_count[plandata[2]] = c_persist.plan_fail_count[plandata[2]] + 1 end end return false end end -- any plan that might not know whether or not it successfully took an action -- (e.g. autoexplore) should prepend "try_" to its text plan_pre_explore = cascade { {plan_fly, "fly"}, {plan_ancestor_life, "ancestor_life"}, {plan_sacrifice, "sacrifice"}, {plan_bless_weapon, "bless_weapon"}, {plan_upgrade_weapon, "upgrade_weapon"}, {plan_maybe_upgrade_armour, "maybe_upgrade_armour"}, {plan_use_good_consumables, "use_good_consumables"}, } -- hack plan_pre_explore2 = cascade { {plan_disturbance_random_step, "disturbance_random_step"}, {plan_upgrade_armour, "upgrade_armour"}, {plan_upgrade_amulet, "upgrade_amulet"}, {plan_upgrade_rings, "upgrade_rings"}, {plan_read_id, "try_read_id"}, {plan_quaff_id, "quaff_id"}, {plan_use_id_scrolls, "use_id_scrolls"}, {plan_drop_other_items, "drop_other_items"}, {plan_full_inventory_panic, "full_inventory_panic"}, } -- hack plan_emergency = cascade { {plan_cure_starving, "cure_starving"}, {plan_special_purification, "special_purification"}, {plan_cure_confusion, "cure_confusion"}, {plan_coward_step, "coward_step"}, {plan_flee_step, "flee_step"}, {plan_remove_terrible_jewellery, "remove_terrible_jewellery"}, {plan_teleport, "teleport"}, {plan_cure_bad_poison, "cure_bad_poison"}, {plan_drain_life, "drain_life"}, {plan_heal_wounds, "heal_wounds"}, {plan_cloud_step, "cloud_step"}, {plan_hand, "hand"}, {plan_haste, "haste"}, {plan_resistance, "resistance"}, {plan_heroism, "heroism"}, {plan_bia, "bia"}, {plan_sgd, "sgd"}, {plan_divine_warrior, "divine_warrior"}, {plan_apocalypse, "try_apocalypse"}, {plan_slouch, "try_slouch"}, {plan_hydra_destruction, "try_hydra_destruction"}, {plan_grand_finale, "grand_finale"}, {plan_wield_weapon, "wield_weapon"}, {plan_swap_weapon, "swap_weapon"}, {plan_water_step, "water_step"}, {plan_finesse, "finesse"}, {plan_dig_grate, "try_dig_grate"}, {plan_might, "might"}, {plan_berserk, "berserk"}, {plan_continue_flee, "continue_flee"}, {plan_other_step, "other_step"}, } -- hack plan_orbrun_emergency = cascade { {plan_cure_starving, "cure_starving"}, {plan_special_purification, "special_purification"}, {plan_cure_confusion, "cure_confusion"}, {plan_orbrun_teleport, "orbrun_teleport"}, {plan_orbrun_heal_wounds, "orbrun_heal_wounds"}, {plan_orbrun_finesse, "orbrun_finesse"}, {plan_orbrun_haste, "orbrun_haste"}, {plan_orbrun_heroism, "orbrun_heroism"}, {plan_orbrun_divine_warrior, "orbrun_divine_warrior"}, {plan_hand, "hand"}, {plan_resistance, "resistance"}, {plan_wield_weapon, "wield_weapon"}, {plan_orbrun_might, "orbrun_might"}, } -- hack plan_eatrest = cascade { {plan_eat_chunk, "eat_chunk"}, {plan_eat_permafood, "eat_permafood"}, {plan_dd_hand_for_healing, "dd_hand_for_healing"}, {plan_rest, "rest"}, {plan_handle_corpses, "handle_corpses"}, {plan_find_corpses, "try_find_corpses"}, {plan_eat_anyway, "eat_anyway"}, } -- hack plan_abyss_eatrest = cascade { {plan_eat_chunk, "eat_chunk"}, {plan_eat_permafood, "eat_permafood"}, {plan_go_to_abyss_exit, "try_go_to_abyss_exit"}, {plan_abyss_hand, "abyss_hand"}, {plan_abyss_rest, "rest"}, {plan_handle_corpses, "handle_corpses"}, {plan_find_corpses, "try_find_corpses"}, {plan_eat_anyway, "eat_anyway"}, {plan_go_down_abyss, "go_down_abyss"}, {plan_go_to_abyss_downstairs, "try_go_to_abyss_downstairs"}, } -- hack plan_orbrun_eatrest = cascade { {plan_orbrun_eat_permafood, "orbrun_eat_permafood"}, {plan_orbrun_rest, "orbrun_rest"}, {plan_orbrun_hand, "orbrun_hand"}, } -- hack plan_explore = cascade { {plan_unshaft, "try_unshaft"}, {plan_continue_travel, "try_continue_travel"}, {plan_enter_portal, "enter_portal"}, {plan_go_to_portal_entrance, "try_go_to_portal_entrance"}, {plan_enter_abyss, "enter_abyss"}, {plan_go_to_abyss_portal, "try_go_to_abyss_portal"}, {plan_enter_pan, "enter_pan"}, {plan_go_to_pan_portal, "try_go_to_pan_portal"}, {plan_enter_zig, "enter_zig"}, {plan_go_to_zig, "try_go_to_zig"}, {plan_dive, "try_dive"}, {plan_dive_pan, "dive_pan"}, {plan_dive_go_to_pan_downstairs, "try_dive_go_to_pan_downstairs"}, {plan_early_new_travel, "try_early_new_travel"}, {plan_autoexplore, "try_autoexplore"}, } -- hack plan_explore2 = cascade { {plan_zig_leave_level, "zig_leave_level"}, {plan_zig_go_to_stairs, "try_zig_go_to_stairs"}, {plan_exit_portal, "exit_portal"}, {plan_go_to_portal_exit, "try_go_to_portal_exit"}, {plan_open_runed_doors, "open_runed_doors"}, {plan_exit_pan, "exit_pan"}, {plan_go_to_pan_exit, "try_go_to_pan_exit"}, {plan_go_down_pan, "try_go_down_pan"}, {plan_go_to_pan_downstairs, "try_go_to_pan_downstairs"}, {plan_enter_branch, "try_enter_branch"}, {plan_shopping_spree, "try_shopping_spree"}, {plan_simple_go_down, "try_simple_go_down"}, {plan_new_travel, "try_new_travel"}, } -- hack plan_move = cascade { {plan_ancestor_identity, "try_ancestor_identity"}, {plan_join_beogh, "join_beogh"}, {plan_shop, "shop"}, {plan_stairdance_up, "stairdance_up"}, {plan_emergency, "emergency"}, {plan_recall, "recall"}, {plan_recall_ancestor, "try_recall_ancestor"}, {plan_recite, "try_recite"}, {plan_wait_for_melee, "wait_for_melee"}, {plan_starting_spell, "try_starting_spell"}, {plan_wait_spit, "try_wait_spit"}, {plan_wait_throw, "try_wait_throw"}, {plan_wait_wait, "wait_wait"}, {plan_attack, "attack"}, {plan_cure_poison, "cure_poison"}, {plan_flail_at_invis, "try_flail_at_invis"}, {plan_burn_spellbooks, "try_burn_spellbooks"}, {plan_eatrest, "eatrest"}, {plan_pre_explore, "pre_explore"}, {plan_step_towards_lair, "step_towards_lair"}, {plan_continue_tab, "continue_tab"}, {plan_abandon_god, "abandon_god"}, {plan_unwield_weapon, "unwield_weapon"}, {plan_convert, "convert"}, {plan_join_god, "try_join_god"}, -- bug with faded altar not taking time {plan_find_conversion_altar, "try_find_conversion_altar"}, {plan_find_altar, "try_find_altar"}, {plan_go_to_temple, "try_go_to_temple"}, {plan_explore, "explore"}, {plan_pre_explore2, "pre_explore2"}, {plan_explore2, "explore2"}, {plan_tomb_go_to_final_hatch, "try_tomb_go_to_final_hatch"}, {plan_tomb_go_to_hatch, "try_tomb_go_to_hatch"}, {plan_tomb_use_hatch, "tomb_use_hatch"}, {plan_swamp_clear_exclusions, "try_swamp_clear_exclusions"}, {plan_swamp_go_to_rune, "try_swamp_go_to_rune"}, {plan_swamp_clouds_hack, "swamp_clouds_hack"}, {plan_stuck_clear_exclusions, "try_stuck_clear_exclusions"}, {plan_stuck_dig_grate, "try_stuck_dig_grate"}, {plan_stuck_cloudy, "stuck_cloudy"}, {plan_stuck_forget_map, "try_stuck_forget_map"}, {plan_stuck_teleport, "stuck_teleport"}, {plan_stuck, "stuck"}, } -- hack plan_orbrun_move = cascade { {plan_orbrun_emergency, "orbrun_emergency"}, {plan_recall, "recall"}, {plan_recall_ancestor, "try_recall_ancestor"}, {plan_attack, "attack"}, {plan_cure_poison, "cure_poison"}, {plan_orbrun_eatrest, "orbrun_eatrest"}, {plan_go_up, "go_up"}, {plan_fly, "fly"}, {plan_use_good_consumables, "use_good_consumables"}, {plan_find_upstairs, "try_find_upstairs"}, {plan_disturbance_random_step, "disturbance_random_step"}, {plan_stuck_clear_exclusions, "try_stuck_clear_exclusions"}, {plan_stuck_cloudy, "stuck_cloudy"}, {plan_stuck_teleport, "stuck_teleport"}, {plan_autoexplore, "try_autoexplore"}, {plan_gd1, "try_gd1"}, {plan_stuck, "stuck"}, } -- hack plan_abyss_move = cascade { {plan_lugonu_exit_abyss, "lugonu_exit_abyss"}, {plan_exit_abyss, "exit_abyss"}, {plan_emergency, "emergency"}, {plan_recall_ancestor, "try_recall_ancestor"}, {plan_recite, "try_recite"}, {plan_attack, "attack"}, {plan_cure_poison, "cure_poison"}, {plan_flail_at_invis, "try_flail_at_invis"}, {plan_abyss_eatrest, "abyss_eatrest"}, {plan_pre_explore, "pre_explore"}, {plan_autoexplore, "try_autoexplore"}, {plan_pre_explore2, "pre_explore2"}, {plan_stuck_cloudy, "stuck_cloudy"}, {plan_wait, "wait"}, } -- hack ---------------------------------------- -- skill selection local skill_list = {"Fighting","Short Blades","Long Blades","Axes","Maces & Flails", "Polearms","Staves","Unarmed Combat","Bows","Crossbows", "Throwing","Slings","Armour","Dodging","Shields", "Invocations","Evocations","Stealth","Spellcasting", "Conjurations","Hexes","Charms","Summonings","Necromancy", "Translocations","Transmutations","Fire Magic","Ice Magic", "Air Magic","Earth Magic","Poison Magic"} function choose_single_skill(sk) you.train_skill(sk, 1) for _,sk2 in ipairs(skill_list) do if sk ~= sk2 then you.train_skill(sk2, 0) end end end function skill_value(sk) if you.god() == "Okawaru" and sk ~= "Fighting" and sk ~= "Invocations" and you.base_skill(sk) >= 22 then return 0 end if sk == "Dodging" then local str,_ = you.strength() if str < 1 then str = 1 end local dex,_ = you.dexterity() local evp_adj = max(armour_evp() - 3, 0) local penalty_factor if evp_adj >= str then penalty_factor = str / (2 * evp_adj) else penalty_factor = 1 - evp_adj / (2 * str) end if you.race() == "Tengu" and you.xl() >= 14 then penalty_factor = penalty_factor * 1.2 -- flying EV mult end return 18 * math.log(1 + dex/18) / (20 + 2 * body_size()) * penalty_factor elseif sk == "Armour" then local str,_ = you.strength() if str < 0 then str = 0 end local val1 = 2/225 * armour_evp()^2 / (3 + str) local val2 = base_ac()/22 return val1 + val2 elseif sk == "Fighting" then return 0.75 elseif sk == "Shields" then local shield = items.equipped_at("Shield") if not shield then return 0 end return (at_target_shield_skill() and 0.2 or 0.75) elseif sk == "Invocations" then if you.god() == "Uskayaw" or you.god() == "Zin" then return 0.75 elseif you.god() == "Elyvilon" then if you.piety_rank() >= 4 and you.race() == "Deep Dwarf" then return 1 else return 0.5 end else return 0 end elseif sk == wskill() then return (at_min_delay() and 0.5 or 1.5) end end function choose_skills() skills = {} -- Choose one martial skill to train. martial_skills = { wskill(), "Fighting", "Shields", "Armour", "Dodging", "Invocations"} local best_sk local best_utility = 0 local utility for _,sk in ipairs(martial_skills) do if you.skill_cost(sk) then utility = skill_value(sk) / you.skill_cost(sk) if utility > best_utility then best_utility = utility best_sk = sk end end end if best_utility > 0 then table.insert(skills, best_sk) end -- Choose one MP skill to train. mp_skill = "Evocations" if you.god() == "Makhleb" or you.god() == "Cheibriados" or you.god() == "Okawaru" or you.god() == "Yredelemnul" or you.god() == "Beogh" or you.god() == "Qazlal" or you.god() == "the Shining One" or you.god() == "Lugonu" or you.god() == "Hepliaklqana" or you.god() == "Uskayaw" or you.god() == "Elyvilon" or you.god() == "Zin" then mp_skill = "Invocations" elseif you.god() == "Ru" or you.god() == "Xom" then mp_skill = "Spellcasting" end mp_skill_level = you.base_skill(mp_skill) bmp = you.base_mp() if you.god() == "Makhleb" and you.piety_rank() >= 2 and mp_skill_level < 15 then table.insert(skills, mp_skill) elseif you.god() == "the Shining One" and you.piety_rank() >= 5 and mp_skill_level < 12 then table.insert(skills, mp_skill) elseif you.god() == "Okawaru" and you.piety_rank() >= 1 and mp_skill_level < 4 then table.insert(skills, mp_skill) elseif you.god() == "Okawaru" and you.piety_rank() >= 4 and mp_skill_level < 10 then table.insert(skills, mp_skill) elseif you.god() == "Cheibriados" and you.piety_rank() >= 5 and mp_skill_level < 8 then table.insert(skills, mp_skill) elseif you.god() == "Yredelemnul" and you.piety_rank() >= 4 and (mp_skill_level < 8 or you.race() == "Deep Dwarf" and bmp < 10) then table.insert(skills, mp_skill) elseif you.race() == "Deep Dwarf" and you.god() ~= "No God" and (bmp < 5 or mp_skill_level < 2 or mp_skill ~= "Spellcasting" and mp_skill_level < 4) then table.insert(skills, mp_skill) elseif you.race() == "Vine Stalker" and you.god() ~= "No God" and mp_skill_level < 12 and (at_min_delay() or you.base_skill(wskill()) >= 3*mp_skill_level) then table.insert(skills, mp_skill) end skills2 = {} safe_count = 0 for _,sk in ipairs(skills) do if you.can_train_skill(sk) and you.base_skill(sk) < 27 then table.insert(skills2, sk) if you.base_skill(sk) < 26.5 then safe_count = safe_count + 1 end end end --if you.god() == "Xom" and safe_count == 1 and util.contains(skills2, weapon_skill) and you.base_skill(weapon_skill) < 26.5 and you.base_skill("Fighting") < 26.5 then -- Just in case Xom unwields our weapon in early game before we abandon, -- though currently we abandon Xom on T0. --table.insert(skills2, "Fighting") --end -- Try to avoid getting stuck in the skill screen. if safe_count == 0 then if you.base_skill("Fighting") < 26.5 then table.insert(skills2, "Fighting") elseif you.base_skill(mp_skill) < 26.5 then table.insert(skills2, mp_skill) else for _,sk in ipairs(skill_list) do if you.can_train_skill(sk) and you.base_skill(sk) < 26.5 then table.insert(skills2, sk) return skills2 end end end end return skills2 end function handle_skills() skills = choose_skills() choose_single_skill(skills[1]) for _,sk in ipairs(skills) do you.train_skill(sk, 1) end end function choose_stat_gain() local ap = armour_plan() if ap == "heavy" or ap == "large" then return "s" elseif ap == "light" then return "d" else local str,_ = you.strength() local dex,_ = you.dexterity() if 3*str < 2*dex then return "s" else return "d" end end end function auto_experience() return true end ------------------------------------------- -- a few utility functions function contains_string_in(name,t) for _, value in ipairs(t) do if string.find(name, value) then return true end end return false end function split(str, del) local res = { } local v for v in string.gmatch(str, "([^" .. del .. "]+)") do table.insert(res, v) end return res end function control(c) return string.char(string.byte(c) - string.byte('a') + 1) end function delta_to_vi(dx, dy) local d2v = { [-1] = { [-1] = 'y', [0] = 'h', [1] = 'b'}, [0] = { [-1] = 'k', [1] = 'j'}, [1] = { [-1] = 'u', [0] = 'l', [1] = 'n'}, } -- hack return d2v[dx][dy] end function vi_to_delta(c) local d2v = { [-1] = { [-1] = 'y', [0] = 'h', [1] = 'b'}, [0] = { [-1] = 'k', [1] = 'j'}, [1] = { [-1] = 'u', [0] = 'l', [1] = 'n'}, } -- hack local x,y for x = -1,1 do for y = -1,1 do if supdist(x,y) > 0 and d2v[x][y] == c then return x,y end end end end function sign(a) return a > 0 and 1 or a < 0 and -1 or 0 end function abs(a) return a * sign(a) end function vector_move(dx, dy) local str = '' for i = 1, abs(dx) do str = str .. delta_to_vi(sign(dx), 0) end for i = 1, abs(dy) do str = str .. delta_to_vi(0, sign(dy)) end return str end function max(x, y) if x > y then return x else return y end end function min(x, y) if x < y then return x else return y end end function supdist(dx, dy) return max(abs(dx), abs(dy)) end function adjacent(dx, dy) return abs(dx) <= 1 and abs(dy) <= 1 end --------------------------------------------- -- initialization/control/saving function make_endgame_plans() endgame_plan_list = split(ENDGAME_PLAN, ", ") for _,pl in ipairs(endgame_plan_list) do if pl == "slime" then SLIMY_RUNE = true elseif pl == "pan" then PAN_RUNE = true elseif pl == "abyss" then ABYSSAL_RUNE = true elseif pl == "hells" then HELL_RUNE = true elseif pl == "tomb" then GOLDEN_RUNE = true elseif pl == "tso" then TSO_CONVERSION = true end end if endgame_plan_list[#endgame_plan_list] ~= "zot" then table.insert(endgame_plan_list, "zot") end if you.race() == "Deep Dwarf" and you.god() == "Lugonu" then LUGONU_CONVERSION = true end end function initialize() make_endgame_plans() if not did_first_turn and you.turns() == 0 then did_first_turn = true first_turn() end where = "nowhere" expect_new_location = true if c_persist.branches_entered == nil then c_persist.branches_entered = { "D" } end if c_persist.portals_found == nil then c_persist.portals_found = { } end if c_persist.plan_fail_count == nil then c_persist.plan_fail_count = { } end set_options() initialize_monster_array() if not level_map then level_map = {} stair_dists = {} clear_level_map(1) clear_level_map(2) waypoint_parity = 1 prev_where = "nowhere" end for _,god in ipairs(god_options()) do if god == "the Shining One" or god == "Elyvilon" or god == "Zin" then MIGHT_BE_GOOD = true end end initialized = true end function stop() automatic = false unset_options() end function start() automatic = true set_options() ready() end function panic(msg) crawl.mpr("" .. msg .. "") stop() end function startstop() if automatic then stop() else start() end end function hit_closest() startstop() end function set_counter() crawl.formatted_mpr("Set counter to what? ", "prompt") local res = crawl.c_input_line() c_persist.record.counter = tonumber(res) note("counter set to " .. c_persist.record.counter) end function first_turn_persist() if not c_persist.record then c_persist.record = {} end if not c_persist.record.counter then c_persist.record.counter = 1 else c_persist.record.counter = c_persist.record.counter + 1 end note("counter = " .. c_persist.record.counter) --if not c_persist.mlist then -- c_persist.mlist = {} --end --if not c_persist.record.mlist then -- c_persist.record.mlist = {} --end --for _,mname in ipairs(c_persist.mlist) do -- if not c_persist.record.mlist[mname] then -- c_persist.record.mlist[mname] = 1 -- else -- c_persist.record.mlist[mname] = c_persist.record.mlist[mname] + 1 -- end --end local god_list = c_persist.next_god_list for key,_ in pairs(c_persist) do if key ~= "record" then c_persist[key] = nil end end c_persist.cur_god_list = god_list if COMBO_CYCLE then local combo_string_list = split(COMBO_CYCLE_LIST, ", ") local combo_string = combo_string_list[1 + (c_persist.record.counter % (#combo_string_list))] local combo_parts = split(combo_string, "^") c_persist.options = "combo = " .. combo_parts[1] if #combo_parts > 1 then c_persist.next_god_list = { } for g in combo_parts[2]:gmatch(".") do table.insert(c_persist.next_god_list, fullgodname(g)) end end end end function fullgodname(g) if g == "B" then return "Beogh" elseif g == "C" then return "Cheibriados" elseif g == "E" then return "Elyvilon" elseif g == "H" then return "Hepliaklqana" elseif g == "L" then return "Lugonu" elseif g == "M" then return "Makhleb" elseif g == "O" then return "Okawaru" elseif g == "Q" then return "Qazlal" elseif g == "R" then return "Ru" elseif g == "T" then return "Trog" elseif g == "U" then return "Uskayaw" elseif g == "X" then return "Xom" elseif g == "Y" then return "Yredelemnul" elseif g == "Z" then return "Zin" elseif g == "1" then return "the Shining One" else return "???" end end function first_turn() first_turn_persist() if AUTO_START then automatic = true set_options() end end function run_update() if update_coroutine == nil then update_coroutine = coroutine.create(update_stuff) end local okay, err = coroutine.resume(update_coroutine) if not okay then error("Error in coroutine: " .. err) end if coroutine.status(update_coroutine) == "dead" then update_coroutine = nil do_dummy_action = false else do_dummy_action = true end end -- We want to call this exactly once each turn. function update_stuff() if not initialized then initialize() end if you.turns() == old_turn_count then time_passed = false return end time_passed = true old_turn_count = you.turns() if not did_first_turn and you.turns() == 0 then did_first_turn = true first_turn() end if you.turns() >= dump_count then dump_count = dump_count+100 crawl.dump_char() end if you.turns() >= skill_count then skill_count = skill_count+5 handle_skills() end if did_move then move_count = move_count + 1 else move_count = 0 end did_move = false dd_hw_meter = math.floor(6*dd_hw_meter/7) if did_move_towards_monster > 0 then did_move_towards_monster = did_move_towards_monster - 1 end if you.where() ~= where then if (where == "nowhere" or is_waypointable(where)) and is_waypointable(you.where()) then waypoint_parity = 3 - waypoint_parity if you.where() ~= prev_where or you.where():find("Tomb") then clear_level_map(waypoint_parity) set_waypoint() coroutine.yield() end cur_where = you.where() prev_where = where elseif is_waypointable(you.where()) and you.where() ~= cur_where then clear_level_map(waypoint_parity) set_waypoint() coroutine.yield() cur_where = you.where() end clear_ignores() target_stair = nil if expect_new_location then if where_shafted_from == you.where() then say("Successfully unshafted to " .. you.where() .. ".") where_shafted_from = nil end elseif automatic and not you.where():find("Abyss") then say("Shafted from " .. where .. " to " .. you.where() .. ".") if not where_shafted_from then where_shafted_from = where end end where = you.where() if cur_branch() and not util.contains(c_persist.branches_entered, cur_branch()) then say("Entered " .. cur_branch() .. ".") table.insert(c_persist.branches_entered, cur_branch()) end if expect_portal and in_portal() then say("Entered " .. where .. ".") end c_persist.portals_found = { } if where == "Vaults:5" and not v5_entry_turn then v5_entry_turn = you.turns() end end if is_waypointable(where) then update_level_map(waypoint_parity) end update_game_status() expect_new_location = false expect_portal = false check_messages() update_monster_array() danger = sense_danger(LOS) immediate_danger = sense_immediate_danger() find_good_stairs() cloudy = (not view.is_safe_square(0,0) and view.cloud_at(0,0) ~= nil) sense_sigmund() choose_tactical_step() if collectgarbage("count") > 7000 then collectgarbage() end end function ready() offlevel_travel = true run_update() if do_dummy_action then if not did_waypoint then crawl.process_keys(":" .. string.char(27) .. string.char(27)) else did_waypoint = false end return end if time_passed and SINGLE_STEP then stop() end if automatic then crawl.flush_input() crawl.more_autoclear(true) if have_message then plan_message() elseif you.where():find("Abyss") then plan_abyss_move() elseif you.have_orb() then plan_orbrun_move() else plan_move() end end end function magic(command) crawl.process_keys(command .. string.char(27) .. string.char(27) .. string.char(27)) end -------------------------------- -- a function to test various things conveniently function ttt() for i = -7,7 do for j = -7,7 do m = monster.get_monster_at(i,j) if m then crawl.mpr("(" .. i .. "," .. j .. "): name = " .. m:name() .. ", desc = " .. m:desc() .. ".") end end end --for it in inventory() do -- crawl.mpr("name = " .. it.name() .. ", ego = " .. (it.ego() or "none") .. ", subtype = " .. (it.subtype() or "none") .. ", slot = " .. slot(it) .. ".") --end for it in at_feet() do local val1,val2 = equip_value(it) local val3,val4 = equip_value(it, true) crawl.mpr("name = " .. it.name() .. ", ego = " .. (it.ego() or "none") .. it.ego_type .. ", subtype = " .. (it.subtype() or "none") .. ", slot = " .. (slot(it) or -1) .. ", values = " .. val1 .. " " .. val2 .. " " .. val3 .. " " .. val4 .. ".") end end function print_level_map() local num = waypoint_parity local dx,dy = travel.waypoint_delta(num) local x,y local str for y = -20,20 do str = "" for x = -20,20 do if level_map[num][dx+x][dy+y] == nil then str = str .. " " else str = str .. level_map[num][dx+x][dy+y] end end say(str) end end function print_stair_dists() local num = waypoint_parity local dx,dy = travel.waypoint_delta(num) local x,y local i local str for i = 1, #stair_dists[num] do say("---------------------------------------") for y = -20,20 do str = "" for x = -20,20 do if stair_dists[num][i][dx+x][dy+y] == nil then str = str .. " " else str = str .. string.char(string.byte('A') + stair_dists[num][i][dx+x][dy+y]) end end say(str) end end end function c_trap_is_safe(trap) return (trap ~= "permanent teleport") end function c_answer_prompt(prompt) if prompt == "Die?" then return false end if prompt:find("blurry vision") then return true end if prompt:find("Have to go through") then return offlevel_travel end if prompt:find("transient mutations") then return true end if prompt:find("Keep disrobing") then return false end if prompt:find("Really unwield") or prompt:find("Really take off") or prompt:find("Really remove") or prompt:find("Really wield") or prompt:find("Really wear") or prompt:find("Really put on") or prompt:find("Really drink") then return true end if prompt:find("Keep reading") then return true end if prompt:find("Keep eating") then return true end if prompt:find("This attack would place you under penance") then return false end if prompt:find("You cannot afford") and prompt:find("travel there anyways") then return true end if prompt:find("Shopping list") then return false end end function ch_stash_search_annotate_item(it) return "" end }