#Note the minimum possible changes to get this to work on 0.13 have been done. If you run into a bug or need to update scary_monsters feel free to do so, although you might want to do it on the other serves (currently CLAN, CSZO, and CAO) too. ##### CURRENT MAJOR ISSUES ##### # # * plan_happy_stop() is pretty crude... it should be changed to save a list of # messages for the player instead of just one, at any rate. It would also be # good if you could press tab to restart after being stopped by this at the # hardcoded level stops. # # * Better autoupgrading of armour would be nice, as would be even the most # basic weapon upgrading (switching from the starting hand axe). Also # jewellery upgrading from empty slots. The real problem with more # complicated autoupgrading is that you don't want it to overwrite the # human's choices... not sure what to do there. # # * I really feel improvements to early game should be possible... some ideas: # * handle jellies better (probably this just means always stopping at them # when using a corrodable weapon) # * do something to make stopping at low HP and very hungry less common... # maybe just try eating permafood at very hungry instead of autoexploring # (i.e. after searching for corpses) # * tweak scary_monsters more # # * Autoexploring into shops occasionally seems to bug out. This is # especially noticeable on Orc:4, though hard to reproduce. # ##### SOME "MINOR" ISSUES ##### # # * water + electric weapon stops it # * glass usually stops it # * weapon/armour should be dropped if it autoupgrades away from it # * swapping to flaming weapons for hydras would be cool # * it picks up and holds onto too many wands late game # * armour upgrading while poisoned causes trouble # * fighting monsters while slowed can be a bit dangerous, though I'm not sure # it is worth the extra stops # * auto-berserk in low levels might be good # * read-IDing enchant armour and recharging correctly would be cool, but I # don't think we have any way of knowing which scrolls we have already # IDed. # * maybe stop when portal vaults are found? or enter them even? # * it doesn't stop on rock worm-derived undead ################################ ##### 1 species = HO background = Be weapon = hand axe default_manual_training = true ##### 3 autopickup = $?!"/% ae := autopickup_exceptions df := drop_filter ae = df = ae += useless_item df += useless_item ae += dangerous_item df += dangerous_item ae += evil_item df += evil_item ae += amulet of (the gourmand|warding|stasis) ae += amulet of (resist .*|clarity|rage|conservation|guardian spirit|faith) df += amulet of (the gourmand|rage|warding) ae += ring of (see invisible|levitation|poison resistance|teleportation) ae += ring of (invisibility|teleport control|magical power) ae += ring of (sustain abilities|sustenance|wizardry|life protection) ae += ring of (strength|dexterity|intelligence|ice) df += ring of (invisibility|sustain abilities|life protection|magical power) df += ring of (strength|dexterity|intelligence|ice|sustenance) ae += scrolls? of (unholy creation|vulnerability|vorpalise weapon) ae += scrolls? of (magic mapping|fog|fear|silence) df += scrolls? of (unholy creation|enchant .*|vulnerability|vorpalise weapon) df += scrolls? of (magic mapping|fog|fear|silence) ae += potions? of (restore abilities|brilliance|magic|berserk rage) ae += potions? of (cure mutation|resistance|levitation|invisibility|blood) ae += potions? of coagulated blood df += potions? of (restore abilities|brilliance|magic|berserk rage) df += potions? of (levitation|invisibility|blood|coagulated blood) ae += wand of (random effects|slowing|magic darts|flame|frost|confusion) ae += wand of (enslavement|paralysis|invisibility|lightning|fireball) df += wand of (random effects|slowing|magic darts|flame|frost|confusion) df += wand of (enslavement|paralysis|invisibility|lightning|fireball) : if you.num_runes() < 3 then -- change this to not require save/load sometime df += scrolls? of recharging df += potions? of (cure mutation|resistance) : end { good_slots = {cloak="Cloak", helmet="Helmet", gloves="Gloves", boots="Boots"} function autopickup(it, name) if name:find("of Zot") then return true end local class = it.class(true) old_value = 0 new_value = 0 if class == "armour" then st, _ = it.subtype() if good_slots[st] ~= nil then return (items.equipped_at(good_slots[st]) == nil or it.artefact or name:find("runed") or name:find("glowing") or name:find("embroidered")) else if not items.equipped_at("Armour") then return true end if name:find("crystal plate armour") then new_value = new_value + 140 elseif name:find("gold dragon") then new_value = new_value + 130 elseif name:find("pearl dragon") then new_value = new_value + 125 elseif name:find("plate armour") or name:find("storm dragon") then new_value = new_value + 120 elseif name:find("splint mail") then new_value = new_value + 110 elseif name:find("chain mail") then new_value = new_value + 100 else return false end if it.artefact then new_value = new_value + 25 elseif name:find("runed") or name:find("glowing") or name:find("embroidered") then new_value = new_value + 5 end oname = items.equipped_at("Armour").name() if oname:find("crystal plate armour") then old_value = old_value + 140 elseif oname:find("gold dragon") then old_value = old_value + 130 elseif oname:find("pearl dragon") then old_value = old_value + 125 elseif oname:find("plate armour") or oname:find("storm dragon") then old_value = old_value + 120 elseif oname:find("splint mail") then old_value = old_value + 110 elseif oname:find("chain mail") then old_value = old_value + 100 end return (new_value > old_value) end elseif class == "weapon" then if not items.equipped_at("Weapon") then return true end if name:find("executioner") then new_value = new_value + 140 elseif name:find("battleaxe") then new_value = new_value + 130 elseif name:find("broad axe") then new_value = new_value + 120 elseif name:find("war axe") then new_value = new_value + 110 elseif name:find("hand axe") then new_value = new_value + 100 else return false end if name:find("vamp") then new_value = new_value + 25 elseif it.artefact or name:find("god gift") then new_value = new_value + 15 elseif name:find("runed") or name:find("glowing") then new_value = new_value + 5 end oname = items.equipped_at("Weapon").name() if oname:find("executioner") then old_value = old_value + 140 elseif oname:find("battleaxe") then old_value = old_value + 130 elseif oname:find("broad axe") then old_value = old_value + 120 elseif oname:find("war axe") then old_value = old_value + 110 elseif oname:find("hand axe") then old_value = old_value + 100 end if oname:find("vamp") then old_value = old_value + 10 end return (new_value > old_value) end if it.artefact then return true end return false end add_autopickup_func(autopickup) } autopickup_no_burden = false travel_delay = -1 explore_delay = -1 travel_key_stop = false explore_stop = explore_stop += branches,portals,stairs,greedy_visited_item_stack # explore_stop += greedy_sacrificeable # auto_sacrifice = true stop := runrest_stop_message ignore := runrest_ignore_message stop = ignore = ignore += .* runrest_ignore_poison = 3:15 runrest_ignore_monster += fish:2 runrest_ignore_monster += shark:2 runrest_ignore_monster += butterfly:1 trapwalk_safe_hp = net:1,dart:1,needle:1,arrow:1,bolt:1,spear:1,blade:1,alarm:1 autofight_stop = 50 hp_warning = 0 show_more = false show_inventory_weights = true show_newturn_mark = false list_rotten = false force_more_message = show_travel_trail = false skill_focus = false # channel.multiturn = mute autoinscribe += distortion:!w autoinscribe += potion.*mutation:!q autoinscribe += potion.*berserk rage:!q autoinscribe += scroll.*torment:!r autoinscribe += scroll.*silence:!r autoinscribe += slaying:mikee flush.failure = false ##### 4 dump_order = header,hiscore,stats,misc,mutations,skills,spells,inventory 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 ##### 5 char_set = ascii cset = cloud:xa4 cset_ascii=item_orb:0 use_fake_player_cursor = true { -- options that should be switched on/off with the bot function set_options() crawl.setopt("auto_list = false") crawl.setopt("confirm_butcher = always") crawl.setopt("hp_warning = 0") crawl.setopt("clear_messages = false") 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") end function unset_options() crawl.setopt("auto_list = true") crawl.setopt("always_confirm_butcher = auto") crawl.setopt("hp_warning = 25") crawl.setopt("clear_messages = true") 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") end -- some monsters that we don't want to autofight unless we are at least a -- given level: local scary_monsters = { {7, "jelly"}, {7, "ogre"}, {17, "ogre mage"}, {7, "orc wizard"}, {10, "orc warrior"}, {10, "orc priest"}, {17, "orc sorcerer"}, {17, "orc high priest"}, {17, "orc warlord"}, {7, "ice beast"}, {7, "Sigmund"}, {7, "Grinder"}, {7, "Crazy Yiuf"}, {7, "Prince Ribbit"}, {7, "Dowan"}, {7, "Duvessa"}, {12, "spiny frog"}, {12, "black mamba"}, {12, "blink frog"}, {14, "death yak"}, {17, "dire elephant"}, {20, "Rupert"}, {20, "Aizul"}, {20, "hydra"}, {100, "statue"}, {100, "Nessos"}, {100, "Nikola"}, {100, "Sonja"}, {100, "Louise"}, {100, "catoblepas"}, {100, "Mennas"}, {100, "Margery"}, {100, "Frederick"}, {100, "Boris"}, {100, "Mara"}, {100, "boggart"}, {100, "lich"}, {100, "'s ghost"}, {100, "' ghost"}, {100, "oklob"}, {100, "rock worm"}, {100, "hellion"}, {100, "tormentor"}, {100, "hell sentinel"}, {100, "fiend"}, {100, "curse toe"}, {100, "curse skull"}, {100, "Tiamat"}, {100, "titanic slime creature"}, {100, "enormous slime creature"}, } -- hack -- some variables: local dump_count = you.turns() + 1000 - (you.turns() % 1000) local skill_count = you.turns() - (you.turns() % 100) local danger local where local expect_new_location local automatic = false local happy_stop_msg = nil local do_explore = true function initialize() automatic = true where = you.where() expect_new_location = false set_options() end function magic(command) crawl.process_keys(command .. string.char(27) .. string.char(27) .. string.char(27)) end function stop() automatic = false unset_options() end function start() initialize() ready() end function note(x) crawl.take_note(you.turns() .. " ||| " .. x) end function say(x) crawl.mpr(you.turns() .. " ||| " .. x) note(x) end 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 --------------------------------------- -- status-checking functions function hp_is_low() local hp, mhp = you.hp() if you.berserk() then return (3*hp <= mhp) else return (2*hp <= mhp) end end function should_rest() local hp, mhp = you.hp() return (4*hp <= 3*mhp or you.confused() or you.slowed() or you.berserk() or you.teleporting() or you.silencing()) end function sense_danger(no_ballisto) local x,y for x = -8,8 do for y = -8,8 do if is_candidate_for_attack(x,y,no_ballisto) then return true end end end return false end function check_scary_monsters() local x,y local xl = you.xl() for x = -8,8 do for y = -8,8 do m = monster.get_monster_at(x, y) if m and (m:attitude() == 0) and not contains_string_in(m:desc(), {"skeleton", "zombie", "simulacrum", "spectral"}) then if m:is(68) then -- MB_DEATHS_DOOR, hopefully return true end desc = m:desc() for _, value in ipairs(scary_monsters) do if xl < value[1] and string.find(desc, value[2]) then return true end end end end end return false end function want_permafood() return ((you.hunger_name() == "near starving" or you.hunger_name() == "very hungry" and you.xl() >= 18) or you.hunger_name() == "starving") end function want_chunk() return (you.hunger_name() == "hungry" or you.hunger_name() == "very hungry" or want_permafood()) end function on_corpses() local fl = you.floor_items() for it in iter.invent_iterator:new(fl) do if string.find(it.name(), "corpse") and not string.find(it.name(), "rotting") then return true end end return false end function on_dangerous_corpse() local fl = you.floor_items() for it in iter.invent_iterator:new(fl) do if string.find(it.name(), "corpse") then return food.dangerous(it) end end return false end function on_gdc() local fl = you.floor_items() for it in iter.invent_iterator:new(fl) do if string.find(it.name(), "corpse") then return string.find(it.name(), "golden dragon") end end return false end function inventory() return iter.invent_iterator:new(items.inventory()) end -- set skill training function handle_skills() train_axes = you.skill("Axes") <= 26 and 1 or 0 train_fight_armour = you.skill("Axes") >= 20 and 1 or 0 train_dodge = you.skill("Fighting") >= 20 and 1 or 0 train_traps = you.skill("Fighting") >= 10 and you.skill("Traps") <= 5 and 1 or 0 you.train_skill("Axes", train_axes) you.train_skill("Fighting", train_fight_armour) you.train_skill("Armour", train_fight_armour) you.train_skill("Dodging", train_dodge) you.train_skill("Traps", train_traps) end function check_messages() local recent_messages = crawl.messages(10) if recent_messages:find("Found a staircase to the Lair") then happy_stop_msg = "Found Lair." elseif recent_messages:find("Found a staircase to the Vaults") then happy_stop_msg = "Found Vaults." end end ---------------------------------------------- function rest() magic("s") end function attack() make_attack(get_target()) end function pray() magic("p") end function chop() magic("ccq") end function wait() magic("s") end --------------------------------------- -- plans function panic(msg) crawl.mpr("" .. msg .. "") stop() end function cascade(plans) local plan_turns = {} return function () for i, plan in ipairs(plans) do if you.turns() ~= plan_turns[plan] then result = plan() if not automatic then break end plan_turns[plan] = you.turns() if result then return true end end end end end function plan_unhappy_stop() reason = stop_reason() if reason then panic(reason) end end function stop_reason() if you.where() ~= where then if expect_new_location then where = you.where() else return "Shafted!" end end expect_new_location = false hp, mhp = you.hp() if you.rotting() then return "Rotting!" elseif you.on_fire() and not you.conservation() then return "On fire!" elseif you.petrifying() then return "Petrifying!" elseif you.confused() and sense_danger(true) then return "Confused!" --elseif danger and you.teleporting() then -- return "Teleporting!" elseif hp_is_low() and sense_danger(true) then return "Low HP!" elseif hp < 6 or 8*hp <= mhp then return "Very low HP!" elseif check_scary_monsters() then return "Dangerous monsters!" elseif not options.autopick_on then return "Invisible monsters!" elseif free_inventory_slots() <= 2 then return "Inventory nearly full!" elseif you.burden() >= you.max_burden() then return "Burdened!" end end function free_inventory_slots() local slots = 52 for _ in inventory() do slots = slots - 1 end return slots end function plan_attack() if danger then attack() return true end end function plan_eat_chunk() if want_chunk() then for it in inventory() do if string.find(it.name(), "chunk") and not food.dangerous(it) then magic("ee") return true end end end end function plan_eat_permafood() if want_permafood() then if eat_permafood() then return true else panic("Out of food!") end end end function eat_permafood() local l local max_prefer = 0 for it in inventory() do if it.class(true) == "food" then local name = it.name() local prefer if name:find("ration") or name:find("royal") then prefer = 1 elseif name:find("honey") then prefer = 2 else prefer = 3 end if prefer > max_prefer then l = items.index_to_letter(it.slot) max_prefer = prefer end end end if max_prefer > 0 then magic("e" .. l) return true end end local plan_eat = cascade { plan_eat_chunk, plan_eat_permafood } function plan_rest() if should_rest() then rest() return true end end function plan_handle_corpses() if on_corpses() then if (not want_chunk() or on_dangerous_corpse() or you.levitating()) and not string.find(view.feature_at(0, 0), "altar") and not you.silenced() and not on_gdc() then pray() else chop() end return true end end function plan_find_corpses() magic(string.char(6) .. "@corpse&&!!rott&&!!skel" .. "\ra\r") return true end function plan_autoexplore() magic("o") return true end function plan_drop_filtered_items() magic("d,,\r") return true end function plan_quaff_id() for it in inventory() do if it.class(true) == "potion" and it.name():find("potions") and not (it.name():find("potion of") or it.name():find("potions of")) then l = items.index_to_letter(it.slot) magic("q" .. l) return true end end end function plan_read_id() for it in inventory() do if it.class(true) == "scroll" and not (it.name():find("scroll of") or it.name():find("scrolls of") or it.tried) then items.swap_slots(it.slot, items.letter_to_index('Y'), false) magic("rY\rY b") -- hack that seems to handle all scrolls somehow return true end end end function plan_use_id_scrolls() 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 end local oldslots = { } local newslots = {[0] = 'y', [1] = 'k', [2] = 'u'} -- fairly harmless keys local count = 0 for it in inventory() do if it.class(true) == "jewellery" and not it.name():find(" of ") and not it.name():find('ring "') and not it.name():find('amulet "') and count < 3 then oldslots[count] = it.slot count = count + 1 end end local count1 = count if count < 3 then for it in inventory() do if ((it.class(true) == "wand" or it.class(true) == "scroll") and it.name():find("tried") or (it.class(true) == "potion" and not it.name():find("potion of") and not it.name():find("potions of"))) and count < 3 then oldslots[count] = it.slot count = count + 1 end end end if count1 == 0 and count < 3 then return end for i = 0,count-1 do items.swap_slots(oldslots[i], items.letter_to_index(newslots[i]), false) end if count > 0 then magic("r" .. items.index_to_letter(id_scroll.slot) .. "y k u") return true end end function plan_use_good_consumables() for it in inventory() do if it.class(true) == "scroll" then if it.name():find("acquirement") then magic("r" .. items.index_to_letter(it.slot) .. " b") return true elseif it.name():find("enchant weapon") then weap = items.equipped_at("weapon") if weap and not weap.artefact and (weap.name():find("vamp") or weap.name():find("anti") and weap.name():find("executioner")) then magic("r" .. items.index_to_letter(it.slot)) return true end elseif it.name():find("enchant armour") then body = items.equipped_at("armour") if body and not body.artefact and (body.name():find("gold dragon") and not body.name():find("+12") or body.name():find("pearl dragon") and not body.name():find("+10") or body.name():find("crystal plate") and not body.name():find("+14")) then magic("r" .. items.index_to_letter(it.slot) .. items.index_to_letter(body.slot)) return true end elseif it.name():find("recharging") then for it2 in inventory() do if it2.class(true) == "wand" and (it2.name():find("heal wounds") or it2.name():find("hasting")) and not (it2.name():find("recharged") or it2.name():find("9")) then magic("r" .. items.index_to_letter(it.slot) .. items.index_to_letter(it2.slot)) return true end end end elseif it.class(true) == "potion" then if it.name():find("of gain") or it.name():find("experience") then magic("q" .. items.index_to_letter(it.slot)) return true end end end end function plan_zap_id() for it in inventory() do if it.class(true) == "wand" and not (it.name():find("wand of") or it.name():find("empty") or it.tried) then l = items.index_to_letter(it.slot) magic("V" .. l .. ".Y") return true end end end -- TODO: Handle things like upgrading (non-artefact) armour slots -- to better base types, e.g. chain -> plate, wizard hat -> helmet function plan_upgrade_armour() for it in inventory() do if it and it.class(true) == "armour" then local st, _ = it.subtype() local equip = false -- Fill empty slots. if good_slots[st] ~= nil and items.equipped_at(good_slots[st]) == nil then equip = true -- Try to upgrade the starting animal skin at least. elseif items.equipped_at("Armour").name():find("animal skin") and not items.equipped_at("Armour").cursed and not (items.equipped_at("Cloak") and items.equipped_at("Cloak").cursed) and st == "body" and not it.name():find("animal skin") then equip = true end if equip then l = items.index_to_letter(it.slot) magic("W" .. l) return true end end end end function plan_done_exploring() crawl.mpr("Done exploring.") stop() end function plan_happy_stop() if not happy_stop_msg and (where == "D:13" or where == "D:20" or where == "D:27" or where == "Lair:8" or where == "Orc:4" or where == "Vaults:4" or where == "Vaults:5" or where == "Zot:4" or where == "Snake:5" or where == "Swamp:5" or where == "Shoals:5" or where == "Spider:5" or where == "D:7") then happy_stop_msg = "Finished " .. where .. "." end if happy_stop_msg then crawl.mpr("" .. happy_stop_msg .. "") happy_stop_msg = nil stop() end end function plan_orbrun_eat_permafood() if you.hunger_name() ~= "completely stuffed" and you.hunger_name() ~= "very full" then if eat_permafood() then return true else panic("Out of food!") end end end function plan_orbrun_rest() if you.confused() or you.slowed() or you.berserk() or you.teleporting() or you.silencing() then rest() return true end end function plan_hand() local hp, mhp = you.hp() if mhp - hp >= 30 and not you.regenerating() and not you.silenced() then magic("ab") return true end end function plan_go_up() local feat = view.feature_at(0,0) if feat:find("stone_stairs_up") or feat:find("escape_hatch_up") or feat:find("return_from_zot") or feat:find("exit_dungeon") then expect_new_location = true magic("<") return true end 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 end function plan_simple_go_down() expect_new_location = true magic("G>") return true end function plan_find_upstairs() magic("X<\r") 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:find("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() panic("Can't find downstairs!") end function plan_orbrun_stuck() panic("Can't find upstairs!") end -- Step randomly if interrupted by a disturbance. function plan_disturbance_random_step() if crawl.messages(5):find("There is a strange disturbance nearby!") then 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 view.is_safe_square(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 magic(delta_to_vi(dx,dy)) return true end end end function plan_wait() magic(".") end function plan_found_exit() if crawl.messages(10):find("Found a gateway leading out of the Abyss") then crawl.mpr("Found Abyss exit!") stop() end end move = cascade { plan_unhappy_stop, plan_attack, plan_eat, plan_rest, plan_handle_corpses, plan_find_corpses, plan_autoexplore, plan_disturbance_random_step, plan_use_good_consumables, plan_drop_filtered_items, plan_upgrade_armour, plan_read_id, plan_zap_id, plan_quaff_id, plan_use_id_scrolls, plan_happy_stop, -- plan_go_down, -- plan_find_downstairs, plan_simple_go_down, -- let's test this out plan_stuck, } -- hack no_explore_move = cascade { plan_unhappy_stop, plan_attack, plan_eat, plan_rest, plan_use_good_consumables, plan_drop_filtered_items, plan_upgrade_armour, plan_read_id, plan_zap_id, plan_quaff_id, plan_use_id_scrolls, plan_happy_stop, stop, } -- hack orbrun_move = cascade { plan_unhappy_stop, plan_attack, plan_orbrun_eat_permafood, plan_orbrun_rest, plan_hand, plan_go_up, plan_find_upstairs, plan_orbrun_stuck, } -- hack orbrun_no_explore_move = cascade { plan_unhappy_stop, plan_attack, plan_orbrun_eat_permafood, plan_orbrun_rest, stop, } -- hack abyss_move = cascade { plan_found_exit, plan_unhappy_stop, plan_attack, plan_eat, plan_rest, plan_handle_corpses, plan_find_corpses, plan_autoexplore, plan_disturbance_random_step, plan_use_good_consumables, plan_upgrade_armour, plan_read_id, plan_zap_id, plan_quaff_id, plan_use_id_scrolls, plan_wait, } -- hack abyss_no_explore_move = cascade { plan_found_exit, plan_unhappy_stop, plan_attack, plan_eat, plan_rest, plan_use_good_consumables, plan_upgrade_armour, plan_read_id, plan_zap_id, plan_quaff_id, plan_use_id_scrolls, stop, } -- hack ---------------------------------------------- -- the autofight core 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 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 adjacent(dx, dy) return abs(dx) <= 1 and abs(dy) <= 1 end function have_reaching() local wp = items.equipped_at("weapon") return wp and wp.reach_range == 8 and not wp.is_melded end function have_ranged() local wp = items.equipped_at("weapon") return wp and wp.is_ranged and not wp.is_melded end local ATT_NEUTRAL = 1 local ATT_HOSTILE = 0 function try_move(dx, dy) m = monster.get_monster_at(dx, dy) if view.is_safe_square(dx, dy) and (not m or m:attitude() > ATT_NEUTRAL) then return delta_to_vi(dx, dy) else return nil end end function move_towards(dx, dy) 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 then panic("Failed to move towards target.") else magic(move) end end function get_monster_info(dx, dy) m = monster.get_monster_at(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 have_ranged() then info.attack_type = you.see_cell_no_trans(dx, dy) and 3 or 0 elseif 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 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_ballisto) m = monster.get_monster_at(x, y) if not m or m:attitude() ~= ATT_HOSTILE then return false end if m:name() == "butterfly" or m:name() == "orb of destruction" then return false end if m:is_firewood() then if string.find(m:name(), "ballistomycete") and not no_ballisto then return true end return false end return true end function get_target() local x, y, bestx, besty, best_info, new_info bestx = 0 besty = 0 best_info = nil for x = -8, 8 do for y = -8, 8 do if is_candidate_for_attack(x, y) then new_info = get_monster_info(x, y) if (not best_info) or compare_monster_info(new_info, best_info) then bestx = x besty = y best_info = new_info end end end end return bestx, besty, best_info end function attack_fire(x, y) magic('fr' .. vector_move(x, y) .. 'f') end function attack_reach(x, y) magic('vr' .. vector_move(x, y) .. '.') end function attack_melee(x, y) magic(delta_to_vi(x, y)) end function make_attack(x, y, info) if info.attack_type == 3 then attack_fire(x, y) elseif info.attack_type == 2 then attack_melee(x, y) elseif info.attack_type == 1 then attack_reach(x, y) else move_towards(x, y) end end ---------------------------------------- -- abstract functions function startstop() if automatic then stop() else start() end end function ready() if you.turns() >= dump_count then dump_count = dump_count+1000 crawl.dump_char() end if you.turns() >= skill_count then skill_count = skill_count+100 handle_skills() end check_messages() if automatic then crawl.flush_input() crawl.more_autoclear(true) danger = sense_danger() if you.where() == "Abyss" then if do_explore then abyss_move() else abyss_no_explore_move() end elseif you.have_orb() then if do_explore then orbrun_move() else orbrun_no_explore_move() end else if do_explore then move() else no_explore_move() end end end end function choose_stat_gain() if you.xl() <= 10 then crawl.sendkeys('s ') else crawl.sendkeys('d ') end end -- Comment this out and macro something to ===startstop if you want tab to -- work as autofight. function hit_closest() startstop() end -- Macro something to ===toggle_explore to be able to toggle whether -- the bot tries to explore levels or go downstairs function toggle_explore() if do_explore then crawl.mpr("Disabling exploration.") do_explore = false else crawl.mpr("Enabling exploration.") do_explore = true end end }