# 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 = 100 # 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 = true # experimental: do second lair rune branch before depths : EARLY_SECOND_RUNE = true # lair rune preferences, current options are: # * random - no preference, chooses randomly # * nowater - does Snake/Spider first # * smart - currently prefers Spider > Snake/Swamp > Shoals : RUNE_PREFERENCE = "smart" # experimental: get abyssal rune : ABYSSAL_RUNE = false : ABYSS_RUNE_DEPTH = "Abyss:3" # experimental: get slimy rune : SLIMY_RUNE = false # depth to attempt to dive in ziggurats (0 to turn off) # if turned on, this causes annoying throttling delays when played online # for some reason : ZIG_DIVE = 0 # burn books with Trog, may theoretically cause navigation problems : BURN_BOOKS = true # tab just takes a single action (for testing) : SINGLE_STEP = false # panic (stop) at full inventory : FULL_INVENTORY_PANIC = true # when choosing a god on DD, only choose Makhleb : DD_MAKHLEB_ONLY = false # quit after this number of turns while stuck : QUIT_TURNS = 1000 # qw can play any starting combo. These ones are recommended: combo = DDBe.handaxe, GrBe.handaxe, MiBe.handaxe combo = NaBe.handaxe # DDGl is the recommended non-berserker start. It will worship whichever of # Makhleb and Trog it finds first. #combo = DDGl.waraxe # For a mix of the above: #combo = DDBe.handaxe, DDGl.waraxe, GrBe.handaxe, GrBe.handaxe #combo += MiBe.handaxe, MiBe.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: DDBe GrBe MiBe # offline wins: DsBe HOBe HuBe MfBe NaBe TeBe VSBe # (offline) runes: CeBe DrBe FoBe KoBe TrBe VpBe # non-Be offline wins: DDAr DDAs DDCK DDFi DDGl DDMo DDNe DDSk DDSu DDTm DDWn DDWr ##################################### # enum values :/ : ENUM_MONS_PANDEMONIUM_LORD = 344 : ATT_NEUTRAL = 1 : ATT_HOSTILE = 0 : LOS = 7 ##################################### # miscellaneous simple options name = qw restart_after_game = false view_delay = 0 use_animations = clear_messages = true travel_delay = -1 explore_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,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 #################################### # 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 = [.] CMD_NO_CMD_DEFAULT #################################### # 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 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:Unknown command") 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:Unknown command") 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 function equip_value(it, cur, it2) 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) elseif class == "jewellery" then if it.name():find("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 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 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 subtype == "amulet of warding" 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 == "amulet of resist corrosion" 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 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 return you.mutation("negative energy resistance") 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 item_resist(str, it) or 0 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 if 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 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 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 ((you.num_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 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 ((you.num_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 -300 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 total_resist_value(it, cur, it2) artproplist = { "rF", "rC", "rElec", "rPois", "rN", "MR", "rCorr", "SInv", "Spirit" } local val1, val2 = 0,0,0 for _,str in ipairs(artproplist) do local a,b = resist_value(str, it, cur, it2) val1 = val1 + a val2 = val2 + b end return val1,val2 end function rune_goal() return 3 + (ABYSSAL_RUNE and 1 or 0) + (SLIMY_RUNE and 1 or 0) end function zot_soon() return you.where():find("Zot") or (you.where():find("Depths") and you.num_runes() >= rune_goal() and not you.have_orb()) end function slime_soon() return you.where():find("Slime") or (SLIMY_RUNE and you.where():find("Lair") and you.have_rune("silver") and not you.have_rune("slimy")) end function abyss_soon() return you.where():find("Abyss") or (ABYSSAL_RUNE and (you.where():find("Depths") or you.where():find("D:") and (you.have_rune("slimy") or not SLIMY_RUNE)) and you.have_rune("silver") and not you.have_rune("abyssal")) 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 you.race() == "Deep Dwarf" and ap and ap["MP"] and ap["MP"] < 0 then return -1,-1 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 elseif ego == "flying" and not intrinsic_flight() then if not intrinsic_flight() then value = value + 200 end elseif ego == "ponderousness" then return -1,-1 end end value = value + 100*it.ac if it.plus then value = value + 50*it.plus end st, _ = it.subtype() if good_slots[st] == "Shield" then if it.encumbrance == 1 then if not want_buckler() then return -1,-1 end elseif not want_shield() 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 evp = it.encumbrance if intrinsic_dodgy() then if evp > 7 then return -1,-1 elseif evp >= 6 then value = value - 250 elseif evp >= 3 then value = value - 100 end else if evp >= 20 then value = value - 200 end if name:find("pearl dragon") then value = value + 150 end end if name:find("hide") and not name:find("salamander hide") then value = value + 100*it.ac - 50 end end return value+val1,value+val2 end function weapon_value(it, cur, it2) 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 return -1,-1 end end if it.hands == 2 and want_buckler() 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 you.race() == "Deep Dwarf" and ap and ap["MP"] and ap["MP"] < 0 then return -1,-1 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 ego then -- names are mostly in weapon_brands_verbose[] if ego == "distortion" then return -1,-1 elseif ego == "holy wrath" then if intrinsic_evil() then return -1,-1 end elseif ego == "vampirism" then value = value + 500 -- this is what we want elseif ego == "speed" then value = value + 300 -- this is good too elseif ego == "electrocution" then value = value + 150 -- not bad elseif ego == "flaming" or ego == "freezing" or ego == "draining" or ego == "crushing" or ego == "slicing" or ego == "piercing" or ego == "chopping" or ego == "slashing" then value = value + 75 elseif ego == "antimagic" then if you.race() == "Vine Stalker" then value = value - 300 elseif you.race() ~= "Deep Dwarf" then value = value + 75 end end end if it.plus then value = value + 30*it.plus end value = value + 150*it.damage if it.delay > 17 then value = value - 150*(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 you.race() == "Deep Dwarf" and ap and ap["MP"] and ap["MP"] < 0 then return -1,-1 end if name:find("macabre finger necklace") then return -1,-1 end end if subtype == "amulet of faith" then return 1000,1000 -- fixed value so we don't unequip for a randart one end if subtype == "amulet of stasis" then return -1,-1 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,(SLIMY_RUNE and 1200 or 1000) end end local val1,val2 = total_resist_value(it, cur, it2) if subtype == "amulet of resist mutation" or subtype == "amulet of clarity" or subtype == "amulet of regeneration" and you.race() ~= "Deep Dwarf" then value = value + 50 if subtype == "amulet of resist mutation" and ABYSSAL_RUNE then if cur then if abyss_soon() then value = value + 450 end else val2 = val2 + 450 end end elseif subtype == "amulet of inaccuracy" then value = value - 250 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 you.race() == "Deep Dwarf" and ap and ap["MP"] and ap["MP"] < 0 then return -1,-1 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 if it.plus and it.plus < 0 then return -1,-1 end local val1,val2 = total_resist_value(it, cur, it2) local plus = it.plus or 0 if not it.fully_identified then plus = 6 end if subtype == "ring of slaying" or subtype == "ring of protection" or subtype == "ring of evasion" then value = value + 50*plus if not it.fully_identified and not cur then val1 = val1 - 50*12 end elseif subtype == "ring of strength" or subtype == "ring of dexterity" then value = value + 20*plus if not it.fully_identified and not cur then val1 = val1 - 20*12 end --elseif subtype == "ring of sustain abilities" -- or subtype == "ring of stealth" -- or subtype == "ring of magical power" then -- value = value + 10 elseif subtype == "ring of loudness" then value = value - 10 end return value+val1,value+val2 end function want_wand(it) sub = it.subtype() if sub == nil then return true end if sub ~= "teleportation" and sub ~= "heal wounds" and sub ~= "hasting" and sub ~= "digging" and sub ~= "disintegration" 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" or sub == "disintegration" then for it2 in inventory() do if it2.class(true) == "wand" and slot(it2) ~= slot(it) then sub2 = it2.subtype() if sub2 == "digging" or sub2 == "disintegration" then if not it2.name():find("empty") and not it2.name():find("zapped") and (it2.plus == nil or it2.plus > 0) then return false end end end end 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 ~= "beneficial mutation" and sub ~= "cure 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 ~= "holy word" then return false end return true end function item_is_dominated(it) local minv,maxv = equip_value(it) if maxv <= 0 then return true end local slotname = equip_slot(it) 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 then num_slots = num_slots - 1 if num_slots == 0 then return true end end if minv == minv2 and maxv == maxv2 then if slotname == "Ring" then if not it.artefact and not it2.artefact then sub = it.subtype() sub2 = it2.subtype() if sub == sub2 then if sub == "ring of fire" or sub == "ring of ice" or sub == "ring of poison resistance" or sub == "ring of see invisible" or sub == "ring of sustain abilities" then return true elseif sub == "ring of protection from fire" or sub == "ring of protection from cold" then num_slots = num_slots - 1 if num_slots == 0 then return true end end end end elseif slotname == "Amulet" then if not it.artefact and not it2.artefact and it.subtype() == it2.subtype() then return true end elseif it.name() == it2.name() then local name = it.name() if not (name:find("runed") or name:find("glowing") or name:find("dyed") or name:find("embroidered") or name:find("shiny")) then return true end 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) if not old_it then return should_equip(it) end if not it.fully_identified and not should_drop(it) then return (old_it.subtype() ~= "amulet of faith") end return (equip_value(it,true,old_it) > equip_value(old_it,true,old_it)) end -- assumes it is not equipped and an empty slot is available function should_equip(it) return (equip_value(it,true) > 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() 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 global variables: local dump_count = you.turns() + 100 - (you.turns() % 100) local skill_count = you.turns() - (you.turns() % 20) local danger local immediate_danger local where = you.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 kill_plant_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() local travel_destination = nil 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 v5_entry_turn local slime_forgot_map = false ------------------------------ -- some tables with hardcoded data about branches/portals/monsters: -- branch data: code, full name, where name local branch_data = { {"T", "the Ecumenical Temple", "Temple"}, {"O", "the Orcish Mines", "Orc"}, --{"E", "the Elven Halls", "Elf"}, {"L", "the Lair", "Lair"}, {"S", "the Swamp", "Swamp"}, {"A", "the Shoals", "Shoals"}, {"P", "the Snake Pit", "Snake"}, {"N", "the Spider Nest", "Spider"}, {"M", "the Slime Pits", "Slime"}, {"V", "the Vaults", "Vaults"}, --{"C", "the Crypt", "Crypt"}, --{"W", "the Tomb", "Tomb"}, {"D", "the Dungeon", "D:"}, {"U", "the Depths", "Depths"}, {"Z", "Zot", "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"}, --{"Zig", "gateway to a ziggurat", "ziggurat"}, --{"Lab", "labyrinth entrance", "labyrinth"}, } -- hack -- monster lists: XL cutoff (>= to ignore), monster name -- if name is "desc" then look for the pattern given in the third field -- inside the monster desc instead -- berserk these local scary_monsters = { {3, "Terence"}, {5, "gnoll"}, {5, "Ijyb"}, {5, "ice beast"}, {5, "iguana"}, {5, "Natasha"}, {5, "Robin"}, {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, "two-headed ogre"}, {10, "decayed bog body"}, {12, "orc priest"}, {12, "orc warrior"}, {12, "troll"}, {12, "cyclops"}, {12, "hill giant"}, {12, "spiny frog"}, {12, "black mamba"}, {12, "Snorg"}, {12, "fire drake"}, {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, "Maud"}, {15, "Erica"}, {15, "catoblepas"}, {15, "orc knight"}, {15, "boulder beetle"}, {15, "swamp worm"}, {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, "white ugly thing"}, {17, "white very ugly thing"}, {20, "greater naga"}, {20, "naga sharpshooter"}, {20, "emperor scorpion"}, {20, "Wiglaf"}, {20, "Rupert"}, {20, "Aizul"}, {20, "desc", "hydra"}, {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"}, {100, "deep elf annihilator"}, {100, "deep elf sorcerer"}, {100, "the Enchantress"}, {100, "Vashnia"}, {100, "Sojobo"}, {100, "desc", "berserk[^e]"}, {100, "Roxanne"}, {100, "Norris"}, {100, "Erolcha"}, {100, "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, "desc", "'s ghost"}, {100, "desc", "' ghost"}, {100, "desc", "'s illusion"}, {100, "desc", "' illusion"}, {100, "oklob plant"}, {100, "hellion"}, {100, "tormentor"}, {100, "Hell Sentinel"}, {100, "Ice Fiend"}, {100, "Shadow Fiend"}, {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"}, } -- hack -- these scary monsters are slow, so wait to berserk until they are adjacent local scary_slow_monsters = { {4, "worm"}, } -- hack -- BiA these even at low piety local bia_necessary_monsters = { {15, "desc", "hydra"}, {20, "orb spider"}, {100, "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, "desc", "hydra"}, {20, "orc warlord"}, {20, "Aizul"}, {20, "Frances"}, {20, "Saint Roka"}, {20, "Agnes"}, {20, "Jory"}, {20, "Norris"}, {20, "Arachne"}, {20, "Nikola"}, {20, "Vashnia"}, {20, "Asterion"}, {20, "orb spider"}, {20, "thorn hunter"}, {20, "sun demon"}, {20, "Polyphemus"}, {20, "Ilsuiw"}, {100, "deep troll shaman"}, {100, "spriggan air mage"}, {100, "the Enchantress"}, {100, "Sojobo"}, {100, "Roxanne"}, {100, "Erolcha"}, {100, "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, "desc", "'s ghost"}, {100, "desc", "' ghost"}, {100, "oklob plant"}, {100, "Hell Sentinel"}, {100, "Ice Fiend"}, {100, "Brimstone Fiend"}, {100, "Shadow Fiend"}, {100, "Tiamat"}, {100, "orb of fire"}, {100, "caustic shrike"}, {100, "seraph"}, {100, "death cob"}, {100, "royal jelly"}, {100, "spark wasp"}, } -- hack -- Trog's Hand these local hand_monsters = { {10, "Grinder"}, {17, "orc sorcerer"}, {17, "wizard"}, {100, "ogre mage"}, {100, "Rupert"}, {100, "Aizul"}, {100, "Norris"}, {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"}, } -- hack -- potion of resistance these local fire_resistance_monsters = { {100, "Margery"}, {100, "orb of fire"}, {100, "hellephant"}, {100, "Xtahua"}, } -- hack local cold_resistance_monsters = { {100, "Ice Fiend"}, } -- hack local elec_resistance_monsters = { {20, "storm dragon"}, {20, "desc", "black draconian"}, {100, "electric golem"}, {100, "spark wasp"}, } -- 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 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" 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" 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 -- whether we want to use really heavy armour function intrinsic_dodgy() local sp = you.race() return (sp == "Deep Elf" or sp:find("Draconian") or sp == "Felid" or sp == "Halfling" or sp == "High Elf" or sp == "Kobold" or sp == "Merfolk" or sp == "Ogre" or sp == "Octopode" or sp == "Spriggan" or sp == "Troll") end function intrinsic_heavy() return (not intrinsic_dodgy()) end function want_buckler() if you.race() == "Felid" then return false 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 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" or sp == "High Elf" 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.weap_skill ~= "Short Blades" 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 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 in_portal() for _, value in ipairs(portal_data) do if value[1] == where then return true end end return false 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[3]) 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[3]) > 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[3]) 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" then return false end return true end function can_zap() if you.berserk() or you.confused() or transformed() then return false end local x = you.mutation("MP-powered wands") if x > 0 then local a,b = you.mp() if a < 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.exhausted() 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_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 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() local hp,mhp = you.hp() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.piety_rank() < 5 or hp <= 10 or you.god() ~= "Makhleb")) end function can_destruction() local hp,mhp = you.hp() return (not (you.berserk() or you.confused() or you.silenced() or starving() or you.piety_rank() < 4 or hp <= 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 = 5 elseif sdesc == "very fast" then num = 4 elseif sdesc == "fast" or 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 return num end function is_fast(m) return (mon_speed_num(m) > player_speed_num()) end function is_ranged(m) if m:has_known_ranged_attack() then return true end local name = m:name() if name == "Maurice" or name == "crimson imp" or name == "boulder beetle" 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] and (value[2] == name or value[2] == "desc" and desc:find(value[3])) then return true 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_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_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_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 kill_plant_mode or m:attitude() > ATT_NEUTRAL and (m:is_constricted() or m:is_caught() or m:status("petrified") or m:status("paralysed") 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 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) 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 tabbable_square(cx+fx, cy+fy) then return will_tab(cx+fx, cy+fy, ex, ey) 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) 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 (e.m:reach_range() >= 2 or is_fast(e.m)) or is_ranged(e.m) then i = i+1 end end end return i 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 ----------------------------------------- -- 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 it.class(true) == "jewellery" and it.name():find("ring") then table.insert(rings, it) end end return rings end function empty_ring_slots() return max_rings() - table.getn(ring_list()) 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 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 target_shield_skill() local shield = items.equipped_at("Shield") if not shield then return 0 end local size = 5 if you.race() == "Kobold" or you.race() == "Halfling" then size = 7 elseif you.race() == "Spriggan" then size = 9 elseif you.race() == "Troll" or you.race() == "Ogre" or you.race() == "Formicid" or you.race() == "Naga" or you.race() == "Centaur" then size = 3 end return (shield.encumbrance * size) end function min_delay_skill() weap = items.equipped_at("Weapon") if not weap then return 26 end if weap.weap_skill == "Short Blades" and weap.delay == 12 then return 14 end local mindelay = math.floor(weap.delay / 2) if mindelay > 7 then mindelay = 7 end return 2 * (weap.delay - mindelay) 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 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 string.find(it.name(), "corpse") and not string.find(it.name(), "noeat") 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 return (hp_is_low(90) and (you.race() ~= "Deep Dwarf" and you.hunger_name() ~= "bloodless" or you.regenerating()) or you.slowed() or you.exhausted() or you.teleporting() or you.status("manticore barbs") or you.silencing() or you.corrosion() > 0 or you.god() == "Makhleb" and you.turns() <= sgd_timer + 100) 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 pray() magic("p") end function chop() magic("ccq") end function berserk() use_ability("Berserk") end function hand() use_ability("Trog's Hand") end function bia() use_ability("Brothers in Arms") end function sgd() use_ability("Greater Servant of Makhleb") 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_hydra_destruction() if not can_destruction() then return false end if count_sgd(4) > 0 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 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_dd_recharge_teleport() if you.race() ~= "Deep Dwarf" then return false end if you.berserk() or you.confused() or starving() then return false end if count_item("scroll","teleportation") > 0 then return false end if immediate_danger and not want_to_teleport() then return false end local wand_letter for it in inventory() do if it.class(true) == "wand" and it.name():find("teleportation") then if (((not it.fully_identified) and not it.name():find("empty")) or it.plus and it.plus > 0) then return false else wand_letter = letter(it) end end end if not wand_letter then return false end for it in inventory() do if it.class(true) == "scroll" and can_read() and it.name():find("recharging") then oldname = item(wand_letter).name() if read2(it, wand_letter) then say("RECHARGING " .. oldname .. ".") return true end end end local mp,mmp = you.mp() if mp > 0 and you.base_mp() > 0 then for letter, abil in pairs(you.ability_table()) do if abil == "Device Recharging" then say("MP-RECHARGING " .. item(wand_letter).name() .. ".") -- swap slots in case ability fails items.swap_slots(slot(wand_letter), slot('Y')) magic("a" .. letter .. "Y") return true end end end return false end function plan_dd_orbrun_recharge_hasting() if you.race() ~= "Deep Dwarf" then return false end if danger then return false end if you.berserk() or you.confused() or starving() then return false end if count_item("potion","haste") > 0 then return false end local wand_letter for it in inventory() do if it.class(true) == "wand" and it.name():find("hasting") then if (((not it.fully_identified) and not it.name():find("empty")) or it.plus and it.plus > 0) then return false else wand_letter = letter(it) end end end if not wand_letter then return false end for it in inventory() do if it.class(true) == "scroll" and can_read() and it.name():find("recharging") then oldname = item(wand_letter).name() if read2(it, wand_letter) then say("RECHARGING " .. oldname .. ".") return true end end end local mp,mmp = you.mp() if mp > 0 and you.base_mp() > 0 then for letter, abil in pairs(you.ability_table()) do if abil == "Device Recharging" then say("MP-RECHARGING " .. item(wand_letter).name() .. ".") -- swap slots in case ability fails items.swap_slots(slot(wand_letter), slot('Y')) magic("a" .. letter .. "Y") return true end end end return false end function plan_dd_recharge_heal_wounds() if you.race() ~= "Deep Dwarf" then return false end if you.berserk() or you.confused() or starving() then return false end local charge_bound = immediate_danger and 1 or 3 local wand_letter local wand_count = 0 for it in inventory() do if it.class(true) == "wand" and it.name():find("heal wounds") then if (((not it.fully_identified) and not it.name():find("empty")) or it.plus and it.plus > charge_bound) then wand_count = wand_count + 1 else wand_letter = letter(it) end end end if immediate_danger and wand_count > 0 or not wand_letter then return false end for it in inventory() do if it.class(true) == "scroll" and can_read() and it.name():find("recharging") then oldname = item(wand_letter).name() if read2(it, wand_letter) then say("RECHARGING " .. oldname .. ".") return true end end end local mp,mmp = you.mp() if mp > 0 and you.base_mp() > 0 then for letter, abil in pairs(you.ability_table()) do if abil == "Device Recharging" then say("MP-RECHARGING " .. item(wand_letter).name() .. ".") -- swap slots in case ability fails items.swap_slots(slot(wand_letter), slot('Y')) magic("a" .. letter .. "Y") return true end end end return false end function plan_cure_bad_poison() if not (danger or you.race() == "Deep Dwarf") then return false end local hp, mhp = you.hp() if you.poison_survival() <= hp - 60 then if drink_by_name("curing") then say("(to cure bad poison)") return true end end return false end function plan_heal_wounds() if want_to_heal_wounds() then return heal_wounds() end return false end function plan_orbrun_heal_wounds() if want_to_orbrun_heal_wounds() then return heal_wounds() end return false end function plan_orbrun_haste() if want_to_orbrun_haste() then return haste() end return false end function plan_orbrun_might() if want_to_orbrun_might() then return might() end return false end function heal_wounds() if you.mutation("no device heal") >= 3 then return false end if you.race() == "Deep Dwarf" then if drink_by_name("heal wounds") then dd_hw_meter = dd_hw_meter + 100 return true end end if selfzap_by_name("heal wounds") then dd_hw_meter = dd_hw_meter + 100 return true end if you.race() ~= "Deep Dwarf" then if drink_by_name("heal wounds") then dd_hw_meter = dd_hw_meter + 100 return true end end return false end function haste() if you.hasted() or you.race() == "Formicid" then return false end if selfzap_by_name("hasting") then return true 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 tu current square a.supdist = supdist(x,y) -- 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) -- adjacent monsters a.adjacent = count_nearby(x,y,1) -- corners - avoid these if not cleaving 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()) -- 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") -- 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) 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 -- outnumbered - stepping away from a square adjacent to multiple monsters -- (when not cleaving) function step_reason(a1,a2) if not (a2.can_move and a2.safe and a2.supdist > 0) then return false elseif not a1.cloud_safe then return "cloud" elseif a2.fumble or a2.slow then return false 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 reason function step_improvement(reason,a1,a2) if reason == "water" and a2.enemy_distance < a1.enemy_distance then return true elseif reason == "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.enemy_distance < a1.enemy_distance then return true elseif a2.enemy_distance > a1.enemy_distance then return false elseif a1.cornerish and not a2.cornerish then return true else return false end end -- this function always returns false because it doesn't actually take the -- tactical step, just chooses it function plan_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" then return false 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()) then return false 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(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 --say("Stepping ~*~*~tactically~*~*~.") --magic(delta_to_vi(bestx,besty) .. "Y") --say("tactics: " .. delta_to_vi(bestx,besty)) --stop() --return true end return false 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_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_burn_spellbooks() if want_to_burn_spellbooks() then burn_spellbooks() return true end return false end function want_to_bia() 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_sgd() if you.skill("Invocations") >= 8 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 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 count_hostile_sgd(LOS) > 0 then sgd_timer = you.turns() 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 want_to_orbrun_holy_word() return (danger and want_to_orbrun_heal_wounds()) end function dd_want_to_heal_wounds() if (not danger) and (you.regenerating() or dd_prefer_hand()) 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() and (not you.teleporting()) and not hp_is_low(66) then return false end if you.god() == "Makhleb" and you.piety_rank() >= 1 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 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()) else return (hp_is_low(50)) end end function want_to_orbrun_haste() return ((count_pan_lords(LOS) > 0) or check_monsters(LOS, scary_monsters)) end function want_to_orbrun_might() return want_to_orbrun_haste() 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 check_monsters(1, scary_slow_monsters) or (invisi_sigmund and not options.autopick_on)) end function want_to_stay_in_abyss() if ABYSSAL_RUNE and abyss_soon() and not you.have_rune("abyssal") and count_permafood() <= 10 then say("Giving up on abyssal rune: low on food") ABYSSAL_RUNE = false end return (ABYSSAL_RUNE and you.have_rune("silver") and not you.have_rune("abyssal") and count_permafood() > 10 and not hp_is_low(50)) end function rejecting_slime() local c = find_item("wand", "digging") or find_item("wand", "disintegration") if not c then say("Not doing Slime - no dig source.") SLIMY_RUNE = false return true end return false 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 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(0,0,e.x,e.y) 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")) then count = count + 1 if e.m:desc():find("sleeping") 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 plan_wait_throw() if not is_waiting 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 then if attack() then return true end 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" 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) clear_autopickup_funcs() magic(control('f') .. target .. "\ra\r") add_autopickup_func(autopickup) end function plan_find_altar() if not want_altar() then return false end if you.race() == "Deep Dwarf" then if DD_MAKHLEB_ONLY then magicfind("@altar&&makhleb") else magicfind("@altar&&<>") end else magicfind("@altar&&trog") end return true end function plan_abandon_god() if you.god() ~= "No God" and you.god() ~= "Trog" and you.god() ~= "Makhleb" 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_god() if not want_altar() then return false end if view.feature_at(0, 0) == "altar_trog" and not (DD_MAKHLEB_ONLY and you.race() == "Deep Dwarf") or you.race() == "Deep Dwarf" and view.feature_at(0, 0) == "altar_makhleb" then if you.silenced() then rest() else magic("pYY") end return true end return false end function plan_find_corpses() magicfind("@corpse$&&!!rott&&!!skel&&!!noeat") return true end function plan_autoexplore() if free_inventory_slots() == 0 then return false end magic("o") return true end function plan_drop_other_items() upgrade_phase = false for it in inventory() do if it.class(true) == "missile" and not want_missile(it) or it.class(true) == "wand" and not want_wand(it) or it.class(true) == "potion" and not want_potion(it) or it.class(true) == "scroll" and not want_scroll(it) then say("DROPPING " .. it.name() .. ".") magic("d" .. letter(it) .. "\r") return true end end return false end function plan_quaff_id() for it in inventory() do if it.class(true) == "potion" and it.quantity > 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.name():find("vamp") 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) .. "YF") 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("teleportation") or it.name():find("heal wounds") and you.race() ~= "Vine Stalker" or it.name():find("hasting")) 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) name = arm.name() if intrinsic_dodgy() then return (name:find("dragon")) else return (name:find("gold dragon") or name:find("crystal plate") or name:find("plate armour of fire") or name:find("pearl dragon")) end end function body_armour_is_good(arm) if in_branch("Z") then return true end name = arm.name() if intrinsic_dodgy() then return (name:find("of resistance") or name:find("of fire")) else return name:find("plate") 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 you.race() ~= "Felid" then return read2(it, " b") else return read2(it, " f") end elseif it.name():find("enchant weapon") then weapon = items.equipped_at("weapon") if 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("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 armour") 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" then it2 = items.equipped_at(slotname) if it2 and not it2.artefact and it2.plus < 2 and it2.plus >= 0 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 armour") 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("heal wounds") and you.race() ~= "Vine Stalker" or -- it2.name():find("hasting") or it2.name():find("teleportation")) and (it2.name():find("empty") or it2.plus and it2.plus < 4) 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("beneficial") and you.race() ~= "Ghoul" or it.name():find("experience") then return drink(it) end if it.name():find("cure mutation") then if base_mutation("slow regeneration") > 0 and you.race() ~= "Deep Dwarf" and you.race() ~= "Ghoul" or base_mutation("dopey") > 1 or base_mutation("clumsy") > 1 or base_mutation("teleportitis") > 0 or base_mutation("deformed body") > 0 and you.race() ~= "Naga" and you.race() ~= "Centaur" 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 device heal") > 0 and you.race() ~= "Vine Stalker" or base_mutation("MP-powered wands") > 0 then return drink(it) end end end end return false end function base_mutation(str) return you.mutation(str) - you.temp_mutation(str) end function plan_wield_weapon() if items.equipped_at("Weapon") 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") 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 return false end function plan_upgrade_weapon() if you.race() == "Troll" then return false 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) 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 you.race() ~= "Vampire" 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 it.class(true) == "jewellery" and it.name():find("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 it.class(true) == "jewellery" and it.name():find("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 should_drop(it) then drop = true elseif 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 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() 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 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" and you.piety_rank() < 4 then return false end return true end function want_to_stairdance_up() local feat = view.feature_at(0,0) if not (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")) then return false end local n = stairdance_count[where] or 0 if n > 10 then return false end if you.caught() or you.mesmerised() or you.constricted() or you.berserk() or you.rooted() or you.transform() == "tree" or you.transform() == "fungus" or hp_is_low(33) or count_bia(3) > 0 or count_sgd(3) > 0 then return false end local e for _,e in ipairs(enemy_list) do local dist = supdist(e.x,e.y) if dist == 1 then if e.m:stabbability() == 0 and can_use_stairs(e.m:name()) then stairdance_count[where] = n + 1 return true end end 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" 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 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 for n,e in ipairs(items.shop_inventory()) do it = e[1] price = e[2] if price <= you.gold() and autopickup(it, it.name()) then --say("BUYING " .. it.name() .. " (" .. price .. " gold).") magic("<" .. letter(n-1) .. "\ry") return end end 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 you.num_runes() < 2 then return false end expect_new_location = true magic("G>") return true end function want_altar() return (you.god() ~= "Trog" and you.god() ~= "Makhleb" and you.race() ~= "Demigod") end function plan_go_to_temple() local c = c_persist.plan_fail_count["try_go_to_temple"] if c and c >= 10 then return false end if found_branch("T") and want_altar() 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_go_to_lair() if found_branch("L") and not util.contains(c_persist.branches_entered, "T") and in_branch("D") then expect_new_location = true magic("GL\rY") 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 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 ZIG_DIVE <= 0 then return false end if you.num_runes() < rune_goal() then return false end magicfind("gateway to a ziggurat") return true 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_abyss_downstairs() if where:find("Abyss") and where ~= ABYSS_RUNE_DEPTH and want_to_stay_in_abyss() then magic("X>\r") return true 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_dive() if in_branch("M") and where ~= "Slime:6" and not travel_destination then expect_new_location = true magic("G>") return true end return false end function plan_enter_zig() if ZIG_DIVE <= 0 then return false end if you.num_runes() < rune_goal() then return false end if view.feature_at(0,0) == "enter_ziggurat" then expect_new_location = true magic(">") 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() 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_go_down_abyss() if view.feature_at(0,0) == "abyssal_stair" and where ~= ABYSS_RUNE_DEPTH and want_to_stay_in_abyss() then expect_new_location = true magic(">") return true 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("") return true end return false end function plan_exit_abyss() if view.feature_at(0,0) == "exit_abyss" and not want_to_stay_in_abyss() then expect_new_location = true magic("<") return true end return false end function plan_step_towards_lair() local x, y if stepped_on_lair or not found_branch("L") then return false end for x = -LOS,LOS do for y = -LOS,LOS do if view.feature_at(x,y) == "enter_lair" and you.see_cell_no_trans(x,y) then if x == 0 and y == 0 then stepped_on_lair = true return false else kill_plant_mode = true local result = move_towards(x,y) kill_plant_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 == "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 plan_new_travel() local back_to_D_places = { "Temple", "Orc:4", "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:8" then if travel.find_deepest_explored("D") == 15 and you.num_runes() < 2 then travel_destination = choose_lair_rune_branch() elseif SLIMY_RUNE and you.have_rune("silver") and not you.have_rune("slimy") and not rejecting_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 travel_destination = "D" end if where == "Slime:6" and you.have_rune("slimy") then travel_destination = "D" end if where == "D:15" then if you.num_runes() == 1 and not util.contains(c_persist.branches_entered, "V") or you.num_runes() == 2 and util.contains(c_persist.branches_entered, "U") then travel_destination = "V" elseif you.num_runes() >= 1 and not util.contains(c_persist.branches_entered, "U") and not (EARLY_SECOND_RUNE and you.num_runes() == 1) then travel_destination = "U" elseif you.have_rune("silver") then if SLIMY_RUNE and not you.have_rune("slimy") then travel_destination = "L" else travel_destination = "U" end else travel_destination = "L" end end if where == "Depths:5" then if you.num_runes() >= rune_goal() then travel_destination = "Z" else travel_destination = "D" end end return plan_continue_travel() end function plan_fly() if you.xl() >= 14 and intrinsic_flight() and not you.flying() then local a,b = you.mp() if a >= 3 and not you.rooted() and not starving() then if use_ability("Fly") then say("FLYING.") return true end 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 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() 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 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 return false end if drink_by_name("curing") then say("(to cure confusion)") return true end end return false end function plan_cure_starving() if you.hunger_name() == "fainting" or (you.hunger_name() == "starving" and not hp_is_low(50)) then return eat_permafood(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_orbrun_holy_word() if can_read() and want_to_orbrun_holy_word() then return holy_word() end return false 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 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 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) 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_slime_dig_to_rune() if where ~= "Slime:6" or you.have_rune("slimy") then return false end vlist = { {-1,-1}, {-1,1}, {1,-1}, {1,1} } for _,v in ipairs(vlist) do its = items.get_items_at(3*v[1],3*v[2]) if its then for _,it in ipairs(its) do if it.name():find("slimy rune") then local c = find_item("wand", "digging") or find_item("wand", "disintegration") if c and can_zap() then say("ZAPPING " .. item(c).name() .. ".") magic("V" .. letter(c) .. string.upper(delta_to_vi(v[1],v[2]))) return true end return false end end end end return false end function plan_slime_go_to_rune() if where ~= "Slime:6" or you.have_rune("slimy") then return false end x = 2*crawl.random2(2)-1 y = 2*crawl.random2(2)-1 c = delta_to_vi(x,y) magic(control('f') .. "@slimy rune" .. "\ra" .. c .. c .. c .. "\r") return end function plan_slime_forget_map() if where ~= "Slime:6" or slime_forgot_map or you.have_rune("slimy") then return false end slime_forgot_map = true magic("X" .. control('f')) return true 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 selfzap_by_name("teleportation") then dd_hw_meter = 0 return true end if read_by_name("teleportation") then dd_hw_meter = 0 return true end return false end function holy_word() return read_by_name("holy word") end function plan_cure_poison() local hp, mhp = you.hp() if you.poison_survival() <= 1 and you.poisoned() or you.race() == "Deep Dwarf" and you.poison_survival() <= hp - 10 then if drink_by_name("curing") then say("(to cure poison)") return true end end if you.poison_survival() <= 1 and you.poisoned() and can_hand() then hand() return true 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) 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 kill_plant_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 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 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 magic(control(delta_to_vi(x, y))) return 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) for letter, abil in pairs(you.ability_table()) do if abil == name then say("INVOKING " .. name .. ".") 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_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, "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_cure_confusion, "cure_confusion"}, {plan_remove_terrible_jewellery, "remove_terrible_jewellery"}, {plan_teleport, "teleport"}, {plan_dd_recharge_teleport, "dd_recharge_teleport"}, {plan_cure_bad_poison, "cure_bad_poison"}, {plan_heal_wounds, "heal_wounds"}, {plan_choose_tactical_step, "choose_tactical_step"}, {plan_cloud_step, "cloud_step"}, {plan_hand, "hand"}, {plan_resistance, "resistance"}, {plan_bia, "bia"}, {plan_sgd, "sgd"}, {plan_hydra_destruction, "try_hydra_destruction"}, {plan_dd_recharge_heal_wounds, "dd_recharge_heal_wounds"}, {plan_wield_weapon, "wield_weapon"}, {plan_water_step, "water_step"}, {plan_berserk, "berserk"}, {plan_other_step, "other_step"}, } -- hack plan_orbrun_emergency = cascade { {plan_cure_starving, "cure_starving"}, {plan_cure_confusion, "cure_confusion"}, {plan_orbrun_teleport, "orbrun_teleport"}, {plan_dd_recharge_teleport, "dd_recharge_teleport"}, {plan_orbrun_holy_word, "orbrun_holy_word"}, {plan_orbrun_heal_wounds, "orbrun_heal_wounds"}, {plan_orbrun_haste, "orbrun_haste"}, {plan_dd_orbrun_recharge_hasting, "orbrun_dd_recharge_hasting"}, {plan_hand, "hand"}, {plan_resistance, "resistance"}, {plan_dd_recharge_heal_wounds, "dd_recharge_heal_wounds"}, {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_enter_zig, "enter_zig"}, {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_dive, "try_dive"}, {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_enter_branch, "try_enter_branch"}, {plan_go_to_zig, "try_go_to_zig"}, {plan_simple_go_down, "try_simple_go_down"}, {plan_new_travel, "try_new_travel"}, } -- hack plan_move = cascade { {plan_shop, "shop"}, {plan_stairdance_up, "stairdance_up"}, {plan_emergency, "emergency"}, {plan_wait_for_melee, "wait_for_melee"}, {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_join_god, "join_god"}, {plan_find_altar, "try_find_altar"}, {plan_go_to_temple, "try_go_to_temple"}, --{plan_go_to_lair, "try_go_to_lair"}, {plan_explore, "explore"}, {plan_pre_explore2, "pre_explore2"}, {plan_explore2, "explore2"}, {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_slime_dig_to_rune, "slime_dig_to_rune"}, {plan_slime_forget_map, "try_slime_forget_map"}, {plan_slime_go_to_rune, "try_slime_go_to_rune"}, {plan_stuck_teleport, "stuck_teleport"}, {plan_stuck, "stuck"}, } -- hack plan_orbrun_move = cascade { {plan_orbrun_emergency, "orbrun_emergency"}, {plan_attack, "attack"}, {plan_cure_poison, "cure_poison"}, {plan_orbrun_eatrest, "orbrun_eatrest"}, {plan_go_up, "go_up"}, {plan_fly, "fly"}, {plan_find_upstairs, "try_find_upstairs"}, {plan_disturbance_random_step, "disturbance_random_step"}, --{plan_stuck_clear_exclusions, "try_stuck_clear_exclusions"}, {plan_stuck_teleport, "stuck_teleport"}, {plan_autoexplore, "try_autoexplore"}, {plan_gd1, "try_gd1"}, {plan_stuck, "stuck"}, } -- hack plan_abyss_move = cascade { {plan_exit_abyss, "exit_abyss"}, {plan_emergency, "emergency"}, {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_wait, "wait"}, } -- hack ---------------------------------------- -- skill selection function choose_single_skill(sk) you.train_skill(sk, 1) skill_list = {"Fighting","Short Blades","Long Blades","Axes","Maces & Flails", "Polearms","Staves","Unarmed Combat","Bows","Crossbows", "Throwing","Slings","Armour","Dodging","Shields","Spellcasting", "Conjurations","Hexes","Charms","Summonings","Necromancy", "Translocations","Transmutations","Fire Magic","Ice Magic", "Air Magic","Earth Magic","Poison Magic","Invocations", "Evocations","Stealth"} for i,sk2 in ipairs(skill_list) do if sk ~= sk2 then you.train_skill(sk2, 0) end end end function handle_skills() local weapon_skill = wskill() -- need to have at least one skill selected or bad things might happen -- early xom characters might get their weapon removed at any point, so -- always train fighting if you.god() == "Xom" then choose_single_skill("Fighting") you.train_skill(weapon_skill, 1) elseif you.can_train_skill(weapon_skill) then choose_single_skill(weapon_skill) else choose_single_skill("Fighting") end local at_min_delay = (you.base_skill(weapon_skill) >= min_delay_skill()) local ac = armour_ac() local _,mdex = you.dexterity() if you.base_skill("Shields") < target_shield_skill() and (at_min_delay or you.base_skill(weapon_skill) >= 2*you.base_skill("Shields")) and you.base_skill("Fighting") >= you.base_skill("Shields") then you.train_skill("Shields", 1) elseif intrinsic_heavy() and (at_min_delay or mdex >= 15 and you.base_skill(weapon_skill) >= 3*you.base_skill("Dodging")) and you.base_skill("Armour") >= you.base_skill("Dodging") and you.base_skill("Fighting") >= you.base_skill("Dodging") then you.train_skill("Dodging", 1) elseif intrinsic_dodgy() and (at_min_delay or you.base_skill(weapon_skill) >= 2*you.base_skill("Dodging")) and you.base_skill("Fighting") >= you.base_skill("Dodging") then you.train_skill("Dodging", 1) elseif intrinsic_heavy() and (at_min_delay or ac >= 8 and you.base_skill(weapon_skill) >= 3*you.base_skill("Armour")) and you.base_skill("Fighting") >= you.base_skill("Armour") then you.train_skill("Armour", 1) elseif at_min_delay or you.base_skill(weapon_skill) >= 2*you.base_skill("Fighting") then you.train_skill("Fighting", 1) end if you.god() == "Makhleb" and you.piety_rank() >= 2 and you.base_skill("Invocations") < 12 then you.train_skill("Invocations", 1) end -- logic for training skills for MP could be improved probably if you.race() == "Deep Dwarf" and you.can_train_skill("Invocations") and (you.base_skill("Invocations") < 4 or you.base_mp() < 5) then you.train_skill("Invocations", 1) end if you.race() == "Deep Dwarf" and you.god() == "Trog" and (you.base_skill("Evocations") < 4 or you.base_mp() < 5) then you.train_skill("Evocations", 1) end if you.race() == "Vine Stalker" and you.base_skill("Evocations") < 12 and (at_min_delay or you.base_skill(weapon_skill) >= 3*you.base_skill("Evocations")) then you.train_skill("Evocations", 1) end if at_min_delay and you.base_skill(weapon_skill) >= 20 and (you.base_skill(weapon_skill) >= you.base_skill("Fighting") or you.base_skill(weapon_skill) >= 26) then you.train_skill(weapon_skill, 0) end -- make sure we don't run out of skills to train if you.base_skill("Fighting") >= 26 then if intrinsic_dodgy() then you.train_skill("Armour", 1) else you.train_skill("Dodging", 1) end end if (you.base_skill("Armour") >= 26 or you.race():find("Draconian") or you.race() == "Felid" or you.race() == "Octopode") and you.base_skill("Dodging") >= 26 then you.train_skill("Evocations", 1) end end function choose_stat_gain() local str, mstr = you.strength() if intrinsic_dodgy() or mstr >= 20 and mstr >= armour_evp()+2 then return "d" else return "s" 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 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 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 initialize() if not did_first_turn and you.turns() == 0 then did_first_turn = true first_turn() end automatic = true where = you.where() expect_new_location = false 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() end function stop() automatic = false unset_options() end function start() initialize() 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 for key,_ in pairs(c_persist) do if key ~= "record" then c_persist[key] = nil end end end function first_turn() first_turn_persist() if AUTO_START then initialize() end end function ready() 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+20 handle_skills() end local time_passed if you.turns() > old_turn_count then if did_move then move_count = move_count + 1 else move_count = 0 end dd_hw_meter = math.floor(6*dd_hw_meter/7) old_turn_count = you.turns() did_move = false if did_move_towards_monster > 0 then did_move_towards_monster = did_move_towards_monster - 1 end time_passed = true else time_passed = false end if you.where() ~= where then clear_ignores() 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 expect_new_location = false expect_portal = false check_messages() if time_passed and SINGLE_STEP then stop() end if automatic then crawl.flush_input() crawl.more_autoclear(true) update_monster_array() danger = sense_danger(LOS) immediate_danger = sense_immediate_danger() sense_sigmund() 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 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 true end end }