This file is indexed.

/usr/share/crawl/dat/clua/automagic.lua is in crawl-common 2:0.17.1-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
---------------------------------------------------------------------------
-- automagic.lua:
-- One-key casting.
--
--
-- This uses the "very incomplete client monster and view bindings" from
-- autofight.lua, and "is currently very primitive."
--
-- The behavior with target selection is kind of quirky, especially with
-- short range spells and dangerous monsters out of range.
---------------------------------------------------------------------------

local ATT_HOSTILE = 0
local ATT_NEUTRAL = 1

local LOS_RADIUS = 7

if not AUTOMAGIC_SPELL_SLOT then
  initial_slot = true
  AUTOMAGIC_SPELL_SLOT = "a"
end

AUTOMAGIC_STOP = 0
AUTOMAGIC_FIGHT = false

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 is_safe_square(dx, dy)
    if view.feature_at(dx, dy) == "trap_web" then
        return false
    end
    return view.is_safe_square(dx, dy)
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 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)
  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)

  -- Decide what to do for target's range by squareLOS range
  range = spells.range(you.spell_table()[AUTOMAGIC_SPELL_SLOT])
  if abs(dx) <= range and abs(dy) <= range then
    -- In range
    info.attack_type = 1
  else
    -- Out of range
    info.attack_type = 2
  end
  info.can_attack = (info.attack_type == 1) 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.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

local function compare_monster_info(m1, m2)
  flag_order = automagic_flag_order
  if flag_order == nil then
    flag_order = {"can_attack", "safe", "distance", "constricting_you", "injury", "threat", "orc_priest_wizard"}
  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)
  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()
  local x, y, bestx, besty, best_info, new_info
  bestx = 0
  besty = 0
  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
        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

local function spell_attack(x,y)
  -- There has already been a valid target check to have gotten this far.
  -- Required magic points have also been checked.
  -- Spells that could hurt you (clouds, fireball) will still trigger "are you
  -- sure" so that safeguard is not bypassed.
  move = 'z' .. AUTOMAGIC_SPELL_SLOT .. 'r' .. vector_move(x, y) .. 'f'
  crawl.process_keys(move, true)
end

local function set_stop_level(key, value, mode)
  AUTOMAGIC_STOP = tonumber(value)
end

local function set_fight_behavior(key, value, mode)
  AUTOMAGIC_FIGHT = string.lower(value) ~= "false"
end

local function set_automagic_spell_slot(key, value, mode)
  -- need to check for this to make sure we don't overwrite the saved slot
  if initial_slot then
    AUTOMAGIC_SPELL_SLOT = value
  end
end

local function getkey()
  local key
  while true do
    key = crawl.getch()
    if key == 13 then
      return "null"
    end
    if key == 27 then
      return "escape"
    end
    -- Similar check to libutil.h isaalpha(int c) for valid key
    if (key > 96 and key < 123) or (key > 64 and key < 91) then
      local c = string.char(key)
      return c
    else
      return "invalid"
    end
  end
end

local function mp_is_low()
  local mp, mmp = you.mp()
  return (100*mp <= AUTOMAGIC_STOP*mmp)
end

function mag_attack(allow_movement)
  local x, y, info = get_target()
  if af_hp_is_low() then
    crawl.mpr("You are too injured to fight recklessly!")
  elseif you.confused() then
    crawl.mpr("You are too confused!")
  elseif info == nil then
    crawl.mpr("No target in view!")
  elseif spells.mana_cost(you.spell_table()[AUTOMAGIC_SPELL_SLOT]) > you.mp() then
    -- If you want to resort to melee, set AUTOMAGIC_FIGHT to true in rc
    -- First check for enough magic points, then check if below threshold
    if AUTOMAGIC_FIGHT then
      attack(allow_movement)
    else
      crawl.mpr("You don't have enough magic to cast " ..
          you.spell_table()[AUTOMAGIC_SPELL_SLOT] .. "!")
    end
  elseif mp_is_low() then
    if AUTOMAGIC_FIGHT then
      attack(allow_movement)
    else
      crawl.mpr("You are too depleted to cast spells recklessly!")
    end
  elseif info.attack_type == 1 then
    spell_attack(x,y)
  elseif allow_movement then
    move_towards(x,y)
  else
    crawl.mpr("No target in range!")
  end
end

-- Set this as a macro to change which spell is cast, in game!
function am_set_spell()
  crawl.mpr("Which spell slot to assign to automagic? (Enter to disable, Esc to cancel)", "prompt")
  local slot = getkey()
  crawl.clear_messages()

  if slot == "escape" then
    crawl.mpr("Cancelled.")
    return false

  elseif slot == "null" then
    crawl.mpr("Deactivated automagic.")
    crawl.setopt("automagic_enable = false")
    return false

  elseif slot == "invalid" then
    crawl.mpr("Invalid spell slot.")
    return false

  else
    message = ""
    -- All conditional checks completed, continue to assign a slot now. If automagic
    -- had been disabled, enable it now.
    if AUTOMAGIC_ACTIVE == false then
      crawl.setopt("automagic_enable = true")
      message = " enabled,"
    end
    AUTOMAGIC_SPELL_SLOT = slot
    spell_name = you.spell_table()[AUTOMAGIC_SPELL_SLOT] or "no spell currently"
    crawl.mpr("Automagic" .. message .. " will cast spell in slot " .. slot .. " (" ..
        spell_name .. ")" .. ".")
    return false

  end
end

local function spell_slot_save()
  local res = ""
  if AUTOMAGIC_SPELL_SLOT then
    res = res .. "AUTOMAGIC_SPELL_SLOT = " .. "'" .. AUTOMAGIC_SPELL_SLOT
              .. "'\n"
  end
  return res
end

table.insert(chk_lua_save, spell_slot_save)

chk_lua_option.automagic_stop = set_stop_level
chk_lua_option.automagic_fight = set_fight_behavior
chk_lua_option.automagic_slot = set_automagic_spell_slot