##### Yermak's Crawl Init file ############################################### note_skill_levels = 1,3,6,9,12,15,18,21,24,27 dump_message_count = 50 dump_book_spells = false { local need_skills_opened = true function ready() if you.turns() == 0 and need_skills_opened then need_skills_opened = false crawl.sendkeys("m") end if crawl.messages(5):find("You enter a gauntlet") and (you.god()=="Lugonu" or you.god()=="Fedhas" or you.god()=="Sif Muna") then crawl.mpr("Break the walls!") end fmore_early_threats() end local fmet = true function fmore_early_threats() if you.xl() > 13 and fmet then crawl.setopt("force_more_message -= centaur.* comes? into view") crawl.setopt("force_more_message -= hydra.* comes? into view") crawl.setopt("force_more_message -= killer bee.* comes? into view") crawl.setopt("force_more_message -= electric eel.* comes? into view") fmet = false end end local aft = false function toggle_autothrow() if aft then crawl.setopt("use_animations += beam, monster") crawl.setopt("autofight_throw = false") crawl.mpr("Autofight_throw is off.") else crawl.setopt("use_animations -= beam, monster") crawl.setopt("autofight_throw = true") crawl.mpr("Autofight_throw is on.") end aft = not aft end local cheiwalk = false function toggle_cheiwalk() if cheiwalk then crawl.setopt("force_more_message -= comes? into view") crawl.mpr("Cheiwalk mode is off.") else crawl.setopt("force_more_message += comes? into view") crawl.mpr("Cheiwalk mode is on.") end cheiwalk = not cheiwalk end local mmores = false function toggle_more_mores() if mmores then crawl.setopt("force_more_message -= Found") crawl.mpr("Less mores.") else crawl.setopt("force_more_message += Found") crawl.mpr("More mores.") end mmores = not mmores end local portalmode = false function toggle_portal_mode() if portalmode then crawl.setopt("show_game_time = false") crawl.mpr("Portal mode is off.") else crawl.setopt("show_game_time = true") crawl.mpr("Portal mode is on.") end portalmode = not portalmode end local autorestmode = true function toggle_autorest() if autorestmode then crawl.setopt("explore_auto_rest = false") crawl.mpr("Autorest mode is off.") else crawl.setopt("explore_auto_rest = true") crawl.mpr("Autorest mode is on.") end autorestmode = not autorestmode end local function autopickup(it, name) local class = it.class(true) local weap = items.equipped_at("Weapon") local shie = items.equipped_at("Shield") if it.is_useless then return false end if class == "armour" then local aux_slots = {cloak="Cloak", helmet="Helmet", gloves="Gloves", boots="Boots", shield="Shield"} st, _ = it.subtype() if aux_slots[st] == "Shield" then if (weap == nil or weap.hands == 1) and (shie == nil or it.branded) then return true end elseif aux_slots[st] ~= nil and items.equipped_at(aux_slots[st]) == nil then return true elseif st ~= "body" and it.branded then return true end end return nil end add_autopickup_func(autopickup) } ### Macros ### macros += M \{-274} ===toggle_cheiwalk ### macros += M \{-273} ===toggle_autothrow ### macros += M \{-272} ===toggle_autorest ### EN0N's Mini-Map Color Scheme tile_upstairs_col = green tile_downstairs_col = red tile_branchstairs_col = #ffa500 tile_door_col = #c27149 tile_wall_col = #5a524c tile_explore_horizon_col = #aaaaaa tile_floor_col = #1e1b1a tile_item_col = #1e1b1a tile_feature_col = #d4be21 tile_plant_col = #4b6d39 tile_water_col = #0b5d79 tile_deep_water_col = #1212b3 tile_trap_col = #f447ff tile_transporter_col = #ff5656 tile_transporter_landing_col = #59ff89 tile_lava_col = #5f0a00 autoinscribe ^= scroll of silence:!r #: if you.skill("Staves") < 5 then # autoinscribe += magical staff:!a #: end ae := autopickup_exceptions # nullifying the effect of the most annoying 0.24 commit: ae += >ring of (poi|resist cor|fli|see) ae += >amulet of (the acr|fai|the gou|gua|har|mag|rag|ref|reg) ae += >scrolls? of (amn|noise) ae += >wand of rand ae += >ring of (protection from (mag|fire|cold)|mag|ste|ice|fire|pos|wil|wiz) ae += >ring of (dex|int|str) ae ^= staff of confirm_action += Death's Door ### Squelch stupid prompts { function c_answer_prompt(prompt) if prompt == "Annotate level on other end of current stairs?" then return false end end } { function c_answer_prompt(prompt) if prompt == "Are you sure you want to leave the Dungeon? This will make you lose the game!" then return false end end } ### Species, Job, God conditions : if you.race() == "Ghoul" then auto_eat_chunks = false auto_butcher = full : elseif you.race() == "Felid" or you.race() == "Troll" or you.race() == "Kobold" then auto_butcher = satiated : else auto_eat_chunks = true auto_butcher = hungry : end : if true and (you.race() == "Spriggan" or you.race() == "Gnoll") then default_autopickup = false : else default_autopickup = true : end #: if you.race() == "Ghoul" or you.race() == "Felid" or you.race() == "Troll" or you.race() == "Kobold" then easy_eat_chunks = true #: end : if you.race() == "Ghoul" or you.race() == "Mummy" then ae += scrolls? of torment : end : if you.race() == "Tengu" or you.race() == "Merfolk" or you.race() == "Barachi" or you.race() == "Octopode" then ae += >potions? of flight : end : if you.race() == "Vampire" or you.race() == "Mummy" or you.race() == "Ghoul" or you.race() == "Demonspawn" then ae += >scrolls? of holy word : end : if you.race() == "Ogre" or you.race() == "Troll" then ae += potions? of brilliance ae += >potions? of berserk ae += >magical staff : end : if you.god() == "Zin" then ae ^= potions? of ambro : end : if you.god() == "Cheibriados"then default_autopickup = false force_more_message += comes? into view : end : if you.race() == "Barachi" then force_more_message += comes? into view : end : if you.god() == "Fedhas" then autoinscribe += fruit:!e : end : if false then ae += >potions? of brilliance ae += >book ae += >ring of int skill_focus = true : end force_more_message += comes? into view #force_more_message += as you move away #force_more_message += Found a stone staircase #force_more_message += Found #force_more_message += paralyse.*in retribution show_more = false #autofight_throw = true ## ## CONFIG.lua ################################################################################################ { -- Global CONFIG for customizing RC behaviors CONFIG = { } CONFIG.emojis = true } ## ## BEGIN ################################################################################################ : rc_msg("Initializing initializr.rc ...") : crawl.enable_more(false) ##### 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 ## Globals/Globals.rc ################################################################################################ # PAUSE variable for ForceMorePrompts.rc $PAUSE_MORE := PAUSE ## ## Globals/Globals.lua ################################################################################################ { -- Pause for AnnounceDamage.lua PAUSE_MORE = "PAUSE" -- Standard Colors (ignoring black, not visible) COLORS = { } COLORS.darkgrey = "darkgrey" COLORS.lightgrey = "lightgrey" COLORS.white = "white" COLORS.blue = "blue" COLORS.lightblue = "lightblue" COLORS.green = "green" COLORS.lightgreen = "lightgreen" COLORS.cyan = "cyan" COLORS.lightcyan = "lightcyan" COLORS.red = "red" COLORS.lightred = "lightred" COLORS.magenta = "magenta" COLORS.lightmagenta = "lightmagenta" COLORS.yellow = "yellow" COLORS.brown = "brown" } ## ## Utils.lua ################################################################################################ { function withColor(color, str) return string.format("<%s>%s", color, str, color) end function rc_out(symbol, color, msg) crawl.mpr(string.format("%s <%s>%s", symbol, color, msg, color)) end function rc_msg(msg) rc_out("🤖", "blue", msg) end function rc_scs(msg) rc_out("✅", "green", msg) end function rc_err(msg) rc_out("❌", "lightred", msg) end function table_has(table, match) for index, value in ipairs(table) do if value == match then return true end end return false end } ## ## Lua Setup ################################################################################################ ## ## AnnounceDamage.lua ################################################################################################ { local Messages = { ["HPSimple"] = function(delta) return withColor(COLORS.white, string.format("HP[%s]", delta_color(0 - delta)) ) end, ["HPMax"] = function (color, hp, hpm, delta) crawl.mpr( withColor(COLORS.lightgreen, string.format("You now have %s max hp (%s).", hpm, delta_color(delta)) ) ) end, ["HPLoss"] = function (color, hp, hpm, loss) crawl.mpr( string.format("%s%s", withColor(COLORS.red, string.format("You take %s damage,", loss)), withColor(color, string.format(" and now have %s/%s hp.", hp, hpm)) ) ) end, ["HPGain"] = function (color, hp, hpm, gain) crawl.mpr( string.format("%s%s", withColor(COLORS.lightgreen, string.format("You regained %s hp,", gain)), withColor(color, string.format(" and now have %s/%s hp.", hp, hpm)) ) ) end, ["HPFull"] = function (color, hp) crawl.mpr( withColor(COLORS.lightgreen, string.format("Your hp is fully restored (%s).", hp) ) ) end, ["HPMassivePause"] = function () crawl.mpr( withColor(COLORS.lightred, string.format("MASSIVE DAMAGE!! (%s)", PAUSE_MORE) ) ) end, ["MPSimple"] = function(delta) return withColor(COLORS.white, string.format("MP[%s]", delta_color(0 - delta)) ) end, ["MPLoss"] = function (color, mp, mpm, loss) crawl.mpr( string.format("%s%s", withColor(COLORS.cyan, string.format("You lost %s mp,", loss)), withColor(color, string.format(" and now have %s/%s mp.", mp, mpm)) ) ) end, ["MPGain"] = function (color, mp, mpm, gain) crawl.mpr( string.format("%s%s", withColor(COLORS.cyan, string.format("You regained %s mp,", gain)), withColor(color, string.format(" and now have %s/%s mp.", mp, mpm)) ) ) end, ["MPFull"] = function (color, mp) crawl.mpr( withColor(COLORS.cyan, string.format("Your mp is fully restored (%s).", mp)) ) end, [""]=""} local prev_hp = 0 local prev_hp_max = 0 local prev_mp = 0 local prev_mp_max = 0 function delta_color(delta) local color = delta < 0 and COLORS.red or COLORS.green local signDelta = delta < 0 and delta or "+"..delta return string.format("<%s>%s", color, signDelta, color) end -- Simplified condensed HP and MP output -- Print a single condensed line showing HP & MP change -- e.g.😨 HP[-2] MP[-1] function simple_announce_damage(curr_hp, max_hp, hp_diff, mp_diff) local emoji = "" local message = nil -- MP[-1] if hp_diff == 0 and mp_diff ~= 0 then message = Messages.MPSimple(mp_diff) -- HP[-2] elseif hp_diff ~= 0 and mp_diff == 0 then message = Messages.HPSimple(hp_diff) -- HP[-2] MP[-1] elseif hp_diff ~= 0 and mp_diff ~= 0 then message = string.format("%s %s", Messages.HPSimple(hp_diff), Messages.MPSimple(mp_diff)) else -- No changes end if message ~= nil then if curr_hp <= (max_hp * 0.25) then emoji = "😱" elseif curr_hp <= (max_hp * 0.50) then emoji = "😨" elseif curr_hp <= (max_hp * 0.75) then emoji = "😮" elseif curr_hp < max_hp then emoji = "😕" else emoji = "😎" end if CONFIG.emojis then crawl.mpr(string.format("\n%s %s", emoji, message)) else crawl.mpr(string.format("\n%s", message)) end end end -- Try to sync with colors defined in Interface.rc function color_by_max(message_func, curr, max, diff) if curr <= (max * 0.25) then message_func(COLORS.red, curr, max, diff) elseif curr <= (max * 0.50) then message_func(COLORS.lightred, curr, max, diff) elseif curr <= (max * 0.75) then message_func(COLORS.yellow, curr, max, diff) else message_func(COLORS.lightgrey, curr, max, diff) end end function announce_damage() -- TODO Define Colors.Red, Colors.Green, etc. -- TODO Move current/previous into array pair -- Save previous as last_hp -- Shift current into previous -- Early return if last_hp was == 0 local curr_hp, max_hp = you.hp() local curr_mp, max_mp = you.mp() --Skips message on initializing game if prev_hp > 0 then local hp_diff = prev_hp - curr_hp local max_hp_diff = max_hp - prev_hp_max local mp_diff = prev_mp - curr_mp local max_mp_diff = max_mp - prev_mp_max -- Simplified condensed HP and MP output simple_announce_damage(curr_hp, max_hp, hp_diff, mp_diff) -- HP Max if max_hp_diff > 0 then Messages.HPMax(COLORS.green, curr_hp, max_hp, max_hp_diff) elseif max_hp_diff < 0 then Messages.HPMax(COLORS.yellow, curr_hp, max_hp, max_hp_diff) end -- HP Loss -- Ensure we lost MORE than the change in max hp -- i.e. a change in max hp should not be considered damage if (hp_diff > 0 and hp_diff > math.abs(max_hp_diff)) then color_by_max(Messages.HPLoss, curr_hp, max_hp, hp_diff) if hp_diff > (max_hp * 0.20) then Messages.HPMassivePause() end end -- HP Gain -- More than 1 HP gained if (hp_diff < 0) then -- Remove the negative sign by taking absolute value local hp_gain = math.abs(hp_diff) if (hp_gain > 1) and not (curr_hp == max_hp) then color_by_max(Messages.HPGain, curr_hp, max_hp, hp_gain) end if (curr_hp == max_hp) then Messages.HPFull(nil, curr_hp) end end -- MP Gain -- More than 1 MP gained if (mp_diff < 0) then -- Remove the negative sign by taking absolute value local mp_gain = math.abs(mp_diff) if (mp_gain > 1) and not (curr_mp == max_mp) then color_by_max(Messages.MPGain, curr_mp, max_mp, mp_gain) end if (curr_mp == max_mp) then Messages.MPFull(nil, curr_mp) end end -- MP Loss -- Ensure we lost MORE than the change in max mp -- i.e. a change in max mp should not be considered loss if (mp_diff > 0 and mp_diff > math.abs(max_mp_diff)) then color_by_max(Messages.MPLoss, curr_mp, max_mp, mp_diff) end end --Set previous hp/mp and form at end of turn prev_hp = curr_hp prev_hp_max = max_hp prev_mp = curr_mp prev_mp_max = max_mp end } ## ## OpenSkills.lua ################################################################################################ { -- Open skills menu at start of runs local is_gnoll = table_has({"Gnoll"}, you.race()) local need_skills_opened = not is_gnoll local function start_open_skills() if you.turns() == 0 and need_skills_opened then need_skills_opened = false crawl.sendkeys("m") end end -- Runs once when parsed during rc init start_open_skills() } ################################################################################################ autofight_stop = 75 autofight_caught = true # Prevent travel from routing through deep water. # By default, this option is commented out. For merfolk and/or characters with # permanent levitation, this will prevent travel or explore from going through any water # travel_avoid_terrain = deep water # Wait until your HP and MP are both at rest_wait_percent before moving rest_wait_percent = 100 explore_auto_rest = true # Set to -1 for instant-travel, set to 1 to see travel paths travel_delay = -1 explore_delay = 1 # Adjusts how much autoexplore favours attempting to discover room perimeters and corners. # At higher values, autoexplore will more heavily favour visiting squares that are next to walls; # at 0 it will not favour them at all. explore_wall_bias = 0 # auto explore stop defaults explore_stop = stairs,shops,altars,portals,branches,runed_doors,runes explore_stop += artefacts,glowing_items,greedy_pickup_smart,greedy_visited_item_stack ## ## Interface.rc ################################################################################################ # hp and mp bar coloring view_delay = 300 hp_colour = 100:lightgreen, 99:lightgray, 75:yellow, 50:lightred, 25:red mp_colour = 100:cyan, 99:lightgray, 75:yellow, 50:lightred, 25:red hp_warning = 50 # new characters start with manual skill (instead of automatic) default_manual_training = true # Unequip already equipped items by selecting in equip menus (i.e. w, W, P) equip_unequip = true # Cannot target self with risky magic allow_self_target = no # Zot count status # If you spend the indicated number of turns in this branch without descending to # a new floor, Zot will find and consume you. # always_show_zot determines whether to show the "Zot" status light at all times # even when you've got plenty of time left. defaults to `false` always_show_zot = false # Monster colors ########################### monster_list_colour = monster_list_colour += friendly:green,neutral:brown monster_list_colour += good_neutral:brown monster_list_colour += trivial:darkgrey,easy:lightgrey monster_list_colour += tough:yellow,nasty:lightred # Tiles ########################### # mini map size, [X] pixels per tile tile_map_pixels = 16 # scale factor when map mode (X) tile_map_scale = 0.50 # size of tiles (default is 32) tile_cell_pixels = 32 # These options allow configuring the colours used for the minimap of the dungeon # level. Using RGB hex codes is also allowed, such as #00ff00 for green. # tile_player_col - colour of player position, as well as of # map centre during level map mode ('X') # tile_monster_col - colour of hostile monsters # tile_neutral_col - colour of neutral monsters # tile_peaceful_col - colour of peaceful monsters # tile_friendly_col - colour of friendly monsters # tile_plant_col - colour of zero xp monsters (plant and fungus) # tile_item_col - colour of known or detected items # tile_unseen_col - colour of unseen areas (usually stone) # tile_floor_col - colour of floor # tile_wall_col - colour of any wall type # tile_mapped_floor_col - colour of floor detected via magic mapping # tile_mapped_wall_col - colour of walls detected via magic mapping # tile_explore_horizon_col - colour of the edge of explored territory # tile_door_col - colour of known doors, open or closed # tile_downstairs_col - colour of downstairs # tile_upstairs_col - colour of upstairs, including branch exits # tile_branchstairs_col - colour of branch entrances # tile_portal_col - colour of any portal # tile_transporter_col - colour of transporters # tile_transporter_landing_col - colour of transporter destinations # tile_feature_col - colour of any non-stair, non-portal feature # (altar, shop, fountain, ...) # tile_trap_col - colour of known traps of any type # tile_water_col - colour of shallow water # tile_deep_water_col - colour of deep water # tile_lava_col - colour of lava # tile_excluded_col - colour of squares excluded for autotravel # (will only override tile_floor_col colour) # tile_excl_centre_col - colour of exclusion centre (overrides # tile_floor_col and tile_item_col, only) # tile_window_col - colour of the rectangular view window tile_plant_col = #5C8745 tile_item_col = #262626 tile_floor_col = #262626 tile_explore_horizon_col = #78350F tile_lava_col = #EF4444 tile_water_col = #0086B3 tile_deep_water_col = #1F1FED # default comparison # tile_player_col = white # tile_monster_col = #660000 # tile_neutral_col = #660000 # tile_peaceful_col = #664400 # tile_friendly_col = #664400 # tile_plant_col = #446633 #5C8745 # tile_item_col = #005544 #262626 # tile_unseen_col = black # tile_floor_col = #333333 #262626 # tile_wall_col = #666666 #595959 # tile_mapped_floor_col = #222266 # tile_mapped_wall_col = #444499 # tile_explore_horizon_col = #6B301B #78350F # tile_door_col = #775544 #D97706 # tile_downstairs_col = #FF00FF #DC2626 # tile_upstairs_col = cyan #99FF33 # tile_branchstairs_col = #FF7788 #DC2626 # tile_portal_col = #FFDD00 #EC4899 # tile_transporter_col = #0000FF #F9A8D4 # tile_transporter_landing_col = #5200AA #34D399 # tile_feature_col = #997700 #FCD34D # tile_trap_col = #AA6644 #D24DFF # tile_water_col = #114455 #0086B3 # tile_deep_water_col = #001122 #1F1FED # tile_lava_col = #552211 #EF4444 # tile_excluded_col = #552266 # tile_excl_centre_col = #552266 #4C1D95 # tile_window_col = #558855 # tile color defaults, see http://crawl.akrasiac.org/docs/options_guide.txt # tile_player_col = white # tile_monster_col = #660000 # tile_neutral_col = #660000 # tile_peaceful_col = #664400 # tile_friendly_col = #664400 # tile_plant_col = #446633 # tile_item_col = #005544 # tile_unseen_col = black # tile_floor_col = #333333 # tile_wall_col = #666666 # tile_mapped_floor_col = #222266 # tile_mapped_wall_col = #444499 # tile_explore_horizon_col = #6b301b # tile_door_col = #775544 # tile_downstairs_col = #ff00ff # tile_upstairs_col = cyan # tile_branchstairs_col = #ff7788 # tile_portal_col = #ffdd00 # tile_transporter_col = #0000ff # tile_transporter_landing_col = #5200aa # tile_feature_col = #997700 # tile_trap_col = #aa6644 # tile_water_col = #114455 # tile_deep_water_col = #001122 # tile_lava_col = #552211 # tile_excluded_col = #552266 # tile_excl_centre_col = #552266 # tile_window_col = #558855 # Glyphs ########################### ##### 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 ## ## Bindkey.rc ################################################################################################ # All commands and their key binds # https://github.com/jmbjr/dcss/blob/master/crawl-ref/docs/keybind.txt # CTRL+N will autofight without moving bindkey = [^N] CMD_AUTOFIGHT_NOMOVE # 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 ## ## Slots.rc ################################################################################################ ########### # Sorting # ########### # sort_menus = [menu:](true | false | auto:X)[:sort_order] # Controls if and how items are sorted in inventory and pickup # menus. # When sort_menus = false (the default), items are not sorted, and # will be ordered by inventory letter (or in the order they're # stacked for items on the floor). # When sort_menus = true, items are sorted according to the specified # sort_order, with the default being: # equipped, basename, qualname, curse, qty # If sort_menus = auto:X, items are sorted if there are at least # X items in the same category. For instance: # sort_menus = auto:5 # will sort item classes that have at least 5 items. For instance, # having 4 kinds of potions would not sort them, but having 5 # would. # You can explicitly specify sort criteria in the sort_menus # option as: # sort_menus = true : art, basename, qualname, curse, qty # Two items will be compared based on the first sort criteria # where they differ. So with the sort_menus line given above, # if only one of two different items is a known artefact, it will be # listed first, else (if both or neither are artefacts) if their # basenames are different they will be alphabetically compared using # their basenames; if the basenames are the same but the qualified # names are different it will compare their qualified names, and so # on. # The available sort criteria are: # * basename: # This is the name of the item type. The basename for all of # "a +0 robe", "an embroidered robe" and "the cursed +2 robe of # Ponies" is just "robe". The basename for both of "a brass # ring" and "a ring of fire resistance" are "ring". # * qualname: # The name of the item without articles (a/an/the), quantities, # enchantments, or curse-status. The qualified names for the # robes described above are "robe", "embroidered robe" and "robe # of Ponies", respectively. The qualified names for the rings # described above are "brass ring" and "ring of fire # resistance", respectively. # * fullname: # This is the name of the item as displayed in menus (including # (quantities, curse-status, etc.) # * dbname: # Sorts based on an item's unique internal name which is linked # to its description. Items with the same description will be # grouped together. # * curse: # Curse-status of the item (if known). Uncursed items show up # first. # * equipped: # Equipped items show up first. # * art: # Identified artefacts show up first. # * ego: # Identified ego items show up first. # * glowing: # Unidentified glowing/shiny/runed/etc items show up first. # * identified: # Identified items show up before unidentified ones of the # same type. An item is regarded as identified once you know # its subtype or ego. # * qty: # The quantity for stackable items (such as scrolls, potions, # etc.) # * slot: # The inventory letter for items in inventory; irrelevant for # items on the floor. # * charged: # Makes wands known or assumed to have some charges left appear # before wands known to be empty; irrelevant for all other item # types. # You can ask for a descending order sort by prefixing one or more # sort criteria with > as: # sort_menus = true : basename, >qty # You can also request sorting only for specific menus: # sort_menus = pickup: true # or # sort_menus = inv: true # (Menu types must be specified as name:, with no space between # name and colon.) # By default only pickup menus are sorted, and the sort criteria are: # "equipped, basename, qualname, curse, qty". # All other menus (drop, inv) will be sorted by inventory letter. # The menu selectors available are: # pickup: All pickup menus, stash-search menus, etc. for items not # in your inventory. # drop: The item drop menu. # inv: Inventory listings for any command (but not for dropping # items). # any: All menus; this is the default when unspecified. # For example, # sort_menus = true : equipped, basename, qualname, curse, qty # will produce the same inventory and drop menus as by default, # with the exception that all worn/wielded items come first. This # can be convenient if you use the '.' command to select # subsequent items. # If you define sort_menus differently for two or more selectors, # the last one matching will always take precedence, i.e. "any" # as last setting would override any of the others. # for all menus (unspecified) # ignore equipped status for sorting sort_menus = true : identified , curse , art , ego , basename , qualname, >qty # sort equipped items to top # sort_menus = true : equipped , identified , curse , art , ego , basename , qualname, >qty # sort by quantity first # sort_menus = true : >qty , identified , curse , art , ego , basename , qualname, equipped, >qty ##################### ### Ability slots ### ##################### ability_slot += Fly|flight:lF ability_slot += Stop Flying:L ability_slot += Breathe:t ability_slot += Invisibility:iv # Abilities prone to miskeys. ability_slot += Blink:IB ability_slot += Berserk:k ability_slot += Corrupt:C ability_slot += Enter the Abyss:E ################### ### Spell slots ### ################### # Use letters in a word of the spell name at # (1) beginning, (2) end, of words in the spell, # (3) or anywhere from the spell name # Some overrides may exist or order may change by preference # See this dynamic web app for oganizing spell slots in real-time # https://codesandbox.io/s/dcss-spell-slots-zt5px spell_slot += Agony:aAyYgGoOnN spell_slot += Airstrike:aAeEiIrRsStTkK spell_slot += Alistair's intoxication:aiAIsnSNlLtToOxXcCrR spell_slot += Animate armour:aAerERnNimIMoOuUtT spell_slot += Animate dead:adADeEnNiImMtT spell_slot += Animate skeleton:asASenENkKiImlMLtToO spell_slot += Apportation:aAnNpPoOrRtTiI spell_slot += Beastly appendage:baBAyeYEpPsStnTNldLDgG spell_slot += Blade hands:bhBHesESlaLAnNdD spell_slot += Blink:bBkKlLiInN spell_slot += Bolt of magma:bmBMtaTAoOlgLG spell_slot += Borgnjor's revivification:brBRsnSNoeOEvVgiGIjJfFcCaAtT spell_slot += Borgnjor's vile clutch:bvcBVCsehSEHoilOILruRUgtGTnNjJ spell_slot += Call canine familiar:cfCFlerLERaAnmNMiI spell_slot += Call imp:ciCIlpLPamAM spell_slot += Cause fear:cfCFerERaAuUsS spell_slot += Chain lightning:clCLngNGhiHIaAtT spell_slot += Confusing touch:ctCTghGHoOnuNUfFsSiI spell_slot += Conjure ball lightning:cblCBLegEGoaiOAInNjhJHutUTrR spell_slot += Conjure flame:cfCFeEolOLnaNAjmJMuUrR spell_slot += Corona:cCaAoOrRnN spell_slot += Corpse rot:crCRetEToOpPsS spell_slot += Dazzling flash:dfDFghGHalALzZsSiInN spell_slot += Death channel:dcDChlHLeEaAtnTN spell_slot += Death's door:dDsrSReoEOaAtThH spell_slot += Discord:dDiIsScCoOrR spell_slot += Disjunction:JdDnNiIsSjJuUcCtToO spell_slot += Dispel undead:duDUlLinINsSpePEaA spell_slot += Dispersal:dDlLiIsSpPeErRaA spell_slot += Dragon form:dfDFnmNMroROaAgG spell_slot += Dragon's call:dcDCslSLraRAgGoOnN spell_slot += Ensorcelled hibernation:ehEHdnDNiIsbSBoOrRcCaAltLT spell_slot += Eringya's noxious bog:enbENBsgSGroROixIXyuYUaA spell_slot += Excruciating wounds:ewEWgsGSxoXOcuCUrnRNdDiIaAtT spell_slot += Fire storm:fsFSemEMitITroRO spell_slot += Fireball:fFlLiIrReEbBaA spell_slot += Foxfire:fFeEoOxXiIrR spell_slot += Freeze:fFeErRzZ spell_slot += Freezing cloud:fcFCgdGDrlRLeoEOuUzZiInN spell_slot += Frozen ramparts:frFRnsNSaAomOMzpZPeEtT spell_slot += Fulminant prism:fpFPtmTMurURliLIsSnNaA spell_slot += Gell's gravitas:gGsSerERlaLAvViItT spell_slot += Hailstorm:hHmMaAiIlLsStToOrR spell_slot += Haunt:hHtTaAuUnN spell_slot += Ice form:ifIFemEMcoCOrR spell_slot += Ignite poison:ipIPenENgoGOsStT spell_slot += Ignition:iInNgGtToO spell_slot += Infestation:iInNfFeEsStTaAoO spell_slot += Inner flame:ifIFreREnlNLaAmM spell_slot += Invisibility:iIyYnNvVsSbBlLtT spell_slot += Iron shot:isISntNTrhRHoO spell_slot += Irradiate:iIeErRaAdDtT spell_slot += Iskenderun's battlesphere:ibIBseSEaAktKTnlNLdDrpRPuhUH spell_slot += Iskenderun's mystic blast:imbIMBsctSCTylYLkaKAeEnNdDrRuU spell_slot += Leda's liquefaction:lLsnSNeiEIdqDQauAUfFcCtToO spell_slot += Lee's rapid deconstruction:lrdLRDsnSNeaEApcPCioIOtTuU spell_slot += Lehudib's crystal spear:lcsLCSrRepEPhyHYuaUAdtDTiIbB spell_slot += Lesser beckoning:lbLBrgRGeEscSCkKoOnNiI spell_slot += Lightning bolt:lbLBgtGTioIOhHnN spell_slot += Magic dart:mdMDctCTaAgrGRiI spell_slot += Malign gateway:mgMGnyNYaAltLTieIEwW spell_slot += Manifold assault:maMAdtDTsSnNiIfuFUolOL spell_slot += Maxwell's capacitive coupling:mcMCsegSEGaoAOxpuXPUwWlLiItnTNvV spell_slot += Mephitic cloud:mcMCdDelELpoPOhuHUiItT spell_slot += Metabolic englaciation:meMEcnCNtgTGalALbBoOiI spell_slot += Monstrous menagerie:mMseSEoOnNaAtgTGrRuiUI spell_slot += Necromutation:nNeEcCrRoOmMuUtTaAiI spell_slot += Olgreb's toxic radiance:otrOTRsceSCElaLAgxdGXDiIbnBN spell_slot += Orb of destruction:odODbnBNreREsStTuUcCiI spell_slot += Ozocubu's armour:oaOAsrSRzZmMcCuUbB spell_slot += Ozocubu's refrigeration:orORsnSNzeZEfFcCuiUIbgBGaAtT spell_slot += Pain:pPnNaAiI spell_slot += Passage of golubria:pgPGeaEAoOslSLuUbBrRiI spell_slot += Passwall:pPlLaAsSwW spell_slot += Petrify:pPyYeEtTrRiIfF spell_slot += Poisonous vapours:pvPVsSoaOAiIuUnrNR spell_slot += Polar vortex:pvPVrxRXoOlLatATeE spell_slot += Portal projectile:pPleLEorORtjTJaAcCiI spell_slot += Sandblast:sStTaAnNdDbBlL spell_slot += Searing ray:srSRgyGYeaEAiInN spell_slot += Shadow creatures:scSCwWhrHRaeAEdDotOTuU spell_slot += Shatter:sSrRhHaAtTeE spell_slot += Shock:sSkKhHoOcC spell_slot += Silence:sSeEiIlLnNcC spell_slot += Simulacrum:sSmMiIuUlLaAcCrR spell_slot += Slow:sSwWlLoO spell_slot += Spellforged servitor:sSdrDRpePElvLViIftFToOgG spell_slot += Spider form:sfSFrmRMpoPOiIdDeE spell_slot += Starburst:sStTaArRbBuU spell_slot += Static discharge:sdSDceCEtiTIaAhHrRgG spell_slot += Statue form:sfSFemEMtoTOarARuU spell_slot += Sticky flame:sfSFyeYEtlTLiaIAcmCMkK spell_slot += Sting:sSgGtTiInN spell_slot += Stone arrow:saSAewEWtrTRoOnN spell_slot += Storm form:sfSFmMtoTOrR spell_slot += Sublimation of blood:sbSBndNDulULoOiImMaAtT spell_slot += Summon forest:sfSFntNTuoUOmrMReE spell_slot += Summon guardian golem:sgSGnmNMuoUOalALreREdDiI spell_slot += Summon horrible things:shtSHTneNEuoUOmriMRIgGbBlL spell_slot += Summon hydra:shSHnaNAuyUYmdMDrRoO spell_slot += Summon ice beast:sibSIBnetNETucUCmaMAoO spell_slot += Summon lightning spire:slSLngeNGEuipUIPmMhrHRotOT spell_slot += Summon mana viper:smvSMVnarNARuiUIpPeEoO spell_slot += Summon small mammal:smSMnlNLuaUAoO spell_slot += Swiftness:sSwWiIfFtTnNeE spell_slot += Teleport other:toTOrReElhLHpP spell_slot += Tukima's dance:tdTDseSEuaUAknKNicICmM spell_slot += Vampiric draining:vdVDcgCGarARmMpiPInN spell_slot += Wereblood:wWdDeErRbBlLoO spell_slot += Yara's violent unravelling:yvuYVUstgSTGainAINroROlLeE # 0.26 holdback spell_slot += Absolute zero:azAZeoEObBsrSRlLuUtT spell_slot += Hydra form:hfHFamAMyoYOdrDR spell_slot += Summon demon:sdSDnNueUEmMoO spell_slot += Summon greater demon:sgdSGDnrNRueUEmMaoAOtT spell_slot += Tornado:tToOrRnNaAdD # Default letters spell_slot += .*:XYZ ################## ### Item slots ### ################## ## In order of letter used. item_slot += amulet of rage : B item_slot += ring of (ice|protection from cold) : Cc item_slot += amulet of the acrobat : A item_slot += \+[0-9]+ ring of evasion : Ee item_slot += ring of .*fire : Ff item_slot += amulet of regeneration : g item_slot += amulet of guardian spirit : G item_slot += amulet of faith : T item_slot += \+[0-9]+ ring of intelligence : Ii item_slot += ring of flight : L item_slot += ring of protection from magic : Mm item_slot += ring of positive energy : Nn item_slot += \+[0-9]+ ring of protection : Oo item_slot += ring of poison resistance : P item_slot += ring of resist corrosion : R item_slot += amulet of mana regeneration : r item_slot += \+[0-9]+ ring of strength : Ss item_slot += ring of see invisible : v item_slot += ring of magical power : Ww item_slot += \+[0-9]+ ring of dexterity : Dd item_slot += \+[0-9]+ ring of slaying : Yy item_slot += ring of wizardry : Zz # 0.26 holdback item_slot += ring of stealth : Qq item_slot += ring of teleportation : t ## ## AutoInscribe.rc ################################################################################################ show_god_gift = unident ai := autoinscribe # Inscribe forbidden items for PickupEquipment ai += forbidden:forbidden ai += (bad|dangerous)_item.*potion:!q ai += (bad|dangerous)_item.*scroll:!r ai += potions? of berserk rage:!q ai += scrolls? of (blinking|immolation|magic mapping|silence|vulnerability):!r ai += of faith:!P ai += manual of:!d # Inscribe distortion weapons if you are not worshipping Lugonu : if you.god() ~= "Lugonu" then ai += distortion:!w ai += (Sonja|Psyche):!w : end # Prevent melee with all staves; # If we want to melee with one, it's safe to require removing the annotation. ai += magical staff:!a # Prevent auto quivering and cycling ammo ai += (large rock|throwing net|curare|of dispersal):=f # Warn before throwing ai += (throwing net|of dispersal):!f ### Convenient shortcuts ########################### # Potions ai += curing:@q1 ai += potions? of heal wounds:@q2 ai += potions? of haste:@q3 # Scrolls ai += identify:@r1 ai += scrolls? of teleportation:@r4 # Rare items ai += (executioner's axe|double sword|triple sword|eveningstar|quick blade):rare ai += (storm dragon scales|quicksilver dragon scales|shadow dragon scales|pearl dragon scales|gold dragon scales|crystal plate):rare # 0.26 holdback ai += remove curse:@r2 # MR-checking menu += magenta:wand of.*(disintegration|confusion|polymorph) item += wand of.*(disintegration|confusion|polymorph):magenta menu += lightmagenta:wand of.*(paralysis|charming) item += wand of.*(paralysis|charming):lightmagenta ### General identification ### menu += cyan:manual of item += manual of:cyan menu += lightcyan:manual item += manual:lightcyan menu += lightblue:unidentified .*(potion|scroll|wand|jewellery|book|rod|magical staff) item += unidentified.*(potion|scroll|wand|jewellery|book|rod|magical staff).*:lightblue ### Gear ### menu += magenta:.*known .*(ring of (dexterity|strength|intelligence|slaying|evasion|protection(?! from))|amulet of reflection) item += known.*(ring of (dexterity|strength|intelligence|slaying|evasion|protection(?! from))|amulet of reflection):magenta menu += inventory:lightgray:.*(book|jewellery|magical staff) item += (identified|known).*(book|jewellery|magical staff):lightgray menu += lightmagenta:unidentified.*artefact.* item += unidentified.*artefact.*(jewellery).*:lightmagenta menu += white:.*artefact.* item += identified.*artefact.*(jewellery):white # Ego items menu += lightblue:unidentified.*weapon.*(runed|glowing|enchanted) menu += lightblue:unidentified.*armour.*(runed|glowing|embroidered|shiny|dyed) # Want this to override anything above item += (a )?stones?$:lightgray item += useless:darkgrey # Only mark these types when forbidden; for other types it looks odd. item += forbidden.*(potion|scroll|food):red # 0.26 holdback menu += lightgrey:scroll.*remove curse item += scroll.*remove curse:lightgrey menu += blue:wand of.*random effects item += wand of.*random effects:blue menu += lightmagenta:wand of.*(enslavement) item += wand of.*(enslavement):lightmagenta ## ## ForceMorePrompts.rc ################################################################################################ show_more = false # 1. Dungeon Features # 2. Failure # 3. Bad Things # 4. Translocations # 5. Expiring Effects # 6. Religion # 7. Hell Effects # 8. Monsters # Set alias more := force_more_message stop := runrest_stop_message #################### # Dungeon Features # #################### # Abyssal Rune more += Found .* abyssal rune of Zot # Entrances, Exits, and Arrivals more += Found a frozen archway more += Found a gateway leading out of the Abyss more += Found a labyrinth entrance more += Found a staircase to the Ecumenical Temple more += The mighty Pandemonium lord.*resides here # Portal Timers more += distant snort more += interdimensional caravan more += invites you to visit more += oppressive heat more += roar of battle more += sound of rushing water more += The drain falls to bits more += There is an entrance to a bailey on this level more += tolling of a bell more += wave of frost more += You hear the drain falling apart more += You hear.*crackle.*magical portal more += You hear.*crackling.*archway more += You hear.*creaking.*(oriflamme|portcullis) more += You hear.*hiss.*sand more += You hear.*rumble.*avalanche more += You hear.*rusting.*drain more += You hear.*ticking.*clock # Traps more += (blundered into a|invokes the power of) Zot more += A huge blade swings out and slices into you stop += An alarm trap emits a blaring wail stop += found a zot trap stop += hear a soft click more += The power of Zot is invoked against you more += You (become entangled|are caught) in (a|the) (net) more += You fall through a shaft more += You stumble into the trap # Other more += Another plant grows acid sacs more += One of the plants suddenly grows acid sacs more += The walls and floor vibrate strangely for a moment more += You are suddenly pulled into a different region more += You have a vision of.*gates? ########### # Failure # ########### more += do not work when you're silenced more += sense of stasis more += Something interferes with your magic more += The rod doesn't have enough magic points more += The spell fizzles more += The writing blurs in front of your eyes more += The.*is unaffected more += This potion can/'t work under stasis more += This wand has no charges more += too hungry more += You are held in a net more += You are too injured to fight blindly more += You can't gag anything down more += You can't unwield more += You cannot cast spells when silenced more += You cannot cast spells while unable to breathe more += You cannot teleport right now more += You don't have any such object more += You don't have enough magic more += You don't.* that spell more += You fail to use your ability more += You have no appropriate body parts free more += You have no means to grasp a wand firmly enough more += You haven't enough magic at the moment more += You miscast more += Your amulet of stasis more += Your attempt to break free more += Your body armour is too heavy ############################# # Bad and Unexpected Things # ############################# # announce_damage more += $PAUSE_MORE # Bad things happening to you more += corrodes your equipment more += Your corrosive artefact corrodes you more += are blown away by the wind more += dispelling energy hits you more += infuriates you more += lose consciousness more += mark forms upon you more += Ouch! That really hurt! more += silver sears you more += Space bends around you more += Space warps horribly around you more += surroundings become eerily quiet more += Terrible wounds (open|spread) all over you more += The acid corrodes your more += The air around.*erupts in flames more += The air twists around and violently strikes you in flight more += You shudder from the earth-shattering force more += The arrow of dispersal hits you[^r] more += The barbed spikes become lodged in your body more += The barbed spikes dig painfully into your body as you move more += The blast of calcifying dust hits you[^r] more += The poison in your body grows stronger more += The pull of.*song draws you forwards more += The.*engulfs you in water more += The.*grabs you[^r] more += You (are|feel) drained more += You are (blasted|electrocuted) more += You are blown backwards more += You are burned terribly more += You are encased in ice more += You are engulfed in calcifying dust more += You are engulfed in dark miasma more += You are engulfed in mutagenic fog more += You are knocked back more += You are mesmerised more += You are slowing down more += You are trampled more += You convulse more += You feel a (horrible|terrible) chill more += You feel haunted more += You feel less vulnerable to poison more += You feel your attacks grow feeble more += You feel your flesh.*rot more += You feel your power drain away more += You feel your power leaking away more += You feel yourself grow more vulnerable to poison more += You stumble backwards more += You.*re (confused|more confused|too confused) more += You.*re (poisoned|more poisoned|lethally poisoned) more += Your body is wracked with pain more += Your damage is reflected back at you more += Your limbs are stiffening more += Your magical defenses are stripped away more += Your?.*suddenly stops? moving # Monsters doing bad things more += A tree reaches out and hits you! more += Agitated ravens fly out from beneath the more += begins to recite a word of recall more += Being near the torpor snail leaves you feeling lethargic more += blows on a signal horn more += cast banishment more += cast paralyse more += cast Torment more += goes berserk more += The moth of wrath goads something on more += is duplicated more += is no longer invulnerable more += Its appearance distorts for a moment more += Mara seems to draw the.*out of itself more += Mara shimmers more += Miasma billows from the more += shoots a curare more += stands defiantly in death's doorway more += steals.*your more += swoops through the air toward you more += The forest starts to sway and rumble more += The jumping spider pounces on you [^but] more += The octopode crusher throws you more += The shadow imp is revulsed by your support of nature more += The water nymph flows with the water more += The.*offers itself to Yredelemnul more += The.*seems to speed up more += The.*shudders more += There is a horrible\, sudden wrenching feeling in your soul more += Vines fly forth from the trees! more += You are hit by a branch more += You feel you are being watched by something more += Your magical defenses are stripped away more += \'s.*reflects # Unexpected situations more += A magical barricade bars your way more += Done waiting more += doors? slams? shut more += It doesn't seem very happy more += Mutagenic energies flood into your body more += Some monsters swap places more += (are starving|devoid of blood) more += (The|Your).*falls away! more += The divine light dispels your darkness! more += The walls disappear more += There is a sealed passage more += You are wearing\: more += You cannot afford.*fee # more += You feel (dopey|clumsy|weak) more += You feel a genetic drift more += You feel monstrous more += You feel your rage building more += You have disarmed more += You have finished your manual more += You need to eat something NOW more += You smell decay. (^Yuck!) more += You stop (a|de)scending the stairs more += You turn into a fleshy mushroom more += Your body shudders with the violent release of wild energies more += Your guardian golem overheats more += your magic stops regenerating more += Your scales start more += your.*devoured more += Green shoots are pushing up through the earth # Things getting better stop += contamination has completely more += You can move again more += You slip out of the net more += You.*and break free more += Your fit of retching subsides more += seems mollified # Ghouls : if you.race() == "Ghoul" then stop += smell.*(rott(ing|en)|decay) stop += something tasty in your inventory : end ################## # Translocations # ################## # Teleporting more += You blink more += You.*teleport [^f] more += You feel strangely (unstable|stable) more += You feel your translocation being delayed more += Your surroundings flicker more += Your surroundings seem slightly different more += Your surroundings suddenly seem different # -Tele more += You cannot blink right now more += You cannot teleport right now more += You feel.*firmly anchored in space more += You are no longer firmly anchored in space # -cTele more += You feel your control is inadequate #################### # Expiring Effects # #################### # God Abilities # Divine Shield (The Shining One) more += Your divine shield starts to fade. more += Your divine shield fades away. # Jelly Prayer (Jiyva) more += Your prayer is over. # Mirror Damage (Yredelemnul) more += dark mirror aura disappears # Player Spells # Aura of Abjuration stop += Your aura of abjuration expires # Control Teleport stop += you feel uncertain # Death's Door more += time is quickly running out more += life is in your own # Enslavement more += is no longer charmed # Flight more += You are starting to lose your buoyancy stop += You lose control over your flight # Haste more += Your extra speed is starting to run out more += You feel yourself slow down # Invisibility more += You feel more conspicuous more += You flicker for a moment more += You flicker back # Ozocubu's Armour and Condensation Shield more += Your icy (shield|armour) evaporates more += Your.*(shield|armour) melts away # Phase Shift more += You feel closer to the material plane more += You are firmly grounded in the material plane once more # Repel/Deflect stop += missiles spell is about to expire more += You feel less protected from missiles # Shroud of Golubria stop += shroud begins to fray stop += shroud unravels more += Your shroud falls apart # Silence more += Your hearing returns # Swiftness stop += start to feel a little slower more += You feel sluggish # Transmutations more += Your transformation is almost over more += You have a feeling this form more += Your skin feels tender more += You feel yourself come back to life # Other # Potion of Resistance more += You start to feel less resistant. more += Your resistance to elements expires ############ # Religion # ############ # Gifts or abilities are ready # Dithmenos more += You are shrouded in an aura of darkness more += You now sometimes bleed smoke more += You.*no longer.*bleed smoke more += Your shadow no longer tangibly mimics your actions more += Your shadow now sometimes tangibly mimics your actions # Gozag more += will now duplicate a non-artefact item # Jiyva more += will now unseal the treasures of the Slime Pits # Kikubaaqudgha more += Kikubaaqudgha will now enhance your necromancy # Lugonu more += Lugonu will now corrupt your weapon # Qazlal more += resistances upon receiving elemental damage more += You are surrounded by a storm which can block enemy attacks # Ru more += you are ready to make a new sacrifice # Sif Muna more += Sif Muna is protecting you from the effects of miscast magic # The Shining One more += The Shining One will now bless # Zin more += will now cure all your mutations # You Screwed Up more += is no longer ready # Poor Decisions more += You really shouldn't be using # Gaining new abilities : if you.god() ~= "Uskayaw" then more += You can now more += Your?.*can no longer : end # Wrath more += Nemelex gives you another card to finish dealing more += Fedhas invokes the elements against you more += Lugonu sends minions to punish you more += Okawaru sends forces against you more += wrath finds you # Xom Effects more += staircase.*moves more += is too large for the net to hold # Other more += Jiyva alters your body : if you.god() == "Xom" then more += god: : end : if not string.find(you.god(), "Jiyva") then more += splits in two :end ################ # Hell Effects # ################ more += A gut-wrenching scream fills the air more += Brimstone rains from above more += Die\, mortal more += Leave now\, before it is too late more += Something frightening happens more += Trespassers are not welcome here more += We do not forgive those who trespass against us more += We have you now more += You do not belong in this place more += You feel a terrible foreboding more += You feel lost and a long\, long way from home more += You hear diabolical laughter more += You hear words spoken in a strange and terrible language more += You sense a hostile presence more += You sense an ancient evil watching you more += You shiver with fear more += You smell brimstone more += You suddenly feel all small and vulnerable more += You will not leave this place more += You have reached level more += You rejoin the land of the living ############ # Monsters # ############ # Arriving Unexpectedly more += appears in a shower of sparks more += appears out of thin air more += comes (up|down) the stairs more += Something appears in a flash of light more += The.*is a mimic more += You sense the presence of something unfriendly more += The.*answers the.*call more += Wisps of shadow swirl around more += Shadows whirl around # Item Use more += drinks a potion more += evokes.*(amulet|ring) more += reads a scroll more += zaps a (wand|rod) # Dangerous monsters we force_more when first seen. # Things with ranged (or extremely fast), irresistable effects. more += ((floating|shining) eye|dream sheep|death drake).*into view more += (wretched star|apocalypse crab|death drake).*into view more += (entropy weaver|torpor snail|spriggan druid).*into view more += (vault (warden|sentinel)|merfolk (avatar|siren)).*into view more += (guardian serpent|draconian shifter|convoker|death cob).*into view more += (phantasmal warrior|air elemental).*into view # Distortion more += distortion # Malmutate more += (cacodemon|neqoxec).*into view # Paralysis/Petrify/Banish more += (orc sorcerer|(?> note_items += artefact note_items += experience,of Zot,acquirement,Archmagi note_items += crystal plate,pearl dragon scales,gold dragon scales # note some auto inscribes # do not match curare note_messages += (?> 🤖 colortest <%s>%s", color, color, color)) -- end -- end -- colortest() -- rc_out("COLORS", COLORS.brown, COLORS.brown) } tile_show_threat_levels = tough nasty show_more = false macros += M 1 ===bonsai_smart_cast { local monster_ac={["adder"]=1,["jelly"]=0,["Tiamat"]=30,["Sonja"]=2,["jumping spider"]=6,["spatial vortex"]=0,["formicid"]=3,["queen bee"]=10,["acid dragon"]=5,["rime drake"]=3,["dwarf"]=2,["centaur"]=3,["orb of destruction"]=0,["deep elf zephyrmancer"]=0,["hobgoblin"]=2,["Amaemon"]=3,["deep troll shaman"]=6,["shadow imp"]=3,["human"]=3,["eldritch tentacle segment"]=13,["twister"]=0,["bat"]=1,["glowing shapeshifter"]=0,["Bai Suzhen"]=14,["golden dragon"]=15,["draconian annihilator"]=-1,["ophan"]=10,["white draconian"]=9,["death knight"]=2,["radroach"]=13,["Rupert"]=0,["sky beast"]=3,["ballistomycete spore"]=0,["black mamba"]=4,["the Serpent of Hell"]=30,["Gloorx Vloq"]=10,["Roxanne"]=20,["withered plant"]=0,["white imp"]=4,["Grinder"]=3,["thermic dynamo"]=4,["deep elf sorcerer"]=0,["spectral thing"]=8,["deep elf blademaster"]=0,["bunyip"]=6,["flayed ghost"]=0,["kraken"]=20,["bone dragon"]=20,["bombardier beetle"]=4,["tentacle segment"]=5,["shard shrike"]=2,["yaktaur"]=4,["alligator"]=4,["the Royal Jelly"]=8,["emperor scorpion"]=18,["snake"]=0,["curse skull"]=35,["tentacle"]=5,["Gastronok"]=2,["small abomination"]=0,["minotaur"]=6,["acid blob"]=1,["deep elf master archer"]=0,["orange demon"]=3,["Executioner"]=10,["naga"]=6,["frilled lizard"]=0,["oni"]=1,["Louise"]=0,["Murray"]=30,["water elemental"]=4,["bound soul"]=8,["culicivora"]=2,["mummy priest"]=8,["green draconian"]=9,["tentacled starspawn"]=5,["faun"]=2,["fire crab"]=9,["yaktaur captain"]=5,["pandemonium lord"]=1,["servant of whispers"]=1,["komodo dragon"]=7,["occultist"]=0,["goliath frog"]=3,["hydra"]=0,["hellwing"]=16,["Mara"]=10,["orc"]=0,["dream sheep"]=2,["wolf"]=4,["gargoyle"]=18,["Urug"]=2,["Norris"]=1,["ribbon worm"]=1,["Sojobo"]=2,["boggart"]=0,["skyshark"]=6,["Maggie"]=0,["reaper"]=15,["Lom Lobon"]=10,["large simulacrum"]=10,["small zombie"]=0,["satyr"]=2,["fire vortex"]=0,["ancient champion"]=15,["mutant beast"]=8,["demigod"]=2,["snapping turtle"]=16,["hell hound"]=6,["spark wasp"]=9,["deep troll earth mage"]=12,["draconian"]=10,["deep elf death mage"]=0,["manticore"]=5,["sun demon"]=10,["giant cockroach"]=3,["starspawn tentacle"]=8,["spriggan air mage"]=1,["ice dragon"]=10,["halazid warlock"]=8,["laughing skull"]=4,["will-o-the-wisp"]=4,["very ugly thing"]=6,["Duvessa"]=2,["Ijyb"]=2,["hell lord"]=0,["quicksilver dragon"]=10,["entropy weaver"]=7,["Natasha"]=2,["deep elf high priest"]=3,["ghost moth"]=8,["balrug"]=5,["hellion"]=5,["fire elemental"]=4,["dire elephant"]=13,["silent spectre"]=5,["chaos spawn"]=4,["hell rat"]=7,["walking divine tome"]=10,["fungus"]=0,["Sigmund"]=0,["training dummy"]=0,["demonspawn warmonger"]=3,["foxfire"]=0,["molten gargoyle"]=14,["water moccasin"]=2,["starcursed mass"]=10,["Josephine"]=0,["ogre mage"]=1,["merfolk siren"]=4,["draconian stormcaller"]=0,["polar bear"]=7,["demonspawn corrupter"]=3,["Psyche"]=0,["harpy"]=2,["inugami"]=5,["angel"]=10,["quasit"]=5,["Parghit"]=1,["moth"]=0,["eleionoma"]=2,["spectral weapon"]=5,["Zenata"]=10,["sixfirhy"]=2,["royal mummy"]=10,["orc knight"]=2,["blazeheart golem"]=9,["merfolk avatar"]=4,["vault guard"]=1,["halfling"]=2,["orb of fire"]=20,["iron elemental"]=20,["ancient lich"]=20,["curse toe"]=25,["demonspawn blood saint"]=6,["tengu conjurer"]=2,["boulder"]=10,["orc sorcerer"]=5,["Vv"]=27,["purple draconian"]=9,["guardian mummy"]=6,["draconian monk"]=-3,["eidolon"]=12,["sphinx"]=5,["ynoxinul"]=3,["fire bat"]=1,["ironbound frostheart"]=0,["death drake"]=6,["shadow"]=3,["stone giant"]=12,["wight"]=4,["bloated husk"]=5,["ironbound preserver"]=0,["wyvern"]=5,["broodmother"]=2,["frost giant"]=9,["water nymph"]=2,["hog"]=2,["Mnoleg"]=11,["Nessos"]=4,["mummy"]=3,["armataur"]=15,["Agnes"]=0,["sea snake"]=2,["iron imp"]=6,["golem"]=0,["smoke demon"]=5,["daeva"]=10,["iron golem"]=25,["Grum"]=2,["Mennas"]=15,["ghoul"]=4,["formless jellyfish"]=0,["animated tree"]=0,["pillar of salt"]=1,["lava snake"]=2,["shadow demon"]=7,["lightning spire"]=13,["merfolk javelineer"]=0,["merfolk impaler"]=0,["deep elf demonologist"]=0,["sickly merfolk siren"]=4,["sacred lotus"]=24,["tyrant leech"]=5,["phantasmal warrior"]=12,["djinni"]=5,["naga mage"]=6,["Polyphemus"]=10,["quokka"]=2,["ball lightning"]=0,["deathcap"]=5,["snaplasher vine segment"]=6,["Terence"]=0,["fire dragon"]=10,["hornet"]=6,["wendigo"]=4,["lich"]=10,["briar patch"]=10,["kobold demonologist"]=2,["naga ritualist"]=6,["fire giant"]=8,["Ignacio"]=10,["bennu"]=6,["Crazy Yiuf"]=2,["ufetubus"]=2,["malarious merfolk avatar"]=4,["giant"]=0,["grey draconian"]=16,["hell hog"]=2,["living spell"]=0,["shambling mangrove"]=13,["sleepcap"]=5,["bullfrog"]=0,["death yak"]=9,["Dissolution"]=10,["the Lernaean hydra"]=0,["ballistomycete"]=1,["walking frostbound tome"]=10,["elemental wellspring"]=8,["Jory"]=10,["deep elf annihilator"]=0,["iron giant"]=18,["spriggan"]=1,["pale draconian"]=9,["Joseph"]=0,["ancient zyme"]=6,["Chuck"]=14,["nargun"]=25,["ironbound convoker"]=0,["kobold blastminer"]=4,["peacekeeper"]=20,["dragon"]=0,["deep elf knight"]=0,["small simulacrum"]=10,["shock serpent"]=2,["protean progenitor"]=7,["Margery"]=0,["animated armour"]=8,["Lodul"]=3,["snaplasher vine"]=4,["mana viper"]=3,["goblin"]=0,["imperial myrmidon"]=1,["tengu reaver"]=2,["deep elf elementalist"]=0,["worldbinder"]=12,["orc warrior"]=0,["Frederick"]=0,["Jessica"]=0,["elephant slug"]=2,["moon troll"]=20,["Pikel"]=4,["starspawn tentacle segment"]=8,["Azrael"]=10,["draconian scorcher"]=-1,["Asmodeus"]=30,["seraph"]=10,["soul eater"]=18,["great orb of eyes"]=10,["crystal guardian"]=20,["Khufu"]=10,["salamander"]=5,["golden eye"]=0,["orange crystal statue"]=12,["caustic shrike"]=8,["two-headed ogre"]=3,["rust devil"]=10,["Maurice"]=1,["Nellie"]=13,["naga warrior"]=6,["plant"]=0,["floating eye"]=0,["orc wizard"]=1,["revenant"]=8,["ice beast"]=5,["death cob"]=10,["holy swine"]=2,["fenstrider witch"]=3,["thrashing horror"]=5,["meliai"]=2,["drowned soul"]=0,["demonspawn"]=3,["glowing orange brain"]=2,["war gargoyle"]=25,["pearl dragon"]=10,["spriggan berserker"]=2,["spriggan druid"]=1,["Prince Ribbit"]=0,["vault sentinel"]=1,["Cloud Mage"]=0,["vine stalker"]=2,["Kirke"]=0,["green death"]=5,["centaur warrior"]=4,["basilisk"]=3,["dancing weapon"]=10,["tainted leviathan"]=15,["draconian knight"]=9,["orc warlord"]=3,["raiju"]=4,["diamond obelisk"]=12,["warg"]=9,["electric golem"]=5,["Nergalle"]=9,["fulminant prism"]=3,["scrub nettle"]=8,["martyred shade"]=0,["ugly thing"]=4,["demonspawn black sun"]=9,["Vashnia"]=6,["giant lizard"]=0,["ice devil"]=12,["orc priest"]=1,["Saint Roka"]=3,["storm dragon"]=13,["eldritch tentacle"]=13,["crystal echidna"]=10,["burial acolyte"]=0,["Robin"]=1,["tormentor"]=12,["Jorgrun"]=2,["ironbound thunderhulk"]=1,["glass eye"]=2,["Fannar"]=4,["toenail golem"]=8,["block of ice"]=15,["Hellbinder"]=0,["red devil"]=7,["ancestor"]=5,["giant frog"]=0,["Killer Klown"]=10,["necromancer"]=0,["meteoran"]=2,["torpor snail"]=8,["electric eel"]=1,["juggernaut"]=20,["hell beast"]=5,["apocalypse crab"]=11,["red draconian"]=9,["tengu warrior"]=2,["rat"]=1,["guardian serpent"]=6,["large zombie"]=8,["Arachne"]=3,["creeping inferno"]=0,["spectator"]=0,["slime creature"]=1,["felid"]=2,["merfolk aquamancer"]=0,["demonic plant"]=0,["doom hound"]=6,["earth elemental"]=14,["merfolk"]=4,["iron dragon"]=20,["Blork the orc"]=0,["elephant"]=8,["cacodemon"]=11,["orc high priest"]=1,["hell knight"]=0,["lemure"]=4,["gnoll sergeant"]=2,["shadow dragon"]=15,["Snorg"]=0,["weeping skull"]=7,["Xtahua"]=18,["phantom"]=3,["Ice Fiend"]=15,["antique champion"]=20,["Aizul"]=8,["spellforged servitor"]=10,["crimson imp"]=3,["Ilsuiw"]=5,["hexer"]=5,["river rat"]=5,["yellow draconian"]=9,["titan"]=10,["wolf spider"]=3,["small skeleton"]=0,["Orb Guardian"]=13,["naga sharpshooter"]=6,["vault warden"]=1,["ball python"]=0,["steam dragon"]=5,["salamander tyrant"]=5,["swamp worm"]=3,["Frances"]=0,["battlemage"]=5,["death scarab"]=7,["black bear"]=2,["quicksilver ooze"]=3,["arcanist"]=0,["freezing wraith"]=12,["yak"]=4,["Jeremiah"]=2,["deep elf pyromancer"]=0,["jackal"]=2,["spriggan rider"]=1,["insubstantial wisp"]=0,["necrophage"]=2,["tentacled monstrosity"]=5,["efreet"]=10,["saltling"]=15,["nameless horror"]=8,["lost soul"]=0,["octopode"]=1,["Nikola"]=1,["Edmund"]=0,["ragged hierophant"]=0,["strange machine"]=12,["unseen horror"]=5,["spriggan defender"]=3,["dread lich"]=20,["hound"]=2,["nagaraja"]=6,["Brimstone Fiend"]=15,["jiangshi"]=10,["large skeleton"]=0,["elemental"]=0,["alligator snapping turtle"]=19,["vampire mage"]=10,["drake"]=0,["catoblepas"]=10,["tarantella"]=3,["lindwurm"]=8,["putrid mouth"]=5,["Josephina"]=10,["skeleton"]=0,["swamp dragon"]=7,["vampire mosquito"]=2,["Mlioglotl"]=10,["Donald"]=3,["boulder beetle"]=20,["vampire"]=10,["troll"]=3,["bush"]=15,["ice statue"]=12,["kobold"]=2,["gnoll bouda"]=2,["Geryon"]=15,["eye of devastation"]=12,["cerulean imp"]=3,["cyclops"]=5,["toadstool"]=1,["ghost"]=0,["vampire knight"]=10,["simulacrum"]=10,["kobold brigand"]=3,["Menkaure"]=3,["walking earthen tome"]=20,["shapeshifter"]=0,["bear"]=0,["Ereshkigal"]=10,["scorpion"]=5,["blizzard demon"]=10,["rakshasa"]=6,["orb spider"]=3,["spatial maelstrom"]=0,["Pargi"]=1,["Cerebov"]=30,["black draconian"]=9,["Hell Sentinel"]=25,["Harold"]=0,["neqoxec"]=4,["draconian shifter"]=-1,["gnoll"]=2,["statue"]=12,["rockslime"]=27,["wandering mushroom"]=5,["Antaeus"]=28,["quicksilver elemental"]=1,["battlesphere"]=0,["Head Instructor"]=0,["Grunn"]=6,["salamander mystic"]=5,["the Enchantress"]=1,["Dispater"]=35,["apis"]=9,["hellephant"]=13,["azure jelly"]=5,["Erolcha"]=3,["Asterion"]=4,["zombie"]=0,["cherub"]=10,["lorocyproca"]=10,["orc apostle"]=2,["steelbarb worm"]=11,["obsidian statue"]=12,["deep dwarf"]=2,["blazeheart core"]=0,["cactus giant"]=1,["walking crystal tome"]=15,["shadow wraith"]=7,["iguana"]=5,["pharaoh ant"]=4,["brain worm"]=1,["redback"]=2,["Dowan"]=0,["anaconda"]=4,["wraith"]=10,["killer bee"]=2,["merged slime creature"]=0,["butterfly"]=0,["endoplasm"]=1,["moth of wrath"]=0,["wind drake"]=3,["crab"]=0,["aspiring flesh"]=2,["searing wretch"]=4,["jorogumo"]=4,["crocodile"]=4,["starflower"]=16,["barachi"]=0,["ettin"]=9,["ghost crab"]=9,["profane servitor"]=10,["shining eye"]=3,["dryad"]=6,["thorn hunter"]=9,["demonic crawler"]=10,["bog body"]=1,["stoker"]=5,["wretched star"]=10,["deep troll"]=6,["Eustachio"]=0,["elf"]=1,["vampire bat"]=1,["cane toad"]=6,["tengu"]=2,["oklob plant"]=10,["skeletal warrior"]=15,["swamp drake"]=3,["lurking horror"]=0,["player ghost"]=1,["iron troll"]=20,["ushabti"]=9,["Erica"]=0,["howler monkey"]=1,["blink frog"]=0,["dart slug"]=1,["spider"]=0,["sun moth"]=6,["ogre"]=1,["knight"]=5,["player illusion"]=1,["Tzitzimitl"]=12,["air elemental"]=2,["walking tome"]=0,["large abomination"]=0,["deep elf archer"]=0,["Boris"]=12,["oklob sapling"]=10} function bonsai_smart_cast() local mp,mp_max=_G.you.mp() local mp_regen=math.max(1,math.floor((mp_max or 20)/10)) -- Djinni uses HP as MP local ok_race,race=pcall(function() return you.race() end) if ok_race and race=="Djinni" then mp=you.hp()-10 end -- reserve 10 HP -- Reserve MP for escape spells (Blink, Passage of Golubria, Dispersal, Disjunction) local escape_spells={"Blink","Passage of Golubria","Dispersal","Disjunction"} local reserve_mp=0 local reserve_spell=nil for _,esc in ipairs(escape_spells) do if spells.memorised(esc) then local c=spells.mana_cost(esc) if c and c>reserve_mp then reserve_mp=c;reserve_spell=esc end end end local los=you.los() local t={} local friends={} local found_bsph=false for x=-los,los do for y=-los,los do local m=monster.get_monster_at(x,y) if m and not m:is_firewood() then if m:attitude()==0 then t[#t+1]={m=m,x=x,y=y} elseif m:attitude()>0 then if m:name()=="battlesphere" then found_bsph=true elseif m:name()~="orb of destruction" then friends[#friends+1]={x=x,y=y} end end end end end if #t==0 then crawl.mpr("No")return end local function has_friendly(cx,cy,radius) for _,f in ipairs(friends) do if math.max(math.abs(f.x-cx),math.abs(f.y-cy))<=radius then return true end end return false end -- LRD: determine dice, radius, and ice flag for a monster target (by name) -- Returns dice,radius,is_ice or 0,0,false if not fraggable local function lrd_dice_mon(m) local mn=string.lower(m:name()) -- Metal monsters (4 dice) — check before rock due to war gargoyle/gargoyle overlap local metal={"iron golem","iron elemental","peacekeeper","war gargoyle","clockwork bee", "lightning spire","blazeheart golem","walking alembic","monarch bomb","bomblet", "phalanx beetle","spellspark servitor","platinum paragon","crawling flesh cage"} for _,p in ipairs(metal) do if string.find(mn,p) then return 4,1,false end end -- Crystal monsters (4 dice, radius 2) local crystal={"crystal guardian","crystal echidna","orange statue","obsidian bat", "obsidian statue","diamond sawblade","roxanne","glass eye","screaming refraction"} for _,p in ipairs(crystal) do if string.find(mn,p) then return 4,2,false end end -- Rock/bone monsters (3 dice) local rock={"toenail golem","saltling","pillar of salt","pile of debris","petrified flower", "earth elemental","mountainshell","rockslime","boulder","ushabti","statue","gargoyle", "rock fish","hellfire mortar"} for _,p in ipairs(rock) do if string.find(mn,p) and mn~="molten gargoyle" and mn~="war gargoyle" then return 3,1,false end end -- Ice monsters (3 dice, cold damage) local ice={"ice beast","simulacr","ice statue","block of ice","nargun", "hoarfrost cannon","pillar of rime","splinterfrost barricade"} for _,p in ipairs(ice) do if string.find(mn,p) then return 3,1,true end end -- Skeletal monsters (3 dice) local skel={"draugr","bone dragon","skeletal warrior","ancient champion","revenant", "weeping skull","laughing skull","curse skull","marrowcuda","murray","nameless revenant"} for _,p in ipairs(skel) do if string.find(mn,p) then return 3,1,false end end -- Petrified/petrifying monsters if m:status("petrified") or m:status("petrifying") then return 3,1,false end return 0,0,false end -- LRD: determine dice and radius for terrain -- Returns dice,radius or 0,0 if not fraggable local function lrd_dice_terrain(f) local fl=string.lower(f or "") -- Exclude stairs early (stone_stairs contains "stone") if string.find(fl,"stair") then return 0,0 end -- Rock/stone terrain (3 dice) if string.find(fl,"rock_wall") or string.find(fl,"stone_wall") or string.find(fl,"slimy_wall") or string.find(fl,"door") or string.find(fl,"stone_arch") or string.find(fl,"granite_statue") or string.find(fl,"orcish_idol") or string.find(fl,"petrified_tree") or string.find(fl,"spike_launcher") then return 3,1 end -- Metal terrain (4 dice) if string.find(fl,"metal") or string.find(fl,"iron") or string.find(fl,"grate") then return 4,1 end -- Crystal terrain (4 dice, radius 2) if string.find(fl,"crystal") then return 4,2 end return 0,0 end -- Track last cast turn for lingering spells last_cast_turn = last_cast_turn or {} local turn=you.turns() -- Check if spell was cast recently local function recently_cast(name) -- For channeled spells, check you.status() directly (more reliable than turn counting) local status=you.status() or "" if name=="Flame Wave" then return string.find(status,"ame Wave")~=nil end if name=="Searing Ray" then return string.find(status,"earing Ray")~=nil end -- Persistent summons: use turn tracking local lt=last_cast_turn[name] if not lt then return false end if name=="Iskenderun's Battlesphere" or name=="Conjure Ball Lightning" then return true end return false end -- Mark spell as cast local function mark_cast(name) last_cast_turn[name]=turn end local function get_ac(m) local pips=m:ac() if pips==0 then return 0 end -- Use exact AC from lookup table if pip count matches local known=monster_ac[m:name()] if known and known>=0 and math.ceil(known/5.0)==pips then return known end return 2.5+(pips-1)*5 end local function get_ev(m) local pips=m:ev() if pips==0 then return 0 end local ev=2.5+(pips-1)*5 -- Status effects that modify EV (source: monster.cc:3345-3360) -- Paralysed/petrified/asleep → EV=0 local ok1,v1=pcall(function() if m:status("paralysed") or m:status("petrified") or m:status("asleep") then return true end return false end) if ok1 and v1 then return 0 end -- Caught/netted → EV/5 local ok2,v2=pcall(function() return m:is_caught() end) if ok2 and v2 then return math.max(0,ev/5) end -- Confused → EV/2 local ok3,v3=pcall(function() return m:status("confused") end) if ok3 and v3 then return math.max(0,ev/2) end -- Constricted → EV-10 local ok4,v4=pcall(function() return m:is_constricted() end) if ok4 and v4 then ev=ev-10 end return math.max(0,ev) end local function get_max_hp(m) local hp_str=tostring(m:max_hp()) hp_str=hp_str:gsub("about ",""):gsub("~","") return tonumber(hp_str) or 20 end local function get_hp(m) local mhp=get_max_hp(m) local dl=m:damage_level() -- damage_level: 0=full, 1=lightly, 2=moderately, 3=heavily, 4=severely, 5=almost dead, 6=dead return math.max(1, mhp*(6-dl)/6) end local function cdist(x,y) return math.max(math.abs(x),math.abs(y)) end local function score_dmg(dmg,m,dist) local hp=get_hp(m) local mhp=get_max_hp(m) local threat=m:threat()+1 -- Overkill waste: only 20% credit for damage beyond kill threshold local effective=dmg if dmg>hp then effective=hp+(dmg-hp)*0.2 end -- Finish-off bonus: killing removes threat entirely local finish=1.0 if dmg>=hp then finish=1.5 end -- Distance urgency: adjacent monsters are most dangerous dist=dist or 4 local urgency=1.0 if dist<=1 then urgency=2.0 elseif dist<=2 then urgency=1.5 elseif dist<=3 then urgency=1.2 end -- Ranged threat: monsters already in attack range are more urgent local ok_rng,mon_rng=pcall(function() return m:range() end) if ok_rng and mon_rng and mon_rng>1 and dist<=mon_rng then urgency=urgency*1.3 end -- Base score: effective damage weighted by threat, finish, urgency, HP ratio local score=effective*finish*urgency*threat/mhp -- Deprioritize summoned monsters (they disappear soon) local desc=m:target_desc() or "" if string.find(desc,"summoned") then score=score*0.2 end -- Deprioritize safe monsters if m:is_safe() then score=score*0.1 end -- Deprioritize stationary monsters (lower threat) local ok_stat,is_stat=pcall(function() return m:is_stationary() end) if ok_stat and is_stat then score=score*0.4 end -- Boost unique/boss targets (high-value kills) local ok_uniq,is_uniq=pcall(function() return m:is_unique() end) if ok_uniq and is_uniq then score=score*1.3 end -- Boost spellcaster threats local ok_sp,mon_sp=pcall(function() return m:spells() end) if ok_sp and mon_sp and #mon_sp>0 then score=score*1.15 end -- Boost regenerating monsters (must kill fast before they heal) local ok_reg,is_reg=pcall(function() return m:status("regenerating") end) if ok_reg and is_reg then score=score*1.3 end return score end local function get_pow(name) local p=spells.power_perc(name) if not p then return 0 end return p end local function player_res(e_type) if e_type==1 then return you.res_cold()end if e_type==2 then return you.res_shock()end if e_type==3 then return you.res_fire()end if e_type==4 then return you.res_poison()end return 0 end local function calc_dmg(base_dmg,ac,ev,pow,noac,nohit,ac3) local dmg=base_dmg -- pow is power_perc (0-100), already factored into get_spell_dmg formulas if noac then -- Skip AC reduction elseif ac>0 then if ac3 then -- AC applied 3 times (Sandblast) local dmg1=dmg local dmg2=dmg local dmg3=dmg if dmg1<=ac then dmg1=dmg1*dmg1/(2*(ac+1)) elseif dmg1>ac then dmg1=dmg1-ac/2 end if dmg2<=ac then dmg2=dmg2*dmg2/(2*(ac+1)) elseif dmg2>ac then dmg2=dmg2-ac/2 end if dmg3<=ac then dmg3=dmg3*dmg3/(2*(ac+1)) elseif dmg3>ac then dmg3=dmg3-ac/2 end dmg=dmg1+dmg2+dmg3 else if dmg<=ac then dmg=dmg*dmg/(2*(ac+1)) else dmg=dmg-ac/2 end end end if nohit then -- Skip EV reduction (always hits) elseif ev>0 then dmg=dmg*math.max(0,1.0-ev/50.0) end return dmg end -- Shield block: ~50% damage reduction for shielded monsters local function sh_damage(dmg,m) local desc=m:target_desc() or "" if string.find(desc,"shield") then return dmg*0.5 end return dmg end -- Evasion check using game API (more accurate than pip estimation) local function evasion_check(m,spellname) if not spellname then return 1.0 end local ok,desc=pcall(function() return m:target_spell(spellname) end) if ok and desc then local hit=string.match(desc,"(%d+)%% to hit") if hit then return tonumber(hit)/100.0 end end return nil -- fallback to pip-based end -- Calculate average damage using wiki formulas (Nd(X) avg = N*(X+1)/2) local function get_spell_dmg(name,pow) -- Level 1 if name=="Foxfire" then return 2*(4+pow/5+1)/2 end -- 2x 1d(4+pow/5) if name=="Freeze" then return (3+3*pow/10+1)/2 end -- 1d(3+3pow/10), ignores AC if name=="Magic Dart" then return (3+pow/5+1)/2 end -- 1d(3+pow/5) if name=="Sandblast" then return 2*(4+pow/3+1)/2 end -- 2d(4+pow/3), triple AC if name=="Poisonous Vapours" then return (1+pow/8+1)/2 end -- 1d(1+pow/8) if name=="Shock" then return (3+pow/4+1)/2 end -- 1d(3+pow/4) -- Level 2 if name=="Mercury Arrow" then return 2*(11+pow/4+1)/2 end -- 2d(11+pow/4) poison+30% irresist if name=="Scorch" then return 2*(5+pow/12+1)/2 end -- 2d(5+pow/12) if name=="Searing Ray" then return 2*(9+pow/7+1)/2 end -- 2d(9+pow/7) irresistible if name=="Static Discharge" then return 3+(2+pow/12)/2 end -- 3+random2(3+pow/12) -- Level 3 if name=="Frozen Ramparts" then return (1+0.3*pow+1)/2 end -- 1d(1+0.3pow) per turn if name=="Hailstorm" then return 3*(15+pow/3+1)/2 end -- 3d(15+pow/3) 50cold+50phys if name=="Stone Arrow" then return 3*(7+pow/8+1)/2 end -- 3d(7+pow/8) -- Level 4 if name=="Airstrike" then return 2*((pow+13)/14+1)/2 end -- 2d((pow+13)/14), +2/space added separately if name=="Brom's Barrelling Boulder" then return 2*(4+pow/10+1)/2 end -- 2d(4+pow/10) if name=="Dispel Undead" then return 3*(6.66+pow/4+1)/2 end -- 3d(6.66+pow/4), ignores AC, undead only if name=="Flame Wave" then return 3*2*(4.5+pow/6+1)/2 end -- 2d(4.5+pow/6) x 3 turns if name=="Fulminant Prism" then return 3*(6+7*pow/40+1)/2 end -- 3d(6+7pow/40) irresistible if name=="Iskenderun's Battlesphere" then return 2*(7+2*pow/9+1)/2 end -- 2d(7+2pow/9) hd=1+rawpow/9 if name=="Iskenderun's Mystic Blast" then return 2*(6+pow/3+1)/2 end -- 2d(6+pow/3) + collision 2d(1+pow/10) if name=="IMB_collision" then return 2*(1+pow/10+1)/2 end -- 2d(1+pow/10) wall/monster impact if name=="Ignite Poison" then return 4*(12+pow*6/100+1)/2 end -- 2*pois_str d(12+pow*6/100), assume pois=2 if name=="Olgreb's Toxic Radiance" then return 3*(1+pow/20+1)/2 end -- 1d(1+pow/20)/turn x ~3 turns if name=="Sticky Flame" then return 2*(4+pow/9+1)/2 + (3+3*pow/40)*8 end -- 2d(4+pow/9) impact + 2d7/turn DoT -- Level 5 if name=="Arcjolt" then return (10+pow/2+1)/2 end -- 1d(10+pow/2) if name=="Borgnjor's Vile Clutch" then return 2*(4+pow/20+1)/2 end -- 2d(4+pow/20) per turn if name=="Freezing Cloud" then return 14 end -- cloud: 6+r2a(16,2) avg ~14/turn, not pow-scaled if name=="Fireball" then return 3*(3.33+pow/6+1)/2 end -- 3d(3.33+pow/6) if name=="Irradiate" then return 3*(11.66+pow/6+1)/2 end -- 3d(11.66+pow/6) if name=="Lee's Rapid Deconstruction" then return 3*(4+pow/5+1)/2 end -- 3d(4+pow/5) triple AC; needs wall/fraggable target -- Level 6 if name=="Bombard" then return 9*((13+2*pow/3)/9+1)/2 end -- 9d((13+2pow/3)/9) if name=="Permafrost Eruption" then return 2*4*(1.75+pow/10+1)/2 end -- 2x 4d(1.75+pow/10) if name=="Plasma Beam" then return 2*(10+11*pow/20+1)/2 end -- 2x 1d(10+11pow/20) elec+fire if name=="Starburst" then return 6*(3+pow/9+1)/2 end -- 6d(3+pow/9) per bolt (8 bolts, single target hit once) -- Level 7 if name=="Hellfire Mortar" then local hd=1+pow/10;local zp=hd*12;return 4*(4+zp*2/21+1)/2 end -- 4d(4+hd*12*2/21) per shot if name=="Magnavolt" then return 4*(9+pow/10+1)/2 end -- 4d(9+pow/10) if name=="Orb of Destruction" then return 9*(5+pow/12+1)/2 end -- 9d(5+pow/12) if name=="Ozocubu's Refrigeration" then return 4*(7.5+pow/9+1)/2 end -- 4d(7.5+pow/9) -- Level 8 if name=="Ignition" then return 3*(3.33+pow/9+1)/2 end -- 3d(3.33+pow/9) per enemy if name=="Lehudib's Crystal Spear" then return 10*(2.3+pow/10+1)/2 end -- 10d(2.3+pow/10) if name=="Fulsome Fusillade" then return 3*(5+pow/8+1)/2 end -- 3d(5+pow/8) per explosion if name=="Maxwell's Capacitive Coupling" then return 999 end -- guaranteed kill -- Level 9 if name=="Fire Storm" then return 8*((5+pow)/8+1)/2 end -- 8d((5+pow)/8) 50% bypasses rF if name=="Chain Lightning" then return 3*(2*pow/3+1)/2 end -- 3d(2pow/3) if name=="Polar Vortex" then return 6*12*(pow/45+1)/2 end -- 12d(rpow/15) per turn x6 turns; rpow≈pow/3 avg (open space) if name=="Shatter" then return 3*(5+pow/3+1)/2 end -- 3d(5+pow/3) base; x2 for nonliving return 10 end local function get_res(m,e_type) if e_type==1 then return m:res_cold()end if e_type==2 then return m:res_shock()end if e_type==3 then return m:res_fire()end if e_type==4 then return m:res_poison()end return 0 end local function get_range(name) local r=spells.range(name) if not r then return 0 end return r end local function can_reach(tx,ty,range) local dist=math.max(math.abs(tx),math.abs(ty)) if dist>range then return false end return view.cell_see_cell(0,0,tx,ty) end local function is_solid(x,y) local f=view.feature_at(x,y) return f=="rock wall" or f=="stone wall" or f=="permarock wall" or f=="unnaturally hard rock wall" or f=="metal wall" or f=="crystal wall" or f=="closed door" or f=="runed door" or f=="sealed door" or f=="tree" end local function get_cost(name) local c=spells.mana_cost(name) if not c then return 99 end -- Flame Wave costs 1 MP to continue, full cost to start if name=="Flame Wave" and recently_cast("Flame Wave") then return 1 end return c end local function near_wall(x,y) for dx=-1,1 do for dy=-1,1 do if dx~=0 or dy~=0 then local f=view.feature_at(x+dx,y+dy) if f and (f=="#" or f=="=" or f=="|" or f=="x") then return true end end end end return false end local function count_empty(x,y) local count=0 for dx=-1,1 do for dy=-1,1 do if dx~=0 or dy~=0 then local m=monster.get_monster_at(x+dx,y+dy) local f=view.feature_at(x+dx,y+dy) if not m and f and f~="#" and f~="=" and f~="|" and f~="x" then count=count+1 end end end end return count end local function beam_blocked_by_friendly(spell_name,tx,ty) local path=spells.path(spell_name,tx,ty) if not path then return false end for _,p in ipairs(path) do local m=monster.get_monster_at(p[1],p[2]) if m and m:attitude()>0 and m:name()~="battlesphere" and m:name()~="orb of destruction" then return true end end return false end local function monsters_on_path(spell_name,tx,ty) local result={} local path=spells.path(spell_name,tx,ty) if not path then return result end for _,p in ipairs(path) do if not (p[1]==tx and p[2]==ty) then local m=monster.get_monster_at(p[1],p[2]) if m and m:attitude()==0 and not m:is_firewood() then result[#result+1]={m=m,x=p[1],y=p[2]} end end end return result end -- Shock bounce: get full path with ricochets (aimed_at_spot=false) -- Returns {hit_counts, enemies} where hit_counts[key]=count (max 2), enemies[key]={m,x,y} local function shock_bounce_hits(tx,ty,pow) local path=spells.path("Shock",tx,ty,0,0,false) if not path then return {},{} end local hit_counts={} local enemies={} for _,p in ipairs(path) do local m=monster.get_monster_at(p[1],p[2]) if m and m:attitude()==0 and not m:is_firewood() then local key=p[1]..","..p[2] if not hit_counts[key] then hit_counts[key]=0;enemies[key]={m=m,x=p[1],y=p[2]} end if hit_counts[key]<2 then hit_counts[key]=hit_counts[key]+1 end end end return hit_counts,enemies end local function monsters_on_line(tx,ty) local result={} local dx=tx>0 and 1 or (tx<0 and -1 or 0) local dy=ty>0 and 1 or (ty<0 and -1 or 0) local steps=math.max(math.abs(tx),math.abs(ty)) if steps==0 then return result end local x,y=0,0 for i=1,steps-1 do x=x+dx y=y+dy local m=monster.get_monster_at(x,y) if m and m:attitude()==0 and not m:is_firewood() then result[#result+1]={m=m,x=x,y=y} end end return result end local s={{ -- Level 1 n="Foxfire",e=3,summon=true,nohit=true -- fire, 2 foxfires, never miss },{n="Freeze",e=1,noac=true,nohit=true -- cold, range 1, ignores AC },{n="Magic Dart",e=0,nohit=true -- irresistible, never misses },{n="Poisonous Vapours",e=4 -- poison, range 3 },{n="Sandblast",e=0,ac3=true,slow=true -- physical, triple AC, 1.5 turns },{n="Shock",e=2,beam=true,halfres=true,penetrate=true,any_target=true -- elec, bouncing bolt, 1/2 AC -- Level 2 },{n="Mercury Arrow",e=4 -- poison+30% irresist },{n="Scorch",e=3,nohit=true -- fire, applies rF- },{n="Searing Ray",e=0,nohit=true,penetrate=true,channeled=true -- irresistible, channeled, penetrates },{n="Static Discharge",e=2,aoe=1,noac=true,nohit=true -- elec, arcs adj, ignores AC -- Level 3 },{n="Frozen Ramparts",e=1,aoe=2,centered=true,nohit=true -- cold, centered, adj walls },{n="Hailstorm",e=1,aoe=3,halfres=true,noadjacent=true -- cold+phys 50/50, donut AoE },{n="Stone Arrow",e=0,beam=true -- physical, bolt -- Level 4 },{n="Airstrike",e=0,nohit=true -- irresistible, smite, AC applies },{n="Brom's Barrelling Boulder",e=0,beam=true -- physical, line, knockback },{n="Dispel Undead",e=0,noac=true,nohit=true,undead=true,no_bsph=true -- ignores AC, undead only },{n="Flame Wave",e=3,aoe=3,nohit=true,channeled=true -- fire, expanding AoE centered, 3 turns },{n="Fulminant Prism",e=0,aoe=2,nohit=true,no_bsph=true,prism=true -- irresistible, smite-placed, delayed 2 turns },{n="Ignite Poison",e=3,aoe=9 -- fire, all poisoned in LOS },{n="Iskenderun's Battlesphere",e=0,summon=true,no_bsph=true -- irresistible, construct },{n="Iskenderun's Mystic Blast",e=0,aoe=2,nohit=true,centered=true -- irresistible, 2-tile AoE },{n="Olgreb's Toxic Radiance",e=4,aoe=9,noac=true,nohit=true -- poison, LOS AoE, ignores AC },{n="Sticky Flame",e=3,nohit=true,noac=true -- fire, DoT ignores AC (bulk of dmg) -- Level 5 },{n="Arcjolt",e=2,aoe=2,halfres=true,nohit=true -- elec, arcs, 1/2 AC },{n="Borgnjor's Vile Clutch",e=0,clutch=true,nohit=true,no_bsph=true -- necro+earth, bolt constricts all on path },{n="Fireball",e=3,aoe=2,nohit=true,any_target=true -- fire, 3x3 explosion },{n="Freezing Cloud",e=1,fcloud=true,nohit=true,no_bsph=true -- cold, smite cloud r2, zone control },{n="Irradiate",e=0,aoe=1,nohit=true -- irresistible, all adjacent },{n="Lee's Rapid Deconstruction",e=0,aoe=1,ac3=true,nohit=true -- physical, triple AC, needs wall/fraggable -- Level 6 },{n="Bombard",e=0,beam=true -- physical, bolt },{n="Conjure Ball Lightning",e=2,summon=true -- elec, 3 autonomous balls },{n="Permafrost Eruption",e=1,aoe=3,halfres=true,centered=true -- phys+cold 50/50 },{n="Plasma Beam",e=2,penetrate=true,halfres=true,auto_target=true -- elec+fire, 2 penetrating bolts, auto-targets farthest },{n="Starburst",e=3,starburst=true -- fire, 8 penetrating bolts from player -- Level 7 },{n="Hellfire Mortar",e=3,mortar=true,halfres=true,no_bsph=true -- fire+earth, bolt path creates lava, summons mortar },{n="Magnavolt",e=2,nohit=true,magnavolt=true -- elec, smite, magnetise chain },{n="Orb of Destruction",e=0,nohit=true,ood=true -- irresistible, homing, distance scaling },{n="Ozocubu's Refrigeration",e=1,aoe=9,nohit=true,auto_target=true -- cold, full LOS, damage reduced by adj enemies },{n="Spellspark Servitor",e=0,summon=true,no_bsph=true -- varies, construct -- Level 8 },{n="Ignition",e=3,aoe=9,nohit=true -- fire, 3x3 on every enemy },{n="Lehudib's Crystal Spear",e=0 -- irresistible, short range },{n="Fulsome Fusillade",e=3,aoe=9 -- variable, 3 explosions/turn },{n="Maxwell's Capacitive Coupling",e=2,aoe=9,noac=true,nohit=true -- elec, guaranteed kill -- Level 9 },{n="Fire Storm",e=3,aoe=3,nohit=true,halfres=true -- fire 50% irresist, smite },{n="Chain Lightning",e=2,aoe=9,halfres=true,nohit=true -- elec, arcs all in LOS },{n="Polar Vortex",e=1,aoe=5,centered=true,halfres=true,nohit=true -- cold 50% irresist, 6-turn sustained, r5 },{n="Shatter",e=0,aoe=9,nohit=true -- physical, full LOS AoE, ignores EV }} -- === Tactical situation assessment === local hp_now,hp_max=you.hp() local hp_ratio=hp_now/hp_max -- Corridor detection: count solid tiles around player local corridor_walls=0 for cx=-1,1 do for cy=-1,1 do if (cx~=0 or cy~=0) and is_solid(cx,cy) then corridor_walls=corridor_walls+1 end end end local in_corridor=(corridor_walls>=5) -- Threat weighting: parse enemy HP, find min distance, detect boss local total_threat=0 local max_single_threat=0 local min_enemy_dist=99 local adj_hostiles=0 for _,e in ipairs(t) do local d=cdist(e.x,e.y) if dmax_single_threat then max_single_threat=mhp end end -- Boss fight: one enemy has >60% of total threat local boss_fight=(#t<=2 and max_single_threat>total_threat*0.6) -- Swarm fight: many weak enemies local swarm_fight=(#t>=4 and max_single_threat=18 then depth_tier=3 end end -- Retreat mode: low HP, no adjacent enemies local retreating=(hp_ratio<0.3 and adj_hostiles==0) -- Surround detection: boost centered AoE when surrounded local surround_boost=1.0 if adj_hostiles>=3 then surround_boost=1.5 elseif adj_hostiles>=2 then surround_boost=1.2 end -- Low HP panic: prefer nohit burst on closest enemy local panic_mode=(hp_ratio<0.25 and adj_hostiles>0) local candidates={} local almost={} -- spells needing 1 more MP -- Battlesphere detected during initial monster scan (found_bsph) local bsph_bonus=0 local bsph_raw=0 local has_bsph=found_bsph if has_bsph and #t>0 then -- Battlesphere targets most-injured enemy: 2d(7+2*pow_perc/9), irresistible, auto-hit, ignores nothing local bsph_pow=0 if spells.memorised("Iskenderun's Battlesphere") then bsph_pow=get_pow("Iskenderun's Battlesphere") end local bsph_base=2*(7+2*bsph_pow/9+1)/2 -- Find most-injured enemy (most hp missing) local most_missing=0 local bsph_target=nil for _,e in ipairs(t) do local missing=get_max_hp(e.m)-get_hp(e.m) if missing>most_missing then most_missing=missing;bsph_target=e end end if not bsph_target then bsph_target=t[1] end -- Battlesphere damage: irresistible (e=0), auto-hit, vs AC local ac=get_ac(bsph_target.m) local dmg=calc_dmg(bsph_base,ac,0,0,false,true,false) dmg=sh_damage(dmg,bsph_target.m) bsph_bonus=score_dmg(dmg,bsph_target.m,cdist(bsph_target.x,bsph_target.y)) bsph_raw=dmg end for _,sp in ipairs(s) do local mem=spells.memorised(sp.n) local cost=get_cost(sp.n) local fail=spells.fail(sp.n) local ok_sev,sev=pcall(function() return spells.fail_severity(sp.n) end) if mem and fail<20 and (not ok_sev or sev<=2) then -- Chain Lightning requires rElec to safely cast -- Irradiate: avoid if post-cast contamination could reach dangerous glow (>=100) if (sp.n=="Chain Lightning" and you.res_shock()<1) or (sp.n=="Irradiate" and (you.contamination() or 0)+40>100) or (sp.n=="Polar Vortex" and string.find(you.status() or "","ortex")) or (sp.n=="Frozen Ramparts" and string.find(you.status() or "","ampart")) or (sp.n=="Fulsome Fusillade" and string.find(you.status() or "","usillade")) or (sp.n=="Iskenderun's Battlesphere" and has_bsph) or (sp.n=="Hellfire Mortar" and (function() local r=you.los() for x=-r,r do for y=-r,r do local m=monster.get_monster_at(x,y) if m and string.find(m:name(),"mortar") then return true end end end return false end)()) then else local range=get_range(sp.n) local pow=get_pow(sp.n) local best_val=0 local best_tx,best_ty=0,0 if sp.summon then local dmg=get_spell_dmg(sp.n,pow) -- Ball Lightning danger: check enemies nearby if sp.n=="Conjure Ball Lightning" then local adj_count=0 local near_count=0 for _,e in ipairs(t) do local dist=math.max(math.abs(e.x),math.abs(e.y)) if dist==1 then adj_count=adj_count+1 elseif dist<=2 then near_count=near_count+1 end end -- Very dangerous if adjacent enemies if adj_count>0 then dmg=dmg*0.1 -- Dangerous if enemies within 2 tiles elseif near_count>0 then dmg=dmg*0.3 -- Also needs rElec elseif you.res_shock()<1 then dmg=dmg*0.2 end end if sp.n=="Foxfire" then local clear=0 for dx=-1,1 do for dy=-1,1 do if dx~=0 or dy~=0 then local f=monster.get_monster_at(dx,dy)if not f then clear=clear+1 end end end end if clear<2 then dmg=dmg*0.1 end end if sp.centered and sp.n=="Polar Vortex" then local walls=0 for dx=-1,1 do for dy=-1,1 do if dx~=0 or dy~=0 then local f=view.feature_at(dx,dy) if f and (f=="#" or f=="=" or f=="|") then walls=walls+1 end end end end if walls>=4 then dmg=dmg*0.3 end end -- Score summon against closest enemy as proxy local best_e=t[1] local best_d=99 for _,e in ipairs(t) do local d=cdist(e.x,e.y) if d0 then local total_dmg=0 local best_tx,best_ty=0,0 if sp.n=="Static Discharge" then for _,e in ipairs(t) do if math.abs(e.x)<=1 and math.abs(e.y)<=1 then local val=0 for _,e2 in ipairs(t) do local dist=math.max(math.abs(e2.x-e.x),math.abs(e2.y-e.y)) if dist<=1 then local r=get_res(e2.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0.0 end if mult>0 then local base=get_spell_dmg(sp.n,pow) local ac=get_ac(e2.m) local ev=get_ev(e2.m) local dmg=calc_dmg(base*mult,ac,ev,pow,sp.noac,sp.nohit,sp.ac3) val=val+score_dmg(dmg,e2.m,cdist(e2.x,e2.y)) end end end if val>total_dmg then total_dmg=val;best_tx=e.x;best_ty=e.y end end end best_val=total_dmg*surround_boost elseif sp.n=="Arcjolt" then -- Chain-arc simulation: start from player, expand outward local hit={} hit["0,0"]=true local base=get_spell_dmg(sp.n,pow) local val=0 for ring=1,los do local found=false for _,e2 in ipairs(t) do local key=e2.x..","..e2.y if not hit[key] then -- Check if adjacent to any already-hit cell local adj=false for dx=-1,1 do for dy=-1,1 do if hit[(e2.x+dx)..","..((e2.y+dy))] then adj=true end end end if adj then hit[key]=true found=true local r=get_res(e2.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0.0 end if mult>0 then local ac=get_ac(e2.m) local dmg if sp.halfres then local dmg1=calc_dmg(base*0.5*mult,ac,0,pow,sp.noac,sp.nohit,sp.ac3) local dmg2=calc_dmg(base*0.5,ac,0,pow,sp.noac,sp.nohit,sp.ac3) dmg=dmg1+dmg2 else dmg=calc_dmg(base*mult,ac,0,pow,sp.noac,sp.nohit,sp.ac3) end val=val+score_dmg(dmg,e2.m,cdist(e2.x,e2.y)) end end end end if not found then break end end best_val=val*surround_boost elseif sp.centered then -- Special handling for Permafrost Eruption: target largest cluster if sp.n=="Permafrost Eruption" then local best_cluster=0 local best_target=nil for _,e in ipairs(t) do local cluster_size=0 for _,e2 in ipairs(t) do if math.abs(e2.x-e.x)<=1 and math.abs(e2.y-e.y)<=1 then cluster_size=cluster_size+1 end end if cluster_size>best_cluster then best_cluster=cluster_size best_target=e end end if best_target then local dmg=0 for _,e2 in ipairs(t) do if math.abs(e2.x-best_target.x)<=1 and math.abs(e2.y-best_target.y)<=1 then local r=get_res(e2.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0.0 end if mult>0 then local base=get_spell_dmg(sp.n,pow) local ac=get_ac(e2.m) local ev=get_ev(e2.m) local tdmg=calc_dmg(base*mult,ac,ev,pow,sp.noac,sp.nohit,sp.ac3) dmg=dmg+score_dmg(tdmg,e2.m,cdist(e2.x,e2.y)) end end end if dmg>0 then if cdist(best_target.x,best_target.y)<=1 then dmg=dmg*surround_boost end best_val=dmg;best_tx=best_target.x;best_ty=best_target.y end end else -- Other centered spells (Starburst, Frozen Ramparts, Polar Vortex) local total_dmg=0 local val=0 for _,e in ipairs(t) do local others=monsters_on_line(e.x,e.y) others[#others+1]={m=e.m,x=e.x,y=e.y} for _,target in ipairs(others) do local r=get_res(target.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0.0 end if mult>0 then local base=get_spell_dmg(sp.n,pow) local ac=get_ac(target.m) local ev=get_ev(target.m) local dmg=calc_dmg(base*mult,ac,ev,pow,sp.noac,sp.nohit,sp.ac3) val=val+score_dmg(dmg,target.m,cdist(target.x,target.y)) end end end best_val=val if best_val>0 then best_tx=0;best_ty=0 end end elseif sp.n=="Lee's Rapid Deconstruction" then -- LRD: scan all cells in range for walls or fraggable monsters local best_dmg=0 for x=-range,range do for y=-range,range do if you.see_cell_solid_see(x,y) then local dice=0 local radius=1 local is_ice=false local from_mon=false -- Check monster first (by name-based lookup, matching source fraggable_monsters) local tm=monster.get_monster_at(x,y) if tm and tm:attitude()==0 and not tm:is_firewood() then local md,mr,mi=lrd_dice_mon(tm) if md>0 then dice=md;radius=mr;is_ice=mi;from_mon=true end end -- Then terrain if dice==0 then local f=view.feature_at(x,y) local td,tr=lrd_dice_terrain(f) if td>0 then dice=td;radius=tr end end if dice>0 and not has_friendly(x,y,radius) then local val=0 local dmg_per=4+pow/5 for _,e2 in ipairs(t) do local dist=math.max(math.abs(e2.x-x),math.abs(e2.y-y)) if dist<=radius and (e2.x~=0 or e2.y~=0) then local ac=get_ac(e2.m) local base=dice*(dmg_per+1)/2 local dmg=calc_dmg(base,ac,0,pow,false,true,true) -- Ice explosion: apply cold resistance if is_ice then local cr=e2.m:res_cold() if cr>0 then dmg=dmg/(1+cr) elseif cr<0 then dmg=dmg*1.5 end end val=val+score_dmg(dmg,e2.m,cdist(e2.x,e2.y)) end end if val>best_dmg then best_dmg=val;best_tx=x;best_ty=y end end end end end best_val=best_dmg else local aoe_centers={} for _,e in ipairs(t) do table.insert(aoe_centers,{x=e.x,y=e.y}) end if sp.aoe and sp.aoe>0 then local p_res=player_res(sp.e) -- Fireball/Fire Storm: can cast on self if rF++ if (sp.n=="Fireball" or sp.n=="Fire Storm") and p_res>=2 then table.insert(aoe_centers,{x=0,y=0}) end -- For any_target spells (Fireball), also add empty cells as potential targets if sp.any_target then for dx=-sp.aoe,sp.aoe do for dy=-sp.aoe,sp.aoe do if not (dx==0 and dy==0) then local empty=true for _,e2 in ipairs(t) do if e2.x==dx and e2.y==dy then empty=false break end end if empty and view.cell_see_cell(0,0,dx,dy) then local exists=false for _,c in ipairs(aoe_centers) do if c.x==dx and c.y==dy then exists=true break end end if not exists then table.insert(aoe_centers,{x=dx,y=dy}) end end end end end end for _,e in ipairs(t) do for dx=-sp.aoe,sp.aoe do for dy=-sp.aoe,sp.aoe do if not (dx==0 and dy==0) then local empty=true for _,e2 in ipairs(t) do if e2.x==e.x+dx and e2.y==e.y+dy then empty=false break end end if empty and view.cell_see_cell(0,0,e.x+dx,e.y+dy) then local exists=false for _,c in ipairs(aoe_centers) do if c.x==e.x+dx and c.y==e.y+dy then exists=true break end end if not exists then table.insert(aoe_centers,{x=e.x+dx,y=e.y+dy}) end end end end end end end for _,center in ipairs(aoe_centers) do -- Prism: must target empty non-solid non-self tile local prism_ok=true if sp.prism then if (center.x==0 and center.y==0) or is_solid(center.x,center.y) or monster.get_monster_at(center.x,center.y) then prism_ok=false end end if prism_ok and not has_friendly(center.x,center.y,sp.aoe) then local val=0 for _,e2 in ipairs(t) do local dist=math.max(math.abs(e2.x-center.x),math.abs(e2.y-center.y)) local adj_dist=math.max(math.abs(e2.x),math.abs(e2.y)) if dist<=sp.aoe and (not sp.noadjacent or adj_dist>1) then local r=get_res(e2.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0.0 end if sp.undead and e2.m:holiness()~="undead" then mult=0 end if sp.n=="Ignite Poison" then local ok,st=pcall(function() return e2.m:status("poisoned") end) if not (ok and st) then mult=0 end end if mult>0 then local base=get_spell_dmg(sp.n,pow) if sp.n=="Shatter" then if e2.m:holiness()=="nonliving" or e2.m:flags("petrified") or e2.m:flags("petrifying") then base=base*2 elseif e2.m:flags("airborne") then base=base/3 end end local ac=get_ac(e2.m) local ev=get_ev(e2.m) local dmg if sp.halfres then local dmg1=calc_dmg(base*0.5*mult,ac,ev,pow,sp.noac,sp.nohit,sp.ac3) local dmg2=calc_dmg(base*0.5,ac,ev,pow,sp.noac,sp.nohit,sp.ac3) dmg=dmg1+dmg2 else dmg=calc_dmg(base*mult,ac,ev,pow,sp.noac,sp.nohit,sp.ac3) end dmg=sh_damage(dmg,e2.m) val=val+score_dmg(dmg,e2.m,cdist(e2.x,e2.y)) end end end -- Prism: discount for 2-turn delay (enemies may move away) if sp.prism then val=val*0.5 end -- Boost AoE centered on/near player when surrounded if cdist(center.x,center.y)<=1 then val=val*surround_boost end if val>total_dmg then total_dmg=val;best_tx=center.x;best_ty=center.y end end end best_val=total_dmg end elseif sp.auto_target then -- Auto-target spells: Plasma Beam (farthest), Permafrost (largest cluster), Ozocubu (all in LOS) if sp.n=="Plasma Beam" then -- Plasma Beam: targets farthest enemy, hits all on line local farthest_dist=0 local farthest_e=nil for _,e in ipairs(t) do local dist=math.max(math.abs(e.x),math.abs(e.y)) if dist>farthest_dist then farthest_dist=dist;farthest_e=e end end if farthest_e then local val=0 local others=monsters_on_line(farthest_e.x,farthest_e.y) for _,target in ipairs(others) do local r=get_res(target.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0.0 end if mult>0 then local base=get_spell_dmg(sp.n,pow) local ac=get_ac(target.m) local ev=get_ev(target.m) local hitC=evasion_check(target.m,sp.n) or math.max(0,1.0-ev/50.0) local tdmg if sp.halfres then local d1=calc_dmg(base*0.5*mult,ac,0,pow,sp.noac,true,sp.ac3) local d2=calc_dmg(base*0.5,ac,0,pow,sp.noac,true,sp.ac3) tdmg=(d1+d2)*hitC else tdmg=calc_dmg(base*mult,ac,0,pow,sp.noac,true,sp.ac3)*hitC end tdmg=sh_damage(tdmg,target.m) val=val+score_dmg(tdmg,target.m,cdist(target.x,target.y)) end end -- Add the farthest target itself local r=get_res(farthest_e.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0.0 end if mult>0 then local base=get_spell_dmg(sp.n,pow) local ac=get_ac(farthest_e.m) local ev=get_ev(farthest_e.m) local hitC=evasion_check(farthest_e.m,sp.n) or math.max(0,1.0-ev/50.0) local tdmg if sp.halfres then local d1=calc_dmg(base*0.5*mult,ac,0,pow,sp.noac,true,sp.ac3) local d2=calc_dmg(base*0.5,ac,0,pow,sp.noac,true,sp.ac3) tdmg=(d1+d2)*hitC else tdmg=calc_dmg(base*mult,ac,0,pow,sp.noac,true,sp.ac3)*hitC end tdmg=sh_damage(tdmg,farthest_e.m) val=val+score_dmg(tdmg,farthest_e.m,cdist(farthest_e.x,farthest_e.y)) end -- Lineup bonus for penetrating beam local lineup=#others if lineup>=1 then val=val*(1.0+lineup*0.3) end if val>0 then best_val=val;best_tx=farthest_e.x;best_ty=farthest_e.y end end elseif sp.n=="Permafrost Eruption" then -- Permafrost Eruption: target largest cluster of adjacent enemies local best_cluster=0 local best_target=nil for _,e in ipairs(t) do local cluster_size=0 for _,e2 in ipairs(t) do if math.abs(e2.x-e.x)<=1 and math.abs(e2.y-e.y)<=1 then cluster_size=cluster_size+1 end end if cluster_size>best_cluster then best_cluster=cluster_size best_target=e end end if best_target then local val=0 for _,e2 in ipairs(t) do if math.abs(e2.x-best_target.x)<=1 and math.abs(e2.y-best_target.y)<=1 then local r=get_res(e2.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0.0 end if mult>0 then local base=get_spell_dmg(sp.n,pow) local ac=get_ac(e2.m) local ev=get_ev(e2.m) local tdmg if sp.halfres then local d1=calc_dmg(base*0.5*mult,ac,0,pow,sp.noac,true,sp.ac3) local d2=calc_dmg(base*0.5,ac,0,pow,sp.noac,true,sp.ac3) tdmg=d1+d2 else tdmg=calc_dmg(base*mult,ac,ev,pow,sp.noac,sp.nohit,sp.ac3) end tdmg=sh_damage(tdmg,e2.m) val=val+score_dmg(tdmg,e2.m,cdist(e2.x,e2.y)) end end end if val>0 then best_val=val;best_tx=best_target.x;best_ty=best_target.y end end elseif sp.n=="Ozocubu's Refrigeration" then -- Ozocubu's Refrigeration: hits all in LOS, damage reduced by adjacent enemies (max 2) local total_dmg=0 for _,e in ipairs(t) do local adj_count=0 for _,e2 in ipairs(t) do if e2~=e and math.abs(e2.x-e.x)<=1 and math.abs(e2.y-e.y)<=1 then adj_count=adj_count+1 end end local reduction=math.min(adj_count,2)*0.3 local r=get_res(e.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0.0 end if mult>0 then local base=get_spell_dmg(sp.n,pow) local ac=get_ac(e.m) local tdmg=calc_dmg(base*mult*(1-reduction),ac,0,pow,sp.noac,sp.nohit,sp.ac3) tdmg=sh_damage(tdmg,e.m) total_dmg=total_dmg+score_dmg(tdmg,e.m,cdist(e.x,e.y)) end end if total_dmg>0 then best_val=total_dmg;best_tx=0;best_ty=0 end end elseif sp.starburst then -- Starburst: 8 rays from player in cardinal+diagonal directions local base=get_spell_dmg(sp.n,pow) local val=0 local dirs={{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}} for _,d in ipairs(dirs) do for R=1,range do local rx,ry=d[1]*R,d[2]*R if is_solid(rx,ry) then break end local m2=monster.get_monster_at(rx,ry) if m2 and m2:attitude()==0 and not m2:is_firewood() then local r=get_res(m2,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0 end if mult>0 then local ac=get_ac(m2) local ev=get_ev(m2) local dmg=calc_dmg(base*mult,ac,ev,pow,sp.noac,sp.nohit,sp.ac3) dmg=sh_damage(dmg,m2) val=val+score_dmg(dmg,m2,cdist(rx,ry)) end end end end if val>0 then best_val=val;best_tx=0;best_ty=0 end elseif sp.magnavolt then -- Magnavolt: collect magnetised targets + clouds, then find best new target local mag_targets={} for x=-los,los do for y=-los,los do local m2=monster.get_monster_at(x,y) if m2 and m2:attitude()==0 and not m2:is_firewood() then local ok,st=pcall(function() return m2:status("covered in magnetic dust") end) if ok and st then mag_targets[#mag_targets+1]={x=x,y=y,m=m2} end end local ok2,cl=pcall(function() return view.cloud_at(x,y) end) if ok2 and cl=="magnetised fragments" then -- Check not already added as monster target local found=false for _,mt in ipairs(mag_targets) do if mt.x==x and mt.y==y then found=true break end end if not found then mag_targets[#mag_targets+1]={x=x,y=y,cloud=true} end end end end -- Calculate damage for a bolt aimed at (tx,ty): hits target + all enemies on path local base=get_spell_dmg(sp.n,pow) local function bolt_dmg_to_mon(m2,mx,my) local r=get_res(m2,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0 end if mult==0 then return 0 end local ac=get_ac(m2) local dmg=calc_dmg(base*mult,ac,0,pow,sp.noac,true,sp.ac3) dmg=sh_damage(dmg,m2) return score_dmg(dmg,m2,cdist(mx,my)) end local function mag_bolt_val(tx,ty) -- Bolt to (tx,ty) penetrates through all enemies on the path local val=0 local path=spells.path(sp.n,tx,ty) if path then for _,p in ipairs(path) do local pm=monster.get_monster_at(p[1],p[2]) if pm and pm:attitude()==0 and not pm:is_firewood() then val=val+bolt_dmg_to_mon(pm,p[1],p[2]) end end end -- Add the endpoint target itself (may not be in path) local tm=monster.get_monster_at(tx,ty) if tm and tm:attitude()==0 and not tm:is_firewood() then -- Check not already counted via path local counted=false if path then for _,p in ipairs(path) do if p[1]==tx and p[2]==ty then counted=true break end end end if not counted then val=val+bolt_dmg_to_mon(tm,tx,ty) end end return val end -- Existing magnetised damage (bolts to all current targets, each penetrating) local existing_val=0 for _,mt in ipairs(mag_targets) do existing_val=existing_val+mag_bolt_val(mt.x,mt.y) end -- Try each non-magnetised enemy as new target for _,e in ipairs(t) do if can_reach(e.x,e.y,range) then local already_mag=false for _,mt in ipairs(mag_targets) do if mt.x==e.x and mt.y==e.y then already_mag=true break end end -- New target gets bolt (penetrating) + all existing magnetised get bolts too local new_bolt=mag_bolt_val(e.x,e.y) local val=existing_val+new_bolt -- Ramp-up bonus: magnetising targets builds future value -- Even vs 1 target, the noise draws enemies who arrive pre-magnetised -- 1.5x base bonus (investing in future stacking) + 0.3 per existing mag target val=val*(1.5+#mag_targets*0.3) if val>best_val then best_val=val;best_tx=e.x;best_ty=e.y end end end -- If all enemies already magnetised, pick highest-damage one as target if best_val==0 and #mag_targets>0 then for _,mt in ipairs(mag_targets) do if not mt.cloud and can_reach(mt.x,mt.y,range) then local val=existing_val if val>best_val then best_val=val;best_tx=mt.x;best_ty=mt.y end end end end elseif sp.clutch then -- Borgnjor's Vile Clutch: bolt constricts all enemies on path -- Score = sum of constriction DoT value for each non-constricted enemy hit -- Prioritize paths that constrict the most dangerous, closest enemies -- Duration: 4 + random_range(pow/35, pow/25+1); avg ~ 4.5 + pow/29 local constr_turns=4.5+pow/29 local constr_dmg=get_spell_dmg(sp.n,pow)*constr_turns -- Try each enemy as a bolt direction; score all enemies on the path for _,e in ipairs(t) do if can_reach(e.x,e.y,range) and not beam_blocked_by_friendly(sp.n,e.x,e.y) then local val=0 local path=spells.path(sp.n,e.x,e.y,0,0,false) local hits={} if path then for _,p in ipairs(path) do local pm=monster.get_monster_at(p[1],p[2]) if pm and pm:attitude()==0 and not pm:is_firewood() then hits[#hits+1]={m=pm,x=p[1],y=p[2]} end end end -- Score each hit: skip already constricted (check target_desc for "onstrict") -- EV synergy now handled globally in get_ev() (-10 EV for constricted monsters) for _,h in ipairs(hits) do local hdesc=h.m:target_desc() or "" if not string.find(hdesc,"onstrict") then local ac=get_ac(h.m) local dmg=calc_dmg(constr_dmg,ac,0,pow,false,true,false) val=val+score_dmg(dmg,h.m,cdist(h.x,h.y)) end end if val>best_val then best_val=val;best_tx=e.x;best_ty=e.y end end end elseif sp.fcloud then -- Freezing Cloud: smite-targeted cloud, radius 2 -- Score by enemies covered + corridor blocking bonus -- Safe to self-cast at rC+3 local cloud_turns=3 local cloud_dmg=get_spell_dmg(sp.n,pow)*cloud_turns local cloud_r=2 local player_immune=(you.res_cold()>=3) -- Gather candidate cloud centers: on enemies, between player and enemies, and on player local centers={} for _,e in ipairs(t) do centers[#centers+1]={x=e.x,y=e.y} -- Also try midpoint between player and enemy (corridor blocking) local mx=math.floor(e.x/2+0.5) local my=math.floor(e.y/2+0.5) if (mx~=0 or my~=0) and not is_solid(mx,my) and cdist(mx,my)<=range then centers[#centers+1]={x=mx,y=my} end end if player_immune then centers[#centers+1]={x=0,y=0} end -- Score each center for _,c in ipairs(centers) do if cdist(c.x,c.y)<=range and not is_solid(c.x,c.y) and (player_immune or cdist(c.x,c.y)>cloud_r) and not has_friendly(c.x,c.y,cloud_r) then local val=0 -- Count enemies in cloud radius, skip those already in freezing cloud for _,e2 in ipairs(t) do local d=math.max(math.abs(e2.x-c.x),math.abs(e2.y-c.y)) local cl=view.cloud_at(e2.x,e2.y) local in_fcloud=cl and string.find(cl,"freezing") if d<=cloud_r and not in_fcloud then local r=get_res(e2.m,sp.e) if r<3 then -- rC+3 immune local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0.25 end local dmg=cloud_dmg*mult val=val+score_dmg(dmg,e2.m,cdist(e2.x,e2.y)) end end end -- Corridor bonus: count solid tiles adjacent to cloud center -- More walls = narrower passage = better zone control local walls=0 for dx=-1,1 do for dy=-1,1 do if (dx~=0 or dy~=0) and is_solid(c.x+dx,c.y+dy) then walls=walls+1 end end end if walls>=2 then val=val*(1+walls*0.15) end if val>best_val then best_val=val;best_tx=c.x;best_ty=c.y end end end elseif sp.mortar then -- Hellfire Mortar: bolt path creates lava (blocks non-flying), summons mortar -- Score = enemies on path damage + lava blocking bonus for nearby non-flying enemies -- Mortar fires ~path_len*1.5 turns; use conservative 3 shots estimate local shots=3 local base_dmg=get_spell_dmg(sp.n,pow)*shots for _,e in ipairs(t) do if can_reach(e.x,e.y,range) and not beam_blocked_by_friendly(sp.n,e.x,e.y) then local val=0 local path=spells.path(sp.n,e.x,e.y) local path_tiles={} if path then for _,p in ipairs(path) do path_tiles[#path_tiles+1]={x=p[1],y=p[2]} -- Score enemies on the path (mortar will fire at them) local pm=monster.get_monster_at(p[1],p[2]) if pm and pm:attitude()==0 and not pm:is_firewood() then local r=get_res(pm,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0.25 elseif r>=3 then mult=0 end if sp.halfres then mult=0.5+mult*0.5 end if mult>0 then local ac=get_ac(pm) local ev=get_ev(pm) local dmg=calc_dmg(base_dmg*mult,ac,ev,pow,false,false,false) val=val+score_dmg(dmg,pm,cdist(p[1],p[2])) end end end end -- Also score endpoint enemy local has_end=false if path then for _,p in ipairs(path) do if p[1]==e.x and p[2]==e.y then has_end=true break end end end if not has_end then local r=get_res(e.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0.25 elseif r>=3 then mult=0 end if sp.halfres then mult=0.5+mult*0.5 end if mult>0 then local ac=get_ac(e.m) local ev=get_ev(e.m) local dmg=calc_dmg(base_dmg*mult,ac,ev,pow,false,false,false) val=val+score_dmg(dmg,e.m,cdist(e.x,e.y)) end end -- Lava blocking bonus: count nearby non-path enemies that would be blocked -- Lava blocks ground movement, so non-flying enemies near path get bonus if #path_tiles>=2 then local lava_bonus=0 for _,e2 in ipairs(t) do -- Check if enemy is adjacent to lava path (would be blocked) for _,pt in ipairs(path_tiles) do local d=math.max(math.abs(e2.x-pt.x),math.abs(e2.y-pt.y)) if d<=1 then -- Non-flying enemies blocked by lava get bonus local hdesc=e2.m:target_desc() or "" local flying=string.find(hdesc,"lying") or string.find(hdesc,"evitat") if not flying then lava_bonus=lava_bonus+0.3 end break end end end val=val*(1+lava_bonus) end -- Corridor bonus: lava in narrow passages is more valuable local walls=0 for _,pt in ipairs(path_tiles) do for dx=-1,1 do for dy=-1,1 do if (dx~=0 or dy~=0) and is_solid(pt.x+dx,pt.y+dy) then walls=walls+1 end end end end if #path_tiles>0 then walls=walls/#path_tiles end if walls>=3 then val=val*1.5 elseif walls>=2 then val=val*1.2 end if val>best_val then best_val=val;best_tx=e.x;best_ty=e.y end end end elseif sp.ood then -- Orb of Destruction: distance scaling, obstacle check, adjacent orb check -- Don't fire if there's already an OoD adjacent to player (collision risk) local adj_ood=false for dx=-1,1 do for dy=-1,1 do local m2=monster.get_monster_at(dx,dy) if m2 and m2:name()=="orb of destruction" and m2:attitude()>0 then adj_ood=true end end end if not adj_ood then local base_M=5+pow/12 for _,e in ipairs(t) do if can_reach(e.x,e.y,range) then local dist=math.max(math.abs(e.x),math.abs(e.y)) -- Enemy likely steps toward player before orb arrives if dist>3 then dist=dist-1 end local scale=1.0 if dist<4 then scale=dist*0.3 end local ac=get_ac(e.m) local dmg=calc_dmg(9*(base_M*scale+1)/2,ac,0,pow,false,true,false) dmg=sh_damage(dmg,e.m) -- Check path for obstacles (walls, firewood, other monsters blocking) local blocked=false local path=spells.path(sp.n,e.x,e.y) if path then for _,p in ipairs(path) do if not (p[1]==e.x and p[2]==e.y) then if is_solid(p[1],p[2]) then blocked=true break end local pm=monster.get_monster_at(p[1],p[2]) if pm and pm:is_firewood() then blocked=true break end if pm and pm:attitude()>0 and pm:name()~="battlesphere" and pm:name()~="orb of destruction" then blocked=true break end end end end if not blocked and dmg>0 then local val=score_dmg(dmg,e.m,cdist(e.x,e.y)) if val>best_val then best_val=val;best_tx=e.x;best_ty=e.y end end end end end else -- Shock wall-bounce: try wall tiles near enemies as aim directions for ricochets local shock_wall_targets={} if sp.n=="Shock" then local tried={} for _,e in ipairs(t) do for dx=-2,2 do for dy=-2,2 do local wx,wy=e.x+dx,e.y+dy local wk=wx..","..wy if not tried[wk] and is_solid(wx,wy) and can_reach(wx,wy,range) then tried[wk]=true local wh,we=shock_bounce_hits(wx,wy,pow) local any_hit=false for _ in pairs(we) do any_hit=true break end if any_hit then shock_wall_targets[#shock_wall_targets+1]={x=wx,y=wy,hits=wh,enemies=we} end end end end end end for _,e in ipairs(t) do if can_reach(e.x,e.y,range) and not ((sp.beam or sp.penetrate) and beam_blocked_by_friendly(sp.n,e.x,e.y)) then local r=get_res(e.m,sp.e) local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r==2 then mult=0 elseif r>=3 then mult=0.0 end if sp.undead and e.m:holiness()~="undead" then mult=0 end local hp=get_hp(e.m) local base=get_spell_dmg(sp.n,pow) -- Sticky Flame: strip DoT if target already burning (doesn't stack) if sp.n=="Sticky Flame" then local ok_burn,is_burn=pcall(function() return e.m:status("covered in liquid flames") end) if ok_burn and is_burn then base=2*(4+pow/9+1)/2 end -- impact only end if sp.n=="Airstrike" then local empty=count_empty(e.x,e.y) base=base+empty*2 end local ac=get_ac(e.m) local ev=get_ev(e.m) -- Use game API evasion if available local hit_pct=evasion_check(e.m,sp.n) local use_ev=ev if hit_pct and not sp.nohit then use_ev=0 end -- will apply hit_pct separately local dmg if sp.halfres then local dmg1=calc_dmg(base*0.5*mult,ac,use_ev,pow,sp.noac,sp.nohit,sp.ac3) local dmg2=calc_dmg(base*0.5,ac,use_ev,pow,sp.noac,sp.nohit,sp.ac3) dmg=dmg1+dmg2 else dmg=calc_dmg(base*mult,ac,use_ev,pow,sp.noac,sp.nohit,sp.ac3) end if hit_pct and not sp.nohit then dmg=dmg*hit_pct end dmg=sh_damage(dmg,e.m) -- Shock bounce: compute full ricochet path once, reuse for scoring local shock_hits,shock_enemies,shock_ekey if sp.n=="Shock" then shock_hits,shock_enemies=shock_bounce_hits(e.x,e.y,pow) shock_ekey=e.x..","..e.y local primary_hits=shock_hits[shock_ekey] or 1 dmg=dmg*primary_hits end if sp.n=="Iskenderun's Mystic Blast" then -- knockback: push enemy up to 2+pow/50 tiles away from player -- collision with wall or another monster deals 2d(1+pow/10) extra local kd=2+pow/50 local dx=e.x>0 and 1 or (e.x<0 and -1 or 0) local dy=e.y>0 and 1 or (e.y<0 and -1 or 0) local kx,ky=e.x,e.y local col_dmg=get_spell_dmg("IMB_collision",pow) for _=1,math.floor(kd) do local nx,ny=kx+dx,ky+dy if is_solid(nx,ny) then dmg=dmg+calc_dmg(col_dmg,get_ac(e.m),0,pow,false,true) break end local om=monster.get_monster_at(nx,ny) if om and om:attitude()==0 and not om:is_firewood() then local cd=calc_dmg(col_dmg,get_ac(e.m),0,pow,false,true) dmg=dmg+cd local cd2=calc_dmg(col_dmg,get_ac(om),0,pow,false,true) dmg=dmg+cd2 break end kx=nx;ky=ny end end -- Note: penetrate intermediate damage is scored per-monster below, not added to primary dmg if dmg>0 then local val=score_dmg(dmg,e.m,cdist(e.x,e.y)) -- Shock bounce: add score for all enemies hit on bounce path if sp.n=="Shock" and shock_hits then local bounce_count=0 for key,info in pairs(shock_enemies) do if key~=shock_ekey then local hits=shock_hits[key] local r2=get_res(info.m,sp.e) local m2=1.0 if r2==-1 then m2=1.5 elseif r2==1 then m2=0.5 elseif r2==2 then m2=0 elseif r2>=3 then m2=0.0 end if m2>0 then local b2=get_spell_dmg(sp.n,pow) local hitC=evasion_check(info.m,sp.n) or math.max(0,1.0-get_ev(info.m)/50.0) local d2 if sp.halfres then local d2a=calc_dmg(b2*0.5*m2,get_ac(info.m),0,pow,sp.noac,true,sp.ac3) local d2b=calc_dmg(b2*0.5,get_ac(info.m),0,pow,sp.noac,true,sp.ac3) d2=(d2a+d2b)*hitC else d2=calc_dmg(b2*m2,get_ac(info.m),0,pow,sp.noac,true,sp.ac3)*hitC end d2=sh_damage(d2,info.m)*hits val=val+score_dmg(d2,info.m,cdist(info.x,info.y)) bounce_count=bounce_count+1 end end end -- Bounce alignment bonus: more enemies hit = stronger preference if bounce_count>=1 then val=val*(1.0+bounce_count*0.5) end end if sp.penetrate and sp.n~="Shock" then local reachChance2=1.0 local others=monsters_on_path(sp.n,e.x,e.y) local lineup_count=0 for _,other in ipairs(others) do local r2=get_res(other.m,sp.e) local m2=1.0 if r2==-1 then m2=1.5 elseif r2==1 then m2=0.5 elseif r2==2 then m2=0 elseif r2>=3 then m2=0.0 end if m2>0 then local b2=get_spell_dmg(sp.n,pow) local hitC=evasion_check(other.m,sp.n) or math.max(0,1.0-get_ev(other.m)/50.0) local d2=calc_dmg(b2*m2,get_ac(other.m),0,pow,sp.noac,true,sp.ac3)*hitC*reachChance2 d2=sh_damage(d2,other.m) val=val+score_dmg(d2,other.m,cdist(other.x,other.y)) reachChance2=reachChance2*(1-hitC) lineup_count=lineup_count+1 end end -- Bolt alignment bonus: prefer penetrating bolts when enemies line up if lineup_count>=1 then val=val*(1.0+lineup_count*0.3) end end -- Soft penalty for single-target spells needing many casts to kill if not sp.aoe and not sp.penetrate and not sp.beam then local total_dmg=dmg+(has_bsph and not sp.no_bsph and bsph_raw or 0) local kn=math.ceil(hp/math.max(total_dmg,1)) if kn>4 then val=val*math.max(0.25,0.8^(kn-4)) end end if val>best_val then best_val=val;best_tx=e.x;best_ty=e.y end end end end -- Shock wall-bounce: score wall-aimed ricochets that may outperform direct shots if sp.n=="Shock" and #shock_wall_targets>0 then for _,wt in ipairs(shock_wall_targets) do local val=0 local bounce_count=0 for key,info in pairs(wt.enemies) do local hits=wt.hits[key] local r2=get_res(info.m,sp.e) local m2=1.0 if r2==-1 then m2=1.5 elseif r2==1 then m2=0.5 elseif r2==2 then m2=0 elseif r2>=3 then m2=0.0 end if m2>0 then local b2=get_spell_dmg(sp.n,pow) local hitC=evasion_check(info.m,sp.n) or math.max(0,1.0-get_ev(info.m)/50.0) local d2 if sp.halfres then local d2a=calc_dmg(b2*0.5*m2,get_ac(info.m),0,pow,sp.noac,true,sp.ac3) local d2b=calc_dmg(b2*0.5,get_ac(info.m),0,pow,sp.noac,true,sp.ac3) d2=(d2a+d2b)*hitC else d2=calc_dmg(b2*m2,get_ac(info.m),0,pow,sp.noac,true,sp.ac3)*hitC end d2=sh_damage(d2,info.m)*hits val=val+score_dmg(d2,info.m,cdist(info.x,info.y)) bounce_count=bounce_count+1 end end if bounce_count>=1 then val=val*(1.0+bounce_count*0.5) end if val>best_val then best_val=val;best_tx=wt.x;best_ty=wt.y end end end end if best_val>0 then local penalty=1.0 if sp.channeled and sp.n=="Flame Wave" then penalty=0.33 end if sp.slow then -- 1.5 turn cast: scale penalty by closest enemy distance local min_d=99 for _,e in ipairs(t) do local d=cdist(e.x,e.y) if d2 then best_val=best_val*0.5 end end -- Tactical modifiers: depth, corridor, fight type local tac=1.0 -- Depth scaling: early game penalizes expensive, late game penalizes cheap if depth_tier==1 and cost>=7 then tac=tac*0.6 elseif depth_tier==3 and cost<=3 then tac=tac*0.5 end -- Corridor: boost beam/penetrate, penalize wide AoE if in_corridor then if sp.beam or sp.penetrate then tac=tac*1.3 elseif sp.aoe and sp.aoe>=2 then tac=tac*0.7 end end -- Boss fight: boost single-target burst if boss_fight and not sp.aoe then tac=tac*1.3 end -- Swarm fight: boost AoE if swarm_fight and sp.aoe then tac=tac*1.3 end local eff_val=best_val*penalty*tac/math.sqrt(cost) local entry={n=sp.n,v=eff_val,x=best_tx,y=best_ty,c=cost,ch=sp.channeled,manual=sp.mortar,ep=not (sp.clutch or sp.penetrate)} if cost<=mp then table.insert(candidates,entry) elseif cost==mp+1 then table.insert(almost,entry) end end end end end -- 2-Turn Combo Lookahead: boost setup spells if payoff spell benefits next turn -- Poison → Ignite Poison combo local has_ignite=spells.memorised("Ignite Poison") if has_ignite then local ig_pow=get_pow("Ignite Poison") local ig_cost=get_cost("Ignite Poison") local ig_base=get_spell_dmg("Ignite Poison",ig_pow) -- Count how many enemies are NOT currently poisoned (Ignite can't hit them yet) local unpoisoned={} for _,e in ipairs(t) do local r=get_res(e.m,4) -- poison resistance if r<2 then -- not poison-immune local ok,st=pcall(function() return e.m:status("poisoned") end) if not (ok and st) then unpoisoned[#unpoisoned+1]=e end end end if #unpoisoned>0 then -- For each poison-applying candidate, calculate Ignite Poison payoff next turn for _,c in ipairs(candidates) do local is_poison_setup=(c.n=="Poisonous Vapours" or c.n=="Mercury Arrow") local is_aoe_poison=(c.n=="Olgreb's Toxic Radiance") if is_poison_setup or is_aoe_poison then local combo_val=0 local targets_hit=is_aoe_poison and unpoisoned or {} -- Single-target poison: only the targeted enemy gets poisoned if is_poison_setup then for _,e in ipairs(unpoisoned) do if e.x==c.x and e.y==c.y then targets_hit={e};break end end end -- Simulate Ignite Poison damage on newly-poisoned targets for _,e in ipairs(targets_hit) do local r=get_res(e.m,3) -- fire resistance for Ignite local mult=1.0 if r==-1 then mult=1.5 elseif r==1 then mult=0.5 elseif r>=2 then mult=0 end if mult>0 then local ac=get_ac(e.m) local ev=get_ev(e.m) local dmg=calc_dmg(ig_base*mult,ac,ev,ig_pow,false,false,false) combo_val=combo_val+score_dmg(dmg,e.m,cdist(e.x,e.y)) end end -- Add discounted future value (0.7 = next turn uncertainty) -- MP check: can we afford setup + payoff next turn? (mp_regen ≈ mp_max/10) if combo_val>0 and (mp-c.c+mp_regen)>=ig_cost then c.v=c.v+combo_val*0.7/math.sqrt(ig_cost) end end end end end -- BVC → EV reduction combo: constriction helps all future non-nohit spells for _,c in ipairs(candidates) do if c.n=="Borgnjor's Vile Clutch" then -- Find best non-nohit spell for EV synergy calculation local best_followup=0 for _,c2 in ipairs(candidates) do if c2.n~=c.n and c2.v>best_followup then best_followup=c2.v end end -- Constriction gives -10 EV; rough estimate: 10/50 = 20% more hits -- Apply to best follow-up spell score if best_followup>0 then c.v=c.v+best_followup*0.2*0.7 -- 20% hit improvement, 0.7 discount end end end table.sort(candidates,function(a,b) return a.v>b.v end) local b=candidates[1] -- If top candidate is channeled but not significantly better than non-channeled, prefer non-channeled if b and b.ch and #candidates>1 and b.n=="Flame Wave" then local best_non_ch=nil for _,c in ipairs(candidates) do if not c.ch then best_non_ch=c;break end end if best_non_ch and b.v < best_non_ch.v * 1.2 then b=best_non_ch end end -- If already channeling, don't override unless significantly better (2x) if recently_cast("Flame Wave") or recently_cast("Searing Ray") then -- If best option is the channeled spell itself, just continue if b and (b.n == "Flame Wave" or b.n == "Searing Ray") then crawl.mpr("Cont") return end -- Check if any option is significantly better (2x) local has_better = false for _,c in ipairs(candidates) do if c.v > 20 * 2 then -- assume current channel does 20 dmg/turn has_better = true break end end if not has_better then crawl.mpr("Cont") return -- don't interrupt channeling end end if not b then for _,c in ipairs(candidates) do local cost_diff=mp-c.c if cost_diff>=0 or cost_diff>=-1 then b=c break end end end -- Check if a significantly better spell is 1 MP away — wait instead of casting weaker if b and #almost>0 then table.sort(almost,function(a,ab) return a.v>ab.v end) if almost[1].v > b.v*1.5 then crawl.mpr("Wait 1MP for "..almost[1].n) return end end if b then -- Check MP reserve for escape spells if reserve_mp>0 and (mp-b.c)