#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
}