Temple Termagant
Posts: 5
Joined: Saturday, 16th February 2013, 02:40
autofight edit
In order to learn some lua, I implemented a hack that improves autofight for me by autolocking when a new type of creature comes into view. Most of my autofight deaths come from leaning on the key and not letting go in time when something horrible appears. So, rather than fiddle with thresholds or "more" messages, I added state to the autofight code. Now, I honestly didn't even think of just using "more" messages, so this is maybe an over-complicated solution. However, it has at least one potential advantage (not implemented yet): it can be made to lock only on monsters of a high relative threat level in addition to a static blacklist. At any rate, the entire autofight.lua is attached below, in case anyone is interested.
Basically, if you used autofight on turn N-1 and then, on turn N, a creature comes into view of a species/name which was not present on turn N-1, the autofight locks down. It can be reenabled by taking a manual move (technically, anything which increments you.turns() by at least 1).
So, you can lean on the key to wipe out a warren of rats or green rats, without worrying about the ogre (resp. hydra) which may be around the corner (apart from possibly ending up right next to it, of course). The first draft simply compared the count of monsters in view, but this would fail if immediately after you kill the last rat, the hydra comes into view.
The checking is very primitive and runs in quadratic time in the number of monsters on the screen, but whatever, the number of monsters is O(1). I'm sure it runs fine on anything which can handle tiles and using autofight on a screen full of monsters is usually a bad idea.
Cheers & good night.
Basically, if you used autofight on turn N-1 and then, on turn N, a creature comes into view of a species/name which was not present on turn N-1, the autofight locks down. It can be reenabled by taking a manual move (technically, anything which increments you.turns() by at least 1).
So, you can lean on the key to wipe out a warren of rats or green rats, without worrying about the ogre (resp. hydra) which may be around the corner (apart from possibly ending up right next to it, of course). The first draft simply compared the count of monsters in view, but this would fail if immediately after you kill the last rat, the hydra comes into view.
The checking is very primitive and runs in quadratic time in the number of monsters on the screen, but whatever, the number of monsters is O(1). I'm sure it runs fine on anything which can handle tiles and using autofight on a screen full of monsters is usually a bad idea.
Cheers & good night.
- Code:
---------------------------------------------------------------------------
-- autofight.lua:
-- One-key fighting.
--
-- To use this, please bind a key to the following commands:
-- ===hit_closest (Tab by default)
-- ===hit_adjacent (Shift-Tab by default)
-- ===toggle_autothrow (not bound by default)
--
-- This uses the very incomplete client monster and view bindings, and
-- is currently very primitive. Improvements welcome!
---------------------------------------------------------------------------
local ATT_HOSTILE = 0
local ATT_NEUTRAL = 1
local AUTOFIGHT_LAST = -2
local AUTOFIGHT_LOCK = false
local AUTOFIGHT_COUNT = -1
local AUTOFIGHT_WARNED = false
local AUTOFIGHT_NAMES = {}
AUTOFIGHT_STOP = 30
AUTOFIGHT_THROW = false
AUTOFIGHT_THROW_NOMOVE = true
local 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'},
}
return d2v[dx][dy]
end
local function sign(a)
return a > 0 and 1 or a < 0 and -1 or 0
end
local function abs(a)
return a * sign(a)
end
local function adjacent(dx, dy)
return abs(dx) <= 1 and abs(dy) <= 1
end
local 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
local function have_reaching()
local wp = items.equipped_at("weapon")
return wp and wp.reach_range == 8 and not wp.is_melded
end
local function have_ranged()
local wp = items.equipped_at("weapon")
return wp and wp.is_ranged and not wp.is_melded
end
local function have_throwing(no_move)
return (AUTOFIGHT_THROW or no_move and AUTOFIGHT_THROW_NOMOVE) and items.fired_item() ~= nil
end
local function try_move(dx, dy)
m = monster.get_monster_at(dx, dy)
-- attitude > ATT_NEUTRAL should mean you can push past the monster
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
local 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
crawl.mpr("Failed to move towards target.")
else
crawl.process_keys(move)
end
end
local function get_monster_info(dx,dy,no_move)
m = monster.get_monster_at(dx,dy)
name = m:name()
if not m then
return nil
end
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
if info.attack_type == 0 and have_throwing(no_move) and you.see_cell_no_trans(dx, dy) then
-- Melee is better than throwing.
info.attack_type = 3
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
-- Only prioritize good stabs: sleep and paralysis.
info.very_stabbable = (m:stabbability() >= 1) and 1 or 0
info.injury = m:damage_level()
info.threat = m:threat()
info.name = name
info.orc_priest_wizard = (name == "orc priest" or name == "orc wizard") and 1 or 0
return info
end
local 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
elseif m1[flag] < m2[flag] then
return false
end
end
return false
end
local function is_candidate_for_attack(x,y)
m = monster.get_monster_at(x, y)
--if m then crawl.mpr("Checking: (" .. x .. "," .. y .. ") " .. m:name()) end
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
--crawl.mpr("... is firewood.")
if string.find(m:name(), "ballistomycete") then
return true
end
return false
end
return true
end
local function get_target(no_move)
local x, y, bestx, besty, count, best_info, new_info
bestx = 0
besty = 0
count = 0
best_info = nil
local names = {}
for x = -8,8 do
for y = -8,8 do
if is_candidate_for_attack(x, y) then
count = count + 1
new_info = get_monster_info(x, y, no_move)
table.insert(names, new_info.name)
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, count, names
end
local function table_contains(t, s)
for k, v in pairs(t) do if v == s then
return true
end end
return false
end
-- check if there is an element in t which is not in u
local function table_not_in(t, u)
-- special case if u is empty return false, since this is the first autofight.
if #u==0 then return false end
for k, v in pairs(t) do
contained = table_contains(u, v)
if not contained then
return true
end
end
return false
end
local function print_table(t)
for k, v in pairs(t) do
crawl.mpr(tostring(v))
end
end
local function attack_fire(x,y)
move = 'fr' .. vector_move(x, y) .. 'f'
crawl.process_keys(move)
end
local function attack_reach(x,y)
move = 'vr' .. vector_move(x, y) .. '.'
crawl.process_keys(move)
end
local function attack_melee(x,y)
move = delta_to_vi(x, y)
crawl.process_keys(move)
end
local function set_stop_level(key, value, mode)
AUTOFIGHT_STOP = tonumber(value)
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 hp_is_low()
local hp, mhp = you.hp()
return (100*hp <= AUTOFIGHT_STOP*mhp)
end
function attack(allow_movement)
local x, y, info, count, names = get_target(not allow_movement)
local current_turn = you.turns()
local caught = you.caught()
if current_turn > AUTOFIGHT_LAST+1 then
AUTOFIGHT_LOCK = false
-- AUTOFIGHT_WARNED = false
end
if not AUTOFIGHT_LOCK and table_not_in(names, AUTOFIGHT_NAMES) and current_turn == AUTOFIGHT_LAST+1 then
crawl.mpr("A new challenger appears! Locking autofight. Move manually once to unlock.")
AUTOFIGHT_LOCK = true
end
if AUTOFIGHT_LOCK then
-- if not AUTOFIGHT_WARNED then
-- crawl.mpr("Autofight has been locked. Move manually once to unlock.")
-- AUTOFIGHT_WARNED = true
-- end
return
end
if you.confused() then
crawl.mpr("You are too confused!")
elseif caught then
crawl.mpr("You are " .. caught .. "!")
elseif hp_is_low() then
crawl.mpr("You are too injured to fight blindly!")
elseif info == nil then
crawl.mpr("No target in view!")
elseif 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)
elseif allow_movement then
move_towards(x,y)
else
crawl.mpr("No target in range!")
end
AUTOFIGHT_LAST = current_turn
AUTOFIGHT_COUNT = count
AUTOFIGHT_NAMES = names
end
function hit_closest()
attack(true)
end
function 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_throw = set_af_throw
chk_lua_option.autofight_throw_nomove = set_af_throw_nomove
- For this message the author retchdog has received thanks:
- CommanderC