##### Crawl Init file ############################################### # For descriptions of all options, as well as some more in-depth information # on setting them, consult the file # options_guide.txt # in your /docs directory. If you can't find it, the file is also available # online at: # https://github.com/crawl/crawl/blob/master/crawl-ref/docs/options_guide.txt # # Crawl uses the first file of the following list as its option file: # * init.txt in the -rcdir directory (if specified) # * .crawlrc in the -rcdir directory (if specified) # * init.txt (in the Crawl directory) # * ~/.crawl/init.txt (Unix only) # * ~/.crawlrc (Unix only) # * ~/init.txt (Unix only) # * settings/init.txt (in the Crawl directory) ##### Some basic explanation of option syntax ####################### # Lines beginning with '#' are comments. The basic syntax is: # # field = value or field.subfield = value # # Only one specification is allowed per line. # # The terms are typically case-insensitive except in the fairly obvious # cases (the character's name and specifying files or directories when # on a system that has case-sensitive filenames). # # White space is stripped from the beginning and end of the line, as # well as immediately before and after the '='. If the option allows # multiple comma/semicolon-separated terms (such as # autopickup_exceptions), all whitespace around the separator is also # trimmed. All other whitespace is left intact. # # There are three broad types of Crawl options: true/false values (booleans), # arbitrary values, and lists of values. The first two types use only the # simple =, with later options - which includes your options that are different # from the defaults - overriding earlier ones. List options allow using +=, ^=, # -=, and = to append, prepend, remove, and reset, respectively. Usually you will # want to use += to add to a list option. Lastly, there is := which you can use # to create an alias, like so: # ae := autopickup_exceptions # From there on, 'ae' will be treated as if it you typed autopickup_exceptions, # so you can save time typing it. # ##### Other files ################################################### # You can include other files from your options file using the 'include' # option. Crawl will treat it as if you copied the whole text of that file # into your options file in that spot. You can uncomment some of the following # lines by removing the beginning '#' to include some of the other files in # this folder. # Some useful, more advanced options, implemented in LUA. # include = advanced_optioneering.txt # Alternative vi bindings for Dvorak users. # include = dvorak_command_keys.txt # Alternative vi bindings for Colemak users. # include = colemak_command_keys.txt # Alternative vi bindings for Neo users. # include = neo_command_keys.txt # Override the vi movement keys with a non-command. # include = no_vi_command_keys.txt # Turn the shift-vi keys into safe move, instead of run. # include = safe_move_shift.txt ##### Ancient versions ############################################## # If you're used to the interface of ancient versions of Crawl, you may # get back parts of it by uncommenting the following options: # include = 034_command_keys.txt # And to revert monster glyph and colouring changes: # include = 052_monster_glyphs.txt # include = 060_monster_glyphs.txt # include = 071_monster_glyphs.txt # include = 080_monster_glyphs.txt # include = 0.9_monster_glyphs.txt # include = 0.12_monster_glyphs.txt # include = 0.13_monster_glyphs.txt # include = 0.14_monster_glyphs.txt show_more = false # force_spell_targeter -= all simple_targeting = true macros += M 4 ===cast_appropriate_spell macros += M 3 ===maybe_cast_lrd macros += M g za macros += M 2 zdf macros += M 1 zc bindkey = [^D] CMD_LUA_CONSOLE # always_use_static_spell_targeters = true # casting spells # ignition, orbofdestruction, fireball, starburst : LOS = 7 { local monster_array local flamewave_start_turn=0 function initialize_monster_array() monster_array = {} local x for x = -LOS-5,LOS+5 do monster_array[x] = {} end end function update_monster_array() local x,y for x = -LOS,LOS do for y = -LOS,LOS do monster_array[x][y] = monster.get_monster_at(x, y) end end end function spellpower(spellname) local maxPower = 200 if spellname == "foxfire" or spellname == "shock" then maxPower = 25 end if spellname == "flame wave" then maxPower = 100 end if spellname == "stone arrow" or spellname == "scorch" or spellname == "poisonous vapours" then maxPower = 50 end return spells.power_perc(spellname) * maxPower / 100.0 end function can_target(x, y) return you.see_cell_no_trans(x, y) end function can_smite(x, y) return you.see_cell_no_trans(x, y) and not is_wall(x, y) end function valid_enemy(m, x, y) return m and m:attitude() == 0 and you.see_cell_no_trans(x, y) and not m:is_safe() end function valid_enemy_explosion(m, x, y, x2, y2) return m and m:attitude() == 0 and not m:is_safe() and view.cell_see_cell(x, y, x2, y2) end function stay() crawl.process_keys(".") -- .. string.char(27) .. string.char(27) .. string.char(27)) end function maybe_cast_lrd() initialize_monster_array() update_monster_array() local eval = evaluate_lrd() if eval[1] == 0 then return end cast_lrd(eval[3], eval[4]) end -- TODO account for: MP efficiency -- accurate evasion calculations (difficult with the info given) -- accurate armor calculations (difficult with the info given) -- preferring to target close enemies -- instead of raw damage, look at something like (enemy danger level) * (damage dealt) / (enemy remaining HP + enemy max HP) -- so it rewards for hitting dangerous enemies, and rewards more for hitting dangerous enemies that are low on HP -- with a big bonus for an expected kill -- bigger bonus for a more *sure* kill (that can't be evaded) -- higher m:threat() increases danger. But this is a very coarse grained indicator. -- We would like to know the monster hit dice. Perhaps the only way available from clua is to go by monster name. -- being close increases danger -- certain enemy types can be prioritized: floating eyes are always max danger -- shining eyes are super high priority targets as well. -- don't count damage that goes beyond the enemy max HP -- don't use expensive spells to kill weak enemies function cast_appropriate_spell() initialize_monster_array() update_monster_array() local value_table = {} table.insert(value_table, evaluate_fireball()) -- table.insert(value_table, evaluate_ood()) -- targeting on this is a bit iffy, besides you should walk to carefully set up each shot table.insert(value_table, evaluate_starburst()) table.insert(value_table, evaluate_ignition()) table.insert(value_table, evaluate_firestorm()) table.insert(value_table, evaluate_foxfire()) table.insert(value_table, evaluate_stonearrow()) table.insert(value_table, evaluate_ironshot()) table.insert(value_table, evaluate_scorch()) table.insert(value_table, evaluate_airstrike()) table.insert(value_table, evaluate_shock()) table.insert(value_table, evaluate_poisonousvapours()) -- table.insert(value_table, evaluate_lrd()) -- works fine, but disabled by default for noise. You may wish to bind a separate macro key to maybe_cast_lrd() -- table.insert(value_table, evaluate_flamewave()) -- too buggy, can't maintain the wave table.sort(value_table, function (k1, k2) return k1[1] < k2[1] end) best = value_table[#value_table] local i for i = 1,#value_table do bpr(value_table[i][1]) bpr(value_table[i][2]) bpr(value_table[i][3]) bpr(value_table[i][4]) bpr("") end if best[2] == "flame wave" then if you.turns() - flamewave_start_turn < 3 then crawl.mpr("continuing flame wave") stay() return end flamewave_start_turn = you.turns() end if best[2] == "lee's rapid deconstruction" then cast_lrd(best[3], best[4]) return end if best[1] > 0 then spells.cast(best[2], best[3], best[4]) end end function resisted_damage(dmg, res) if res == 0 then return dmg end if res == -1 then return dmg*1.5 end if res == 1 then return dmg * 0.5 end if res == 2 then return 0 -- don't even bother -- return dmg * 0.2 end if res == 3 then return 0 end return 0 end -- a friendly monster that isn't you function friendly_at(x, y) local m = monster_array[x][y] if m and m:attitude() > 0 then return true -- neutral or friendly monster end return false end function fireball_damage_at_target(x, y, dmgPerHit, avoidPlayer) local totDmg = 0 local x2, y2 for x2 = -1, 1 do for y2 = -1, 1 do local m = monster_array[x+x2][y+y2] if valid_enemy_explosion(m, x+x2, y+y2, x, y) then local dmg = resisted_damage(dmgPerHit, m:res_fire()) totDmg = totDmg + dmg elseif not you.see_cell(x+x2, y+y2) then totDmg = totDmg + 0.1 -- just slightly prefer to hit out of sight tiles, in case they have enemies elseif avoidPlayer and x+x2 == 0 and y+y2 == 0 then return 0 -- don't hit yourself elseif friendly_at(x+x2, y+y2) then return 0 end end end return totDmg end function castable(spellname) if not spells.memorised(spellname) then return false end if spells.fail(spellname) > 14 then return false end if you.mp() < spells.level(spellname) then return false end if spells.fail_severity(spellname) > 2 then return false end return true end function evaluate_fireball() if not castable("fireball") then return {0, "fireball", 0, 0} end local bestX = 0 local bestY = 0 local bestDmg = 0 -- 3d(3.33+Power/6) local dmgPerHit = 3 * math.floor(3.33 + spellpower("fireball") / 6 + 1) * 0.5 crawl.mpr("fireball ") crawl.mpr(tostring(dmgPerHit)) crawl.mpr(spellpower("fireball")) dmgPerHit = dmgPerHit * (1 - spells.fail("fireball")/100.0) local range = spells.max_range("fireball") local x, y for x = -range,range do for y = -range,range do if can_target(x, y) then local path = spells.path("fireball",x,y) local x2 = path[#path][1] local y2 = path[#path][2] local dmg = fireball_damage_at_target(x2, y2, dmgPerHit, true) if dmg > bestDmg then bestDmg = dmg bestX = x bestY = y end end end end return {bestDmg, "fireball", bestX, bestY} end function evasion_check(monsterEv, spellToHit) return 0.8 -- todo. also todo: dmsl end function evaluate_starburst() if not castable("starburst") then return {0, "starburst", 0, 0} end local totDmg = 0 -- 6d(3+Power/9) local dmgPerHit = 6 * 0.5 * math.floor(1+3+spellpower("starburst")/9) crawl.mpr("starburst ") crawl.mpr(tostring(dmgPerHit)) crawl.mpr(spellpower("starburst")) dmgPerHit = dmgPerHit * (1 - spells.fail("starburst")/100.0) local range = spells.max_range("starburst") local ranges = {} local x, y for x=-1,1 do ranges[x] = {} for y=-1,1 do ranges[x][y] = range end end for R = 1,LOS do for x=-1,1 do for y=-1,1 do if x ~= 0 or y ~= 0 then local m = monster_array[R*x][R*y] if m and ranges[x][y] >= R then local dmg = resisted_damage(dmgPerHit, m:res_fire()) dmg = dmg * evasion_check(m:ev(), spellpower("starburst")) totDmg = totDmg + dmg ranges[x][y] = ranges[x][y] - 1 end end end end end return {totDmg, "starburst", 0, 0} end function is_wall(x, y) return travel.feature_solid(view.feature_at(x, y)) end -- TODO track whether the target already has an OOD en route that is likely to kill them, and don't shoot a second function ood_damage_at_target(x, y) -- 9d([60 + Power]/12) local baseDamage = 9 * 0.5 * math.floor((60 + spellpower("orb of destruction")) / 12) crawl.mpr("ood ") crawl.mpr(tostring(baseDamage)) crawl.mpr(spellpower("orb of destruction")) baseDamage = baseDamage * (1 - spells.fail("orb of destruction")/100.0) local x2, y2 local xstart, ystart local xend, yend xstart = 1 ystart = 1 xend = x yend = y if x < 0 then xstart = x xend = -1 end if y < 0 then ystart = y yend = -1 end if x == 0 then xstart = 0 end if y == 0 then ystart = 0 end for x2=xstart,xend do for y2=ystart,yend do if is_wall(x2, y2) or ((x2 ~= x or y2 ~= y) and monster_array[x2][y2]) then return 0 end end end if x == 0 then for x2=-1,1 do for y2=ystart,yend do if is_wall(x2, y2) or ((x2 ~= x or y2 ~= y) and monster_array[x2][y2]) then baseDamage = baseDamage * 0.5 end end end end if math.abs(x) == 1 and math.abs(y) == 1 then return baseDamage * 0.3 end if math.abs(x) <= 3 and math.abs(y) <= 3 then return baseDamage * 0.6 end if math.abs(x) <= 4 and math.abs(y) <= 4 then return baseDamage * 0.9 end return baseDamage end function evaluate_ood() if not castable("orb of destruction") then return {0, "orb of destruction", 0, 0} end local bestDmg=0 local bestX=0 local bestY=0 local x, y for x=-LOS,LOS do for y=-LOS,LOS do local m = monster_array[x][y] -- todo monster shields if m and m:attitude() == 0 and you.see_cell_solid_see(x, y) then local dmg = ood_damage_at_target(x, y) if dmg > bestDmg then bestX = x bestY = y bestDmg = dmg end end end end return {bestDmg, "orb of destruction", bestX, bestY} end function evaluate_ignition() if not castable("ignition") then return {0, "ignition", 0, 0} end local totDmg=0 -- 3d(3.33+Power/9) local dmgPerHit =0.5 * 3 * math.floor(3.33 + spellpower("ignition") / 9) dmgPerHit = dmgPerHit * 0.5 -- ignition damage is worth less because it is more spread out dmgPerHit = dmgPerHit * (1 - spells.fail("ignition")/100.0) local x, y local numEnemies=0 for x=-LOS,LOS do for y=-LOS,LOS do local m = monster_array[x][y] if valid_enemy(m, x, y) then if m:res_fire() <= 0 then numEnemies = numEnemies + 1 elseif m:res_fire() == 1 then numEnemies = numEnemies + 0.5 end local dmg = fireball_damage_at_target(x, y, dmgPerHit, false) totDmg = totDmg + dmg end end end if numEnemies < 2 then return {0, "ignition", 0, 0} -- don't bother igniting just 1 enemy end return {totDmg, "ignition", 0, 0} end function firestorm_damage_at_target(x, y, dmgPerHit) local totDmg = 0 local x2, y2 for x2 = -3, 3 do for y2 = -3, 3 do local m = monster_array[x+x2][y+y2] if valid_enemy_explosion(m, x+x2, y+y2, x, y) and view.cell_see_cell(x, y, x+x2, y+y2) then local dmg = resisted_damage(dmgPerHit*0.55, m:res_fire()) + dmgPerHit * 0.45 if math.abs(x2) == 3 or math.abs(y2) == 3 then dmg = dmg * 0.5 -- on fringe of aoe: assume 50% chance of hitting end totDmg = totDmg + dmg elseif math.abs(y2) <= 2 and math.abs(x2) <= 2 and not you.see_cell(x+x2, y+y2) and (view.cell_see_cell(x, y, x+x2, y+y2) or math.abs(x+x2) > LOS or math.abs(y+y2) > LOS) then totDmg = totDmg + 0.1 -- just slightly prefer to hit cells out of sight elseif math.abs(y2) <= 2 and math.abs(x2) <= 2 and is_wall(x+x2, y+y2) then totDmg = totDmg - 0.01 -- all other things equal, prefer to hit an open space end end end return totDmg end function evaluate_firestorm() if not castable("fire storm") then return {0, "fire storm uncastable", 0, 0} end local bestX = 0 local bestY = 0 local bestDmg = 0 -- 8d(0.625+Power/8) local dmgPerHit = 8 * 0.5 * math.floor(0.625 + spellpower("fire storm") / 8 + 1) dmgPerHit = dmgPerHit * (1 - spells.fail("fire storm")/100.0) local range = spells.max_range("fire storm") local x, y for x = -range,range do for y = -range,range do if (math.abs(x) > 3 or math.abs(y) > 3) and can_smite(x, y) then local dmg = firestorm_damage_at_target(x, y, dmgPerHit) -- bpr("firestorm at " .. tostring(x) .. tostring(y) .. " dmg" .. tostring(dmg)) if dmg > bestDmg then bestDmg = dmg bestX = x bestY = y end end end end return {bestDmg, "fire storm", bestX, bestY} end function evaluate_foxfire() if not castable("foxfire") then return {0, "foxfire", 0, 0} end -- 2 * 1d(4+ Power/5) local dmgPerHit = 0.5 * 2 * math.floor(4 + spellpower("foxfire")/5.0) dmgPerHit = dmgPerHit * (1 - spells.fail("foxfire")/100.0) local freeCount = 0 for x=-1,1 do for y=-1,1 do if (x ~= 0 or y ~= 0) and not is_wall(x, y) and not monster_array[x][y] then freeCount = freeCount + 1 end end end if freeCount == 0 then return {0, "foxfire", 0, 0} end if freeCount == 1 then return {dmgPerHit / 4.0, "foxfire", 0, 0} end if freeCount == 2 then return {dmgPerHit / 2.0, "foxfire", 0, 0} end return {dmgPerHit, "foxfire", 0, 0} end function evaluate_flamewave() if not castable("flame wave") then return {0, "flame wave", 0, 0} end -- 2d(4.5+Power/8) local dmgPerHit = 0.5 * 2 * math.floor(4.5 + spellpower("flame wave") / 8.0) dmgPerHit = dmgPerHit * (1 - spells.fail("flame wave")/100.0) local totDmg = 0 local flamewave_turns = you.turns() - flamewave_start_turn for x=-3,3 do for y=-3,3 do if valid_enemy(monster_array[x][y], x, y) then if math.max(math.abs(x), math.abs(y)) == 1 then totDmg = totDmg + dmgPerHit end if math.max(math.abs(x), math.abs(y)) == 2 then if flamewave_turns > 2 then totDmg = totDmg + 0.75 * dmgPerHit else totDmg = totDmg + dmgPerHit end end if math.max(math.abs(x), math.abs(y)) == 3 then if flamewave_turns > 2 then totDmg = totDmg + 0.5 * dmgPerHit elseif flamewave_turns > 1 then totDmg = totDmg + dmgPerHit else totDmg = totDmg + 0.75 * dmgPerHit end end end end end return {totDmg, "flame wave", 0, 0} end function evaluate_arrow(spellname, dmgPerHit) if not castable(spellname) then return {0, spellname, 0, 0} end dmgPerHit = dmgPerHit * (1 - spells.fail(spellname)/100.0) local range = spells.max_range(spellname) local x, y local bestDmg = 0 local bestX = 0 local bestY = 0 for x = -range,range do for y = -range,range do if can_target(x, y) then local path = spells.path(spellname,x,y) local cell local totDmg = 0 local reachChance = 1.0 for cell = 1,#path do local x2, y2 x2 = path[cell][1] y2 = path[cell][2] local m = monster_array[x2][y2] if valid_enemy(m, x2, y2) and totDmg ~= -1 then local evChance = evasion_check(m:ev(), spellpower(spellname)) -- TODO local dmg = dmgPerHit * reachChance * evChance reachChance = reachChance * evChance totDmg = totDmg + dmg elseif m then -- non-enemy monster in the way totDmg = -1 end end if totDmg > bestDmg then bestDmg = totDmg bestX = x bestY = y end end end end return {bestDmg, spellname, bestX, bestY} end function evaluate_stonearrow() -- 3d(7+power/8) local dmgPerHit = 0.5 * 3 * (8 + spellpower("stone arrow")/8.0) return evaluate_arrow("stone arrow", dmgPerHit) end function evaluate_ironshot() -- 9d(1.66+Power/12) local dmgPerHit = 0.5 * 9 * (2.66 + spellpower("iron shot")/12.0) return evaluate_arrow("iron shot", dmgPerHit) end -- returns number of dice and explosion radius. see spl-damage.cc function lrd_dice_terrain(feature) feature = string.lower(feature) if string.find(feature, "stair") then return {0, 0} end local featuresRock = {"stone", "rock", "door", "slimy_wall", "petrified", "statue"} local featuresMetal = {"metal", "iron"} local featuresCrystal = {"crystal"} local i for i = 1,#featuresRock do if string.find(feature, featuresRock[i]) then return {3, 1} end end for i = 1,#featuresMetal do if string.find(feature, featuresMetal[i]) then return {4, 1} end end for i = 1,#featuresCrystal do if string.find(feature, featuresCrystal[i]) then return {4, 2} end end return {0, 0} end -- number of dice and radius. see mon-data.cc and spl-damage.cc function lrd_dice_monster(monster) monster = string.lower(monster) if string.find(monster, "troll") then return {0, 0} end local monstersMetal = {"iron", "peacekeeper", "war gargoyle"} local monstersRock = {"toenail", "earth elemental", "saltling", "ushab", "statue", "gargoyle", "skelet", "bone", "simula", "ice beast"} local monstersCrystal = {"crystal", "obsidian", "roxanne"} -- check metal first because of war gargoyle/gargoyle for i = 1,#monstersMetal do if string.find(monster, monstersMetal[i]) then return {4, 1} end end for i = 1,#monstersRock do if string.find(monster, monstersRock[i]) then return {3, 1} end end for i = 1,#monstersCrystal do if string.find(monster, monstersCrystal[i]) then return {4, 2} end end return {0, 0} end function lrd_damage_at_target(x, y, dmgPerDice, dice, radius, fromMonster) local x2, y2 local totDmg = 0 for x2 = -radius,radius do for y2 = -radius,radius do local m = monster_array[x+x2][y+y2] if valid_enemy(m, x+x2, y+y2) then local ac = m:ac() if ac == 0 then ac = 1 end if x2 == 0 and y2 == 0 and fromMonster then totDmg = totDmg + dmgPerDice * dice else -- this is in no way an accurate damage reduction calculation totDmg = totDmg + dmgPerDice * dice / ac end elseif friendly_at(x+x2, y+y2) then return 0 -- don't hit neutrals, even unfriendly ones. elseif x+x2 == 0 and y+y2 == 0 then return 0 -- don't hit the player end end end return totDmg end function evaluate_lrd() if not castable("lee's rapid deconstruction") then return {0, "lee's rapid deconstruction", 0, 0} end local dmgPerDice = math.floor(5 + spellpower("lee's rapid deconstruction")/5.0) dmgPerDice = dmgPerDice * (1 - spells.fail("lee's rapid deconstruction")/100.0) local bestX = 0 local bestY = 0 local bestDmg = 0 local range = spells.max_range("lee's rapid deconstruction") local x, y for x = -range,range do for y = -range,range do if you.see_cell_solid_see(x, y) then local feature = view.feature_at(x, y) local lrdDice = {0, 0} local m = monster_array[x][y] local fromMonster = false if m then lrdDice = lrd_dice_monster(m:name()) end if lrdDice[1] == 0 then lrdDice = lrd_dice_terrain(feature) else fromMonster = true end if lrdDice[1] ~= 0 then local dmg = lrd_damage_at_target(x, y, dmgPerDice, lrdDice[1], lrdDice[2], fromMonster) if dmg > bestDmg then bestDmg = dmg bestX = x bestY = y end end end end end return {bestDmg, "lee's rapid deconstruction", bestX, bestY} end -- for some reason lrd can't be cast with spells.cast -- so this is a workaround function cast_lrd(x, y) local cmdStr = "Z" .. spells.letter("lee's rapid deconstruction") .. "r" local x2, y2 if x < 0 then for x2 = x,-1 do cmdStr = cmdStr .. "h" end elseif x > 0 then for x2 = 1,x do cmdStr = cmdStr .. "l" end end if y < 0 then for y2 = y,-1 do cmdStr = cmdStr .. "k" end elseif y > 0 then for y2 = 1,y do cmdStr = cmdStr .. "j" end end cmdStr = cmdStr .. "." crawl.process_keys(cmdStr) end function evaluate_scorch() if not castable("scorch") then return {0, "scorch", 0, 0} end -- 2d(5+Power/10) local dmgPerHit = 2 * 0.5 * math.floor(6 + spellpower("scorch")/10.0) local range = spells.range("scorch") local x, y local totDmg = 0 local numEnemies = 0 for x=-range,range do for y=-range,range do local m = monster_array[x][y] if valid_enemy(m, x, y) then totDmg = totDmg + resisted_damage(dmgPerHit, m:res_fire()) numEnemies = numEnemies + 1 end end end if numEnemies == 0 then return {0, "scorch", 0, 0} end -- too many enemies in range isn't good because it hits randomly if numEnemies > 2 then return {0.5 * totDmg / numEnemies, "scorch", 0, 0} end return {totDmg / numEnemies, "scorch", 0, 0} end function count_empty_spaces(x, y) local x2, y2 local emptyCount = 0 for x2=-1,1 do for y2=-1,1 do if not is_wall(x+x2, y+y2) and not monster_array[x+x2][y+y2] then emptyCount = emptyCount + 1 end end end return emptyCount end function evaluate_airstrike() if not castable("airstrike") then return {0, "airstrike", 0, 0} end -- 5 + m + [2 + (power) / 7] local baseDmg = 5 + math.floor(3 + spellpower("airstrike") / 7) local bestX=0 local bestY=0 local bestDmg = 0 local x, y for x=-LOS,LOS do for y=-LOS,LOS do local m = monster_array[x][y] if valid_enemy(m, x, y) and can_smite(x, y) then local dmg = baseDmg + math.max(3, count_empty_spaces(x, y)) if dmg > bestDmg then bestDmg = dmg bestX = x bestY = y end end end end return {bestDmg, "airstrike", bestX, bestY} end -- TODO account for bounces function evaluate_shock() if not castable("shock") then return {0, "shock", 0, 0} end -- 1d(3+power/4) local dmgPerHit = 0.5 * math.floor(4 + spellpower("shock") / 4) local range = spells.max_range("shock") local x, y local bestDmg = 0 local bestX = 0 local bestY = 0 for x = -range,range do for y = -range,range do if can_target(x, y) then local path = spells.path("shock",x,y) local cell local totDmg = 0 for cell = 1,#path do local x2, y2 x2 = path[cell][1] y2 = path[cell][2] local m = monster_array[x2][y2] if valid_enemy(m, x2, y2) and totDmg ~= -1 then local evChance = evasion_check(m:ev(), spellpower("shock")) -- TODO local dmg = dmgPerHit * evChance dmg = resisted_damage(dmg, m:res_shock()) totDmg = totDmg + dmg elseif friendly_at(x2, y2) then -- friend monster in the way totDmg = -1 end end if totDmg > bestDmg then bestDmg = totDmg bestX = x bestY = y end end end end return {bestDmg, "shock", bestX, bestY} end function evaluate_poisonousvapours() if not castable("poisonous vapours") then return {0, "poisonous vapours", 0, 0} end -- there isn't a specific damage formula for poisonous vapours -- we'll begin by assuming it is similar in value to scorch -- (below is the damage formula for scorch) local dmgPerHit = 2 * 0.5 * math.floor(6 + spellpower("poisonous vapours")/10.0) -- targeting: we only want to target non-poison-resistant creatures -- that are not already maximally poisoned -- giving priority to the monster that is currently the most poisoned -- and breaking ties going to the most dangerous monster -- status() can be "poisoned", "very poisoned", "extremely poisoned" local bestDmg = 0 local bestX, bestY local x, y local range = spells.max_range("poisonous vapours") for x=-range,range do for y=-range,range do local m = monster_array[x][y] if m and m:res_poison() <= 0 and not m:status("extremely poisoned") then local dmg = dmgPerHit if m:threat() < 2 then dmg = dmg * 0.5 elseif m:threat() > 3 then dmg = dmg * 1.5 end if m:status("very poisoned") then dmg = dmg * 1.1 elseif m:status("poisoned") then dmg = dmg * 1.05 end if dmg > bestDmg then bestDmg = dmg bestX = x bestY = y end end end end return {bestDmg, "poisonous vapours", bestX, bestY} end function bpr(message) crawl.mpr(tostring(message)) end }