< local ATTITUDE_HOSTILE = 0 local ATTITUDE_NEUTRAL = 1 local LOS_RADIUS = 8 local ATTACK_RANGED = 3 local ATTACK_REACHING = 2 local ATTACK_MELEE = 1 local ATTACK_IMPOSSIBLE = 0 AUTOFIGHT_STOP = 75 AUTOFIGHT_HUNGER_STOP = 0 AUTOFIGHT_HUNGER_STOP_UNDEAD = false AUTOFIGHT_CAUGHT = false AUTOFIGHT_THROW = false AUTOFIGHT_THROW_NOMOVE = true AUTOFIGHT_FIRE_STOP = false AUTOFIGHT_WAIT = false AUTOFIGHT_PROMPT_RANGE = true local function sign(x) if x > 0 then return 1 elseif x < 0 then return -1 else return 0 end end local function abs(x) return x * sign(x) end local direction_key_map = { [-1] = { [-1] = 'y', [0] = 'h', [1] = 'b'}, [0] = { [-1] = 'k', [1] = 'j'}, [1] = { [-1] = 'u', [0] = 'l', [1] = 'n'}, } local function tile_is_in_range(source_x, source_y, delta_x, delta_y, maximum_range) return abs(source_x + delta_x) <= maximum_range and abs(source_y + delta_y) <= maximum_range end local function tile_is_adjacent(source_x, source_y, delta_x, delta_y) return tile_is_in_range(source_x, source_y, delta_x, delta_y, 1) end local function get_key_from_direction(delta_x, delta_y) local no_direction = delta_x == 0 and delta_y == 0 local direction_is_not_normalized = not tile_is_adjacent(0, 0, delta_x, delta_y) if (no_direction or direction_is_not_normalized) then error("Out of range access for direction_key_map with delta_x = " .. delta_x .. " delta_y = " .. delta_y) end return direction_key_map[delta_x][delta_y] end local function get_move_key_sequence(target_x, target_x) local step_x = sign(target_x) local step_y = sign(target_y) local key_sequence = "" local key = nil while (target_x ~= 0 and target_y ~= 0) do if (key == nil) then key = get_key_from_direction(sign(target_x), sign(target_y)) end key_sequence = key_sequence .. key target_x = target_x - step_x target_y = target_y - step_y end key = nil while (target_x ~= 0) do if (key == nil) then key = get_key_from_direction(sign(target_x), 0) end key_sequence = key_sequence .. key target_x = target_x - step_x end key = nil while (target_y ~= 0) do if (key == nil) then key = get_key_from_direction(0, sign(target_y)) end key_sequence = key_sequence .. key target_y = target_y - step_y end return key_sequence end local function is_safe_tile(target_x, target_y) local tile_has_web_trap = view.feature_at(target_x, target_y) == "trap_web" if (tile_has_web_trap) then return false end return view.is_safe_square(target_x, target_y) end local function can_move_maybe_with_fighting(target_x, target_y) local tile_is_unseen = view.feature_at(target_x, target_y) == "unseen" if (tile_is_unseen) then return false end local tile_is_not_safe = not view.is_safe_square(target_x, target_y) if (tile_is_not_safe) then return false end local mon = monster.get_monster_at(target_x, target_y) local can_move_or_fight = not mon or not mon:is_firewood() return can_move_or_fight end local function can_swap_places(monster_) local monster_is_not_stationary = true local monster_wont_attack = monster_.attitude() > ATTITUDE_NEUTRAL return monster_is_not_stationary and monster_wont_attack end local function can_move_without_fighting(target_x, target_y) local tile_is_safe = is_safe_tile(target_x, target_y) local mon = monster.get_monster_at(target_x, target_y) local no_fight = not mon or can_swap_places(mon) return tile_is_safe and no_fight end local function try_get_move(source_x, source_y, delta_x, delta_y, tile_check_function) if (delta_x == 0 and delta_y == 0) then return nil end local target_x = source_x + delta_x local target_y = source_y + delta_y local tile_is_outside_of_los = abs(target_x) > LOS_RADIUS or abs(target_y) > LOS_RADIUS if (tile_is_outside_of_los) then return nil end local tile_is_unfitting = not tile_check_function(target_x, target_y) if (tile_is_unfitting) then return nil end return {delta_x, delta_y} end local function try_get_move_lower(source_x, source_y, delta_x, delta_y, tile_check_function) local move = nil crawl.mpr("Moving down") if (abs(delta_y) == 1) then move = try_get_move(source_x, source_y, sign(delta_x), 0, tile_check_function) if (move ~= nil) then return move end end move = try_get_move(source_x, source_y, sign(delta_x), sign(delta_y), tile_check_function) if (move ~= nil) then return move end move = try_get_move(source_x, source_y, sign(delta_x), 0, tile_check_function) if (move ~= nil) then return move end if (abs(delta_x) > abs(delta_y) + 1) then move = try_get_move(source_x, source_y, sign(delta_x), 1, tile_check_function) if (move ~= nil) then return move end move = try_get_move(source_x, source_y, sign(delta_x), -1, tile_check_function) if (move ~= nil) then return move end end move = try_get_move(source_x, source_y, 0, sign(delta_y), tile_check_function) return move end local function try_get_move_middle(source_x, source_y, delta_x, delta_y, tile_check_function) local move = nil crawl.mpr("Moving straight") move = try_get_move(source_x, source_y, sign(delta_x), sign(delta_y), tile_check_function) if (move ~= nil) then return move end move = try_get_move(source_x, source_y, sign(delta_x), 0, tile_check_function) if (move ~= nil) then return move end move = try_get_move(source_x, source_y, 0, sign(delta_y), tile_check_function) return move end local function try_get_move_upper(source_x, source_y, delta_x, delta_y, tile_check_function) local move = nil crawl.mpr("Moving up") if (abs(delta_x) == 1) then move = try_get_move(source_x, source_y, 0, sign(delta_y), tile_check_function) if (move ~= nil) then return move end end move = try_get_move(source_x, source_y, sign(delta_x), sign(delta_y), tile_check_function) if (move ~= nil) then return move end move = try_get_move(source_x, source_y, 0, sign(delta_y), tile_check_function) if (move ~= nil) then return move end if (abs(delta_y) > abs(delta_x) + 1) then move = try_get_move(source_x, source_y, 1, sign(dy), tile_check_function) if (move ~= nil) then return move end move = try_get_move(source_x, source_y, -1, sign(dy), tile_check_function) if (move ~= nil) then return move end end move = try_get_move(source_x, source_y, sign(dx), 0, tile_check_function) if (move ~= nil) then return move end return move end local function choose_move_towards(source_x, source_y, target_x, target_y, tile_check_function) local delta_x = target_x - source_x local delta_y = target_y - source_y local move = nil if (abs(delta_x) > abs(delta_y)) then move = try_get_move_lower(source_x, source_y, delta_x, delta_y, tile_check_function) elseif (abs(delta_x) == abs(delta_y)) then move = try_get_move_middle(source_x, source_y, delta_x, delta_y, tile_check_function) else move = try_get_move_upper(source_x, source_y, delta_x, delta_y, tile_check_function) end return move end local function move_towards(target_x, target_y) local move = choose_move_towards(0, 0, target_x, target_y, can_move_without_fighting) if (move == nil) then crawl.mpr("Failed to move towards target.") return false end local delta_x = move[1] local delta_y = move[2] local key_sequence = get_key_from_direction(delta_x, delta_y) crawl.mpr("Moving keys " .. key_sequence) crawl.process_keys(key_sequence) return true end local function have_melee() local item = items.equipped_at("weapon") return item and item.class == "weapon" and not item.is_ranged and not item.is_melded end local function have_ranged() local item = items.equipped_at("weapon") return item and item.class == "weapon" and item.is_ranged and not item.is_melded end local function have_reaching() local item = items.equipped_at("weapon") return item and item.class == "weapon" and item.reach_range == 8 and not item.is_melded end local function have_throwing() return items.fired_item() ~= nil end local function hack_and_slash_towards(source_x, source_y, target_x, target_y) local tile_is_in_already_in_range = false if (not have_reaching()) then tile_is_in_already_in_range = tile_is_adjacent(source_x, source_y, target_x, target_y) else tile_is_in_already_in_range = tile_is_in_range(source_x, source_y, target_x, target_y, 2) end if (tile_is_already_in_range) then return true end local move = choose_move_towards(source_x, source_y, target_x, target_y, can_move_maybe_with_fighting) if (move == nil) then return false end local delta_x = move[1] local delta_y = move[2] return hack_and_slash_towards(source_x + delta_x, source_y + delta_y, target_x, target_y) end local function tile_is_reachable(target_x, target_y) return true end local function get_monster_info(target_x, target_y) mon = monster.get_monster_at(target_x, target_y) if (not mon) then crawl.mpr("No monster at " .. target_x .. " " .. target_y) return nil end crawl.mpr("Checking info for " .. mon:desc()) info = {} info.desc = mon:desc() if (abs(target_x) > abs(target_y)) then info.distance = abs(target_x) else info.distance = abs(target_y) end info.attack_type = 0 local has_clear_los_with_enemy = you.see_cell_no_trans(target_x, target_y) local can_use_ranged = have_ranged() and has_clear_los_with_enemy local can_use_throwing = have_throwing() and info.distance > 1 and has_clear_los_with_enemy local can_attack_at_range = can_use_ranged or can_use_throwing local can_attack_at_reach = have_reaching() and info.distance <= 2 and has_clear_los_with_enemy local can_attack_in_melee = have_melee() and info.distance == 1 if (can_attack_at_range) then info.attack_type = ATTACK_RANGED elseif (can_attack_at_reach) then info.attack_type = ATTACK_REACHING elseif (can_attack_in_melee) then info.attack_type = ATTACK_MELEE else info.attack_type = ATTACK_IMPOSSIBLE end local can_attack = 0 if (info.attack_type ~= ATTACK_IMPOSSIBLE) then can_attack = 1 else local can_hack_towards = hack_and_slash_towards(0, 0, target_x, target_y) if (can_hack_towards) then can_attack = 0 else can_attack = -1 end end info.can_attack = can_attack info.safe = mon:is_safe() and -1 or 0 info.very_stabbable = mon:is_very_stabbable() and 1 or 0 info.injury = mon:damage_level() info.threat = mon:threat() crawl.mpr("Finished collecting info for " .. info.desc) crawl.mpr("Threat rating " .. info.threat) return info end local function compare_monster_info(m1, m2) flag_order = autofight_flag_order if flag_order == nil then flag_order = {"can_attack", "safe", "distance", "constricting_you", "very_stabbable", "injury", "threat"} end for i, flag in ipairs(flag_order) do if m1[flag] > m2[flag] then return true elseif m1[flag] < m2[flag] then return false end end return false end local function is_candidate_for_attack(x, y) mon = monster.get_monster_at(x, y) if not mon then return false end crawl.mpr("Checking: (" .. x .. "," .. y .. ") " .. mon:desc()) if mon:attitude() ~= ATTITUDE_HOSTILE then return false end if mon:desc() == "butterfly" or mon:desc() == "orb of destruction" then return false end if mon:is_firewood() then if string.find(mon:desc(), "ballistomycete") then return true else return false end end crawl.mpr("Potential candidate " .. mon:desc() .. " with attitude " .. mon:attitude()) return true end local function get_target(movement_allowed) local x, y, new_info local best_x = 0 local best_y = 0 local best_info = nil for x = -LOS_RADIUS, LOS_RADIUS do for y = -LOS_RADIUS, LOS_RADIUS do if is_candidate_for_attack(x, y) then crawl.mpr("Looking for target at " .. x .. " " .. y) new_info = get_monster_info(x, y, movement_allowed) crawl.mpr("Candidate " .. new_info.desc) if (not best_info) or compare_monster_info(new_info, best_info) then best_x = x best_y = y best_info = new_info end end end end return best_x, best_y, best_info end local function execute_action_key_sequence(action_begin_keys, action_positioning_keys, action_end_keys) local key_sequence = "" for index, value in pairs(action_begin_keys) do key_sequence = key_sequence .. value end for index, value in pairs(action_positioning_keys) do key_sequence = key_sequence .. value end for index, value in pairs(action_end_keys) do key_sequence = key_sequence .. value end crawl.process_keys(key_sequence) end local function attack_fire(x, y) local action_begin_keys = {'f', 'r'} local action_positioning_keys = get_move_key_sequence(x, y) local action_end_keys = {'f'} execute_action_key_sequence(action_begin_keys, action_positioning_keys, action_end_keys) end local function attack_fire_stop(x, y) local action_begin_keys = {'f', 'r'} local action_positioning_keys = get_move_key_sequence(x, y) local action_end_keys = {'.'} execute_action_key_sequence(action_begin_keys, action_positioning_keys, action_end_keys) end local function attack_reach(x, y) local action_begin_keys = {'v', 'r'} local action_positioning_keys = get_move_key_sequence(x, y) local action_end_keys = {'.'} execute_action_key_sequence(action_begin_keys, action_positioning_keys, action_end_keys) end local function attack_melee(x, y) local key_sequence = get_key_from_direction(x, y) crawl.process_keys(key_sequence) end local function set_stop_level(key, value, mode) AUTOFIGHT_STOP = tonumber(value) end local function set_hunger_stop_level(key, value, mode) AUTOFIGHT_HUNGER_STOP = tonumber(value) end local function set_hunger_stop_undead(key, value, mode) AUTOFIGHT_HUNGER_STOP_UNDEAD = string.lower(value) ~= "false" end local function set_af_caught(key, value, mode) AUTOFIGHT_CAUGHT = string.lower(value) ~= "false" end local function set_af_throw(key, value, mode) AUTOFIGHT_THROW = string.lower(value) ~= "false" end local function set_af_throw_nomove(key, value, mode) AUTOFIGHT_THROW_NOMOVE = string.lower(value) ~= "false" end local function set_af_fire_stop(key, value, mode) AUTOFIGHT_FIRE_STOP = string.lower(value) ~= "false" end local function set_af_wait(key, value, mode) AUTOFIGHT_WAIT = string.lower(value) ~= "false" end local function set_af_prompt_range(key, value, mode) AUTOFIGHT_PROMPT_RANGE = string.lower(value) ~= "false" end function af_hp_is_low() local hp, mhp = you.hp() return (100 * hp <= AUTOFIGHT_STOP * mhp) end function af_food_is_low() if you.race() == "Mummy" or you.transform() == "lich" then return false elseif (not AUTOFIGHT_HUNGER_STOP_UNDEAD) and (you.race() == "Vampire" or you.race() == "Ghoul") then return false else return (AUTOFIGHT_HUNGER_STOP ~= nil and you.hunger() <= AUTOFIGHT_HUNGER_STOP) end end local function weapon_check() return have_melee() end function attack(movement_allowed) local x, y, info = get_target(movement_allowed) local caught = you.caught() crawl.mpr("Attack type " .. info.attack_type) if af_hp_is_low() then crawl.mpr("You are too injured to fight recklessly!") elseif af_food_is_low() then crawl.mpr("You are too hungry to fight recklessly!") elseif you.confused() then crawl.mpr("You are too confused!") elseif caught then if AUTOFIGHT_CAUGHT then crawl.do_commands({delta_to_vi(1, 0)}) -- Direction doesn't matter. else crawl.mpr("You are " .. caught .. "!") end elseif info == nil then if AUTOFIGHT_WAIT and not allow_movement then crawl.do_commands('.') else crawl.mpr("No target in view!") end elseif info.attack_type == 3 then if AUTOFIGHT_FIRE_STOP then attack_fire_stop(x,y) else attack_fire(x,y) end elseif info.attack_type == 2 then attack_melee(x,y) elseif info.attack_type == 1 then attack_reach(x,y) elseif info.attack_type == -1 then crawl.mpr("No reachable target in view!") elseif allow_movement then if not AUTOFIGHT_PROMPT_RANGE or weapon_check() then move_towards(x,y) end elseif AUTOFIGHT_WAIT then crawl.do_commands('.') else crawl.mpr("No target in range!") end end function hit_closest() crawl.mpr("hit_closest") attack(true) end function hit_adjacent() crawl.mpr("hit_adjacent") attack(false) end function toggle_autothrow() AUTOFIGHT_THROW = not AUTOFIGHT_THROW crawl.mpr(AUTOFIGHT_THROW and "Enabling autothrow." or "Disabling autothrow.") end chk_lua_option.autofight_stop = set_stop_level chk_lua_option.autofight_hunger_stop = set_hunger_stop_level chk_lua_option.autofight_hunger_stop_undead = set_hunger_stop_undead chk_lua_option.autofight_caught = set_af_caught chk_lua_option.autofight_throw = set_af_throw chk_lua_option.autofight_throw_nomove = set_af_throw_nomove chk_lua_option.autofight_fire_stop = set_af_fire_stop chk_lua_option.autofight_wait = set_af_wait chk_lua_option.autofight_prompt_range = set_af_prompt_range > default_manual_training = true : if you.race() == "Mummy" then autopickup = ()$?!+"/%\}0 : else autopickup = $?!+"/% include = autopickup_exceptions.txt : end lua_file = lua/stash.lua lua_file = lua/wield.lua lua_file = lua/runrest.lua lua_file = lua/gearset.lua lua_file = lua/trapwalk.lua drop_filter = useless_item default_target = true darken_beyond_range = true level_map_title = true travel_delay = -1 explore_delay = -1 travel_avoid_terrain = shallow water explore_greedy = true explore_wall_bias = 1 travel_key_stop = true auto_exclude = oklob,statue,curse skull,roxanne,hyperactive,lightning spire runrest_ignore_poison = 2:30 runrest_ignore_monster = butterfly:1 include = runrest_messages.txt trapwalk_safe_hp = dart:20,needle:15,arrow:35,bolt:45,spear:40,axe:45,blade:95 annotate_item_class = true annotate_item_dropped = true sort_menus = inv: true : equipped, freshness, charged autofight_stop = 75 hp_warning = 50 mp_warning = 0 hp_colour = 75:yellow, 50:red mp_colour = 50:yellow, 25:red show_more = true item_stack_summary_minimum = 10 include = standard_colours.txt include = food_colouring.txt include = menu_colours.txt menu_colour = pickup:green:god gift menu_colour = inventory:white:\w \+\s menu_colour = inventory:white:\w \#\s include = messages.txt menu_colour = notes:white:Reached XP level force_more_message = There are no visible monsters within range force_more_message = This wand has no charges force_more_message = You have reached level force_more_message = Your scales start force_more_message = You fall through a shaft force_more_message = Careful! force_more_message = interdimensional caravan force_more_message = distant snort include = tiles_options.txt dump_item_origins = all ood_interesting = 8 note_hp_percent = 5 note_all_skill_levels = true note_all_spells = true note_xom_effects = true note_items = rod of, acquirement, of Zot note_messages = You pass through the gate note_messages = cast .* Abyss note_messages = Your scales start note_messages = protects you from harm note_messages = You fall through a shaft note_monsters = orb of fire, ancient lich, fiend