############### ### Display ### ############### view_delay = 300 hp_colour = 100:green, 99:lightgray, 75:yellow, 50:lightred, 25:red mp_colour = 100:green, 99:lightgray, 75:yellow, 50:lightred, 25:red hp_warning = 50 monster_list_colour = monster_list_colour += friendly:green,neutral:brown monster_list_colour += good_neutral:brown,strict_neutral:brown monster_list_colour += trivial:darkgrey,easy:lightgrey monster_list_colour += tough:yellow,nasty:lightred ############### ### Console ### ############### # Enable use of darkgrey. bold_brightens_foreground=true equip_bar = true ## For item_glyph, subsequent matches override previous ones. item := item_glyph ## Reasonable defaults item += potion:lightgrey item += scroll:lightgrey item += wand:lightgrey item += dangerous_item:blue item += useless_item:darkgrey ### Potions ### item += potions? of.*(curing|flight):lightgrey : if you.race() == "Vampire" then item += potions? of blood:lightgrey : end item += potions? of.*berserk:brown item += potions? of.*might:yellow item += potions? of.*cancellation:cyan item += potions? of.*ambrosia:blue item += potions? of.*experience:lightred item += potions? of.*heal wounds:white item += potions? of.*(resistance|agility):green item += potions? of.*(haste|invisibility):lightgreen item += potions? of.*mutation:lightcyan item += potions? of.*brilliance:magenta item += potions? of.*magic:lightmagenta ### Scrolls ### : if you.race() == "Vampire" or you.race() == "Mummy" : or you.race() == "Ghoul" then item += scroll.*holy word:darkgrey item += scroll.*torment:brown : else item += scroll.*holy word:brown item += scroll.*torment:darkgrey : end item += scroll.*acquirement:white item += scroll.*(summoning):yellow item += scroll.*identify:cyan item += scroll.*magic mapping:lightcyan item += scroll.*(silence|vulnerability|immolation):blue item += scroll.*remove curse:lightgrey item += scroll.*(fog|teleport):green item += scroll.*(fear|blink):lightgreen item += scroll.*(recharging):magenta item += scroll.*(enchant|brand weapon):lightmagenta ### Wands ### # Sometimes dangerous item += wand of.*random effects:blue # Single target piercing. item += wand of.*lightning:brown item += wand of.*acid:yellow item += wand of.*flame:lightgrey item += wand of.*clouds:white item += wand of.*digging:green # Single-turn AOE item += wand of.*iceblast:cyan item += wand of.*scattershot:lightcyan item += wand of.*(disintegration|confusion|polymorph):magenta item += wand of.*(paralysis|enslavement):lightmagenta ### General Identification ### item += (identified|known).*(jewellery|magical staff):brown item += identified.*spellbook:magenta item += unidentified.*(potion|scroll|jewellery|wand|magical staff).*:lightblue item += unidentified.*book:lightmagenta item += manual:lightcyan item += manual of:cyan ### Jewellery ### item += known.*(ring of (dexterity|strength|intelligence|slaying|evasion|protection(?! from))|amulet of reflection):magenta item += identified.*artefact.*(jewellery):white item += unidentified.*artefact.*(jewellery).*:lightmagenta item += (a )?stones?$:lightgray ### Food ### item += chunks?.*flesh:lightgrey item += evil_eating.*chunk:red item += ( ration):green # Want this to override anything above item += useless:darkgrey # Only mark these types when forbidden; for other types it looks odd. item += forbidden.*(potion|scroll|food):red mons := mon_glyph # Monsters that can be easy to miss mons ^= * : ✱ mons ^= dancing weapon : cyan { mons ^= spectral weapon : lightcyan { # Classed Demonspawn mon += blood saint:9 mon += warmonger:9 mon += corrupter:9 mon += black sun:9 # Wall glyphs; requires a font and terminal that can render these. feature += crystal wall {░} feature += stone wall {▒} feature += metal wall {▓} feature += unnaturally hard rock wall {█} feature += altar {⚑} ############# ### Tiles ### ############# tile_window_width = 1300 tile_window_height = 700 tile_full_screen = false tile_map_pixels = 3 ################### ### Menu Colors ### ################### # These should match the item_glyph colours exactly when possible. # For menu_colour, the first match ignores subsequent matches. menu := menu_colour menu = # Many of the entries below come from dat/defaults/menu_colours.txt, # which we have to duplicate here since we want only some of the # entries from that file. menu += notes:white:Reached XP level ### General Inventory### # Items currently not affecting you. menu += darkgrey:(melded) # Items disliked by your god. menu += red:forbidden # Useless items, comes here to override artefacts etc. menu += darkgrey:.*useless.* # Handle cursed and equipped items early to override other colour settings. menu += lightred:.*equipped.* cursed menu += red: (a|the) cursed menu += inventory:lightgreen:.*equipped.* # Colouring of autoinscribed god gifts menu += pickup:lightred:god gift # Highlight (partly) selected items menu += inventory:white:\w \+\s menu += inventory:white:\w \#\s ### Food ### # Color chunks, put ordinary ones on lightgrey. menu += red:evil_eating.*chunk menu += blue:mutagenic.*chunk menu += lightgrey: +(chunks?$|chunks? +) # Other food menu += green:( ration) ### Potions ### : if you.race() == "Vampire" then menu += lightgrey:potions? of blood : end menu += lightgrey:potions? of.*(curing|flight) menu += brown:potions? of.*berserk menu += yellow:potions? of.*might menu += cyan:potions? of.*cancellation menu += blue:potions? of.*(lignification|ambrosia) menu += lightred:potions? of.*experience menu += white:potions? of.*heal wounds menu += green:potions? of.*(resistance|agility) menu += lightgreen:potions? of.*(haste|invisibility) menu += lightcyan:potions? of.*mutation menu += magenta:potions? of.*brilliance menu += lightmagenta:potions? of.*magic ### Scrolls ### : if you.race() == "Vampire" or you.race() == "Mummy" : or you.race() == "Ghoul" then menu += darkgrey:scroll.*holy word menu += brown:scroll.*torment : else menu += brown:scroll.*holy word menu += darkgrey:scroll.*torment : end menu += white:scroll.*acquirement menu += yellow:scroll.*(summoning) menu += cyan:scroll.*identify menu += lightcyan:scroll.*magic mapping menu += blue:scroll.*(noise|silence|vulnerability|immolation) menu += lightgrey:scroll.*remove curse menu += green:scroll.*(fog|teleport) menu += lightgreen:scroll.*(fear|blink) menu += magenta:scroll.*(recharging) menu += lightmagenta:scroll.*(enchant|brand weapon) ### Wands ### # Can be harmful menu += blue:wand of.*random effects # Damaging beam. menu += brown:wand of.*flame menu += yellow:wand of.*acid menu += white:wand of.*clouds menu += green:wand of.*digging # Explosion menu += cyan:wand of.*iceblast menu += lightcyan:wand of.*scattershot # MR-checking menu += magenta:wand of.*(disintegration|polymorph) menu += lightmagenta:wand of.*(paralysis|enslavement) ### General identification ### menu += cyan:manual of menu += lightcyan:manual menu += lightmagenta:unidentified.*artefact.* menu += white:.*artefact.* menu += lightblue:unidentified .*(potion|scroll|wand|jewellery|spellbook|rod|magical staff) menu += magenta:.*known .*(ring of (dexterity|strength|intelligence|slaying|evasion|protection(?! from))|amulet of reflection) menu += inventory:lightgray:.*(book|jewellery|magical staff) # Ego items menu += lightblue:unidentified.*weapon.*(runed|glowing|enchanted) menu += lightblue:unidentified.*armour.*(runed|glowing|embroidered|shiny|dyed) ################# ### Interface ### ################# default_manual_training = true easy_eat_chunks = true equip_unequip = true sort_menus = true:equipped,identified,basename,art,ego,glowing,qualname,curse,charged,slot ability_menu = false autofight_stop = 50 autofight_caught = true autofight_wait = false auto_butcher = true bindkey = [~] CMD_LUA_CONSOLE ############################ ### Travel & Exploration ### ############################ rest_wait_both = true auto_sacrifice = true travel_delay = -1 explore_delay = -1 rest_delay = -1 show_travel_trail = false explore_stop += greedy_items wall_jump_move = false ################## ### Autopickup ### ################## # Add staves, rods, misc; note you can't use += with this option. autopickup = $?!:"/%}|\ ae := autopickup_exceptions ae += scrolls? of silence ae += >(crystal ball of energy|disc of storm) # Don't ever need a second stave ae += staff of .* ################ ### Messages ### ################ channel.multiturn = mute msc := message_colour msc += mute:returns to your side msc += mute:a demon appears msc += lightgrey:The deck of cards disappears msc += mute:puff of smoke msc += mute:carefully avoids msc += mute:is recalled msc += mute:wall.*burn.*your msc += mute:dissolves? into (sparkling lights|shadows) msc += mute:(crumbles|melts) away\. msc += mute:(merges|forms) itself .* the air msc += mute:you swap places msc += mute:your.*(looks stronger|shudders|resists) msc += mute:your.*(stumbles backwards|holds.*ground) msc += mute:your.*(blinks|safely over) msc += mute:(phases out.*|misses) (your|something).* msc += mute:your.*(picks up|drops) msc += mute:your.*basks in the mutagenic energy msc += mute:your.*(struggles|tears|pulls away).*(web|net) more := force_more_message : if you.god() == "Ashenzari" then more += You have a vision of.*gates? : end more += The mighty Pandemonium lord .* resides here # Interrupts more += You don't .* that spell more += You fail to use your ability more += You miscast.*(Blink|Borgnjor|Door|Invisibility) more += You can't (read|drink|do) more += You cannot .* while unable to breathe more += You cannot .* in your current state more += when .*silenced more += too confused more += There's something in the way more += There's nothing to (close|open) nearby more += not good enough to have a special ability more += You are too berserk more += no means to grasp more += That item cannot be evoked more += This wand has no charges more += You are held in a net more += You don't have any such object more += You can't unwield more += enough magic points more += You don't have the energy to cast that spell more += You are unable to access your magic # Bad things more += Your surroundings flicker more += You cannot teleport right now more += A sentinel's mark forms upon you more += (blundered into a|invokes the power of) Zot more += enter a teleport trap more += Ouch! That really hurt! more += dispelling energy hits you more += You are blasted by holy energy! more += You are (blasted|electrocuted)! more += You are.*(confused|poisoned) more += flesh rotting more += (starving|devoid of blood) more += god:(sends|finds|silent|anger) more += You feel a surge of divine spite more += disloyal to dabble more += lose consciousness more += You are too injured to fight blindly more += calcifying dust hits more += Space warps.*around you more += Space bends around you more += watched by something more += flickers and vanishes! more += doesn't seem very happy more += is no longer charmed # Hell effects more += hell_effect: # Expiring effects more += You feel yourself slow down more += You are starting to lose your buoyancy more += Your hearing returns more += Your transformation is almost over more += You have a feeling this form more += You feel yourself come back to life more += time is quickly running out more += life is in your own hands more += Your shroud falls apart more += You start to feel a little slower more += You feel less protected from missiles # Others more += You have reached level more += You rejoin the land of the living more += You have finished (your manual|forgetting about) more += Your scales start more += You feel monstrous more += Jiyva alters your body : if you.god() == "Xom" then more += god: : end # 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 # Paralysis/Petrify/Banish more += (orc sorcerer|(?> note_items += experience,of resistance, Archmagi note_items += crystal plate armour,pearl dragon scales note_items += artefact note_messages += You feel monstrous note_messages += You pass through the gate note_messages += cast .* Abyss # Noteable monsters # Undead note_monsters += ancient lich,curse skull,curse toe,greater mummy # Depths note_monsters += caustic shrike # Special hall_of_zot spawns note_monsters += killer klown,electric golem,orb of fire dump_message_count = 30 dump_order = header,hiscore,stats,misc,mutations,skills,spells,inventory dump_order += overview,screenshot,monlist,messages,skill_gains,action_counts dump_order += vaults,notes,kills,turns_by_place,xp_by_level ############ ## Macros ## ############ macros += M \{-250} ===one_turn_rest macros += M \{-247} ===start_resting ########### ### Lua ### ########### { -- Equipment autopickup (by Medar and various others) local function pickup_equipment(it, name) if it.is_useless then return end local class = it.class(true) if class == "armour" then local good_slots = {cloak="Cloak", helmet="Helmet", gloves="Gloves", boots="Boots"} st, _ = it.subtype() -- Autopickup found aux armour if 1) we don't have any or 2) it's artefact, -- or 3) if we don't have artefact or ego armour, and the found armour is -- ego. if good_slots[st] ~= nil then if good_slots[st] == "Gloves" and you.has_claws() > 0 then return end if it.artefact then return true end local cur = items.equipped_at(good_slots[st]) if cur == nil then return true end if cur.branded or cur.artefact then return end if it.branded then return true end -- Autopickup found body armour of the same kind we're wearing, according -- to conditions (2) and (3) above used for aux slots. elseif st == "body" then local cur = items.equipped_at("armour") if cur == nil then return end if cur.name("qual") ~= it.name("qual") then return end if it.artefact then return true end if cur.branded or cur.artefact then return end if it.branded then return true end end end return end add_autopickup_func(pickup_equipment) -- Spellcasting spam reduction by monqy local function generic_cast_spell(cmd) crawl.mpr('Cast which spell?') crawl.flush_prev_message() crawl.process_keys(cmd) end function cast_spell() generic_cast_spell('z') end function force_cast_spell() generic_cast_spell('Z') end } # Note: My final RC file has code from lua files found at: # https://github.com/gammafunk/dcss-rc/blob/master/README.md # starting from the line below. { --------------------------- ---- Begin force_mores ---- --------------------------- -- See README.md for documentation. last_turn = you.turns() -- Each entry must have a 'name' field with a descriptive name, a 'pattern' -- field, a 'cond' field giving the condition type, and a 'cutoff' field giving -- the max value for where the force_more will apply. Possible values for -- 'cond' are xl and maxhp. -- -- The 'pattern' field's value can be either a regexp string or array of regexp -- strings matching the appropriate monster(s). Any values are joined by "|" to -- make a new force_more of the form: -- -- ((?!spectral )VALUE1|VALUE2|...)(?! (skeleton|zombie|simularcrum)).*into view". -- -- To allow derived undead forms of a monster to match, include 'spectral ' at -- the beginning of and/or ' (skeleton|zombie|simularcrum)' at the end of your -- pattern for that monster. fm_patterns = { -- Fast, early game Dungeon problems for chars with low mhp. {name = "30mhp", cond = "maxhp", cutoff = 30, pattern = "adder|hound"}, -- Dungeon monsters that can damage you for close to 50% of your mhp with a -- ranged attack. {name = "40mhp", cond = "maxhp", cutoff = 40, pattern = "orc priest|electric eel"}, {name = "60mhp", cond = "maxhp", cutoff = 60, pattern = "acid dragon|steam dragon|manticore"}, {name = "70mhp", cond = "maxhp", cutoff = 70, pattern = "centaur(?! warrior)|meliai|yaktaur(?! captain)"}, {name = "80mhp", cond = "maxhp", cutoff = 80, pattern = "gargoyle|orc (warlord|knight)"}, {name = "90mhp", cond = "maxhp", cutoff = 90, pattern = {"centaur warrior", "deep elf archer", "efreet", "molten gargoyle", "tengu conjurer"} }, {name = "110mhp", cond = "maxhp", cutoff = 110, pattern = {"centaur warrior", "deep elf (mage|knight)", "cyclops", "efreet", "molten gargoyle", "tengu conjurer", "yaktaur captain", "necromancer", "deep troll earth mage", "hell knight", "stone giant"} }, {name = "160mhp", cond = "maxhp", cutoff = 160, pattern = {"(fire|ice|quicksilver|shadow|storm) dragon", "(fire|frost) giant", "war gargoyle", "draconian (knight|stormcaller"} }, } -- end fm_patterns active_fm = {} -- Set to true to get a message when the fm change notify_fm = false -- Wrapper of crawl.mpr() that prints text in white by default. if not mpr then mpr = function (msg, color) if not color then color = "white" end crawl.mpr("<" .. color .. ">" .. msg .. "") end end function init_force_mores() for i,v in ipairs(fm_patterns) do active_fm[#active_fm + 1] = false end end function update_force_mores() local activated = {} local deactivated = {} local hp, maxhp = you.hp() for i,v in ipairs(fm_patterns) do local msg = nil if type(v.pattern) == "table" then for j, p in ipairs(v.pattern) do if msg == nil then msg = p else msg = msg .. "|" .. p end end else msg = v.pattern end msg = "(?= v.cutoff then action = "-" elseif not active_fm[i] and you.xl() < v.cutoff then action = "+" end elseif v.cond == "maxhp" then if active_fm[i] and maxhp >= v.cutoff then action = "-" elseif not active_fm[i] and maxhp < v.cutoff then action = "+" end end if action == "+" then activated[#activated + 1] = fm_name elseif action == "-" then deactivated[#deactivated + 1] = fm_name end if action ~= nil then local opt = "force_more_message " .. action .. "= " .. msg crawl.setopt(opt) active_fm[i] = not active_fm[i] end end if #activated > 0 and notify_fm then mpr("Activating force_mores: " .. table.concat(activated, ", ")) end if #deactivated > 0 and notify_fm then mpr("Deactivating force_mores: " .. table.concat(deactivated, ", ")) end end local last_turn = nil function force_mores() if last_turn ~= you.turns() then update_force_mores() last_turn = you.turns() end end init_force_mores() ------------------------- ---- End force_mores ---- ------------------------- ---------------------------- ---- Begin load_message ---- ---------------------------- -- See README.md for documentation. message_color = "white" -- Wrapper of crawl.mpr() that prints text in white by default. if not mpr then mpr = function (msg, color) if not color then color = "white" end crawl.mpr("<" .. color .. ">" .. msg .. "") end end function save_with_message() if you.turns() == 0 then crawl.sendkeys("S") return end crawl.formatted_mpr("Save game and exit?", "prompt") local res = crawl.getch() if not (string.char(res) == "y" or string.char(res) == "Y") then crawl.formatted_mpr("Okay, then.", "prompt") return end crawl.formatted_mpr("Leave a message: ", "prompt") local res = crawl.c_input_line() c_persist.message = res crawl.sendkeys(control("s")) end function load_message() if c_persist.message and c_persist.message ~= "nil" and c_persist.message ~= "" then mpr("MESSAGE: " .. c_persist.message, message_color) c_persist.message = nil end end ----------------------------------- ---- End leave message on save ---- ----------------------------------- } { ----------------------------- ---- Begin char_defaults ---- ----------------------------- -- See README.md for documentation. weapon_skills = {"Unarmed Combat", "Short Blades", "Long Blades", "Axes", "Maces & Flails", "Polearms", "Staves"} ranged_skills = {"Throwing", "Bows", "Crossbows", "Slings"} other_skills = {"Fighting", "Armour", "Dodging", "Shields", "Spellcasting", "Conjurations", "Hexes", "Charms", "Summonings", "Necromancy", "Translocations", "Transmutations", "Fire Magic", "Ice Magic", "Air Magic", "Earth Magic", "Poison Magic", "Invocations", "Evocations","Stealth"} skill_glyphs = { [1] = "+", [2] = "*" } chdat = nil char_combo = you.race() .. you.class() loaded_attempted = false -- Wrapper of crawl.mpr() that prints text in white by default. if not mpr then mpr = function (msg, color) if not color then color = "white" end crawl.mpr("<" .. color .. ">" .. msg .. "") end end function skill_message(prefix, skill, skill_type, value) local msg = "" if prefix then msg = prefix .. ";" end if skill_type then msg = msg .. skill_type .. "(" .. skill .. "):" .. value else msg = msg .. skill .. ":" .. value end return msg end function save_char_defaults(quiet) if you.class() == "Wanderer" then return end if not c_persist.char_defaults then c_persist.char_defaults = { } end c_persist.char_defaults[char_combo] = { } chdat = c_persist.char_defaults[char_combo] local msg = nil local have_weapon = false for _,sk in ipairs(weapon_skills) do if you.train_skill(sk) > 0 then chdat["Weapon"] = you.train_skill(sk) msg = skill_message(nil, sk, "Weapon", skill_glyphs[chdat["Weapon"]]) have_weapon = true break end end if not have_weapon then chdat["Weapon"] = nil end local have_ranged = false for _,sk in ipairs(ranged_skills) do if you.train_skill(sk) > 0 then chdat["Ranged"] = you.train_skill(sk) msg = skill_message(msg, sk, "Ranged", skill_glyphs[chdat["Ranged"]]) have_ranged = true break end end if not have_ranged then chdat["Ranged"] = nil end for _,sk in ipairs(other_skills) do if you.train_skill(sk) > 0 then chdat[sk] = you.train_skill(sk) msg = skill_message(msg, sk, nil, skill_glyphs[chdat[sk]]) else chdat[sk] = nil end end if not quiet then mpr("Saved default for " .. char_combo .. ": " .. msg) end end function have_defaults() return you.class() ~= "Wanderer" and c_persist.char_defaults ~= nil and c_persist.char_defaults[char_combo] ~= nil end function load_char_defaults(quiet) if not have_defaults() then return end local msg = nil local found_weapon = false chdat = c_persist.char_defaults[char_combo] for _,sk in ipairs(weapon_skills) do if you.base_skill(sk) > 0 and chdat["Weapon"] then you.train_skill(sk, chdat["Weapon"]) msg = skill_message(msg, sk, "Weapon", skill_glyphs[chdat["Weapon"]]) found_weapon = true else you.train_skill(sk, 0) end end if chdat["Weapon"] and not found_weapon then you.train_skill("Unarmed Combat", chdat["Weapon"]) msg = skill_message(msg, "Unarmed Combat", "Weapon", skill_glyphs[chdat["Weapon"]]) end local found_ranged = false for _,sk in ipairs(ranged_skills) do if you.base_skill(sk) > 0 and chdat["Ranged"] then you.train_skill(sk, chdat["Ranged"]) msg = skill_message(msg, sk, "Ranged", skill_glyphs[chdat["Ranged"]]) found_ranged = true else you.train_skill(sk, 0) end end if chdat["Ranged"] and not found_ranged then you.train_skill("Throwing", chdat["Ranged"]) msg = skill_message(msg, "Throwing", "Ranged", skill_glyphs[chdat["Ranged"]]) end for _,sk in ipairs(other_skills) do if chdat[sk] then you.train_skill(sk, chdat[sk]) msg = skill_message(msg, sk, nil, skill_glyphs[chdat[sk]]) else you.train_skill(sk, 0) end end if not quiet and msg ~= "" then mpr("Loaded default for " .. char_combo .. ": " .. msg) end end function char_defaults(quiet) if you.turns() ~= 0 then return end if not load_attempted then load_char_defaults(quiet) load_attempted = true -- Open the skill menu if we don't have settings to load. if not have_defaults() then crawl.sendkeys("m") end end end --------------------------- ---- End char_defaults ---- --------------------------- } { ----------------------------- ---- Begin speedrun_rest ---- ----------------------------- -- See README.md for documentation. -- How many turns to rest at max. num_rest_turns = 30 -- If true, look for a foot item inventory slot and use fallback_slot if we -- can't find a ration. If false, always use fallback_slot. automatic_slot = true -- Slot where you keep your slow swing item. fallback_slot = "c" -- Set to true to have Na characters always walk instead of item swing if -- they're able to walk slowly. This is not as turncount efficient for non-Chei -- worshipers, but some Na players may prefer it over having to change the -- wielded weapon. naga_always_walk = false -- Delay in milliseconds before sending the next walk command. Makes the -- visuals a bit less jarring when using walk resting. Set to 0 to disable. walk_delay = 50 -- To have the multi-turn rest ignore status change messages, add an entry here -- giving the pattern of the message you'd like to ignore. The entries below -- cover the transition messages from the regen spell, Trog's hand, and poison -- status. The key should be the status that's seen in the output of -- `you.status()` when the status is active, and the value should be a single -- pattern string or an array of pattern strings matching the message you'd -- like to ignore. See the lua string library manual for details on making -- patterns. status_messages = { ["poisoned"] = {"You feel[^%.]+sick%.", "You are no longer poisoned%."}, ["regenerating"] = {"You feel the effects of Trog's Hand fading%.", "Your skin is crawling a little less now%."} } --end status_messages -- Like status_messages above, but for arbitrary messages. Any message matching -- one of these patterns is ignored. ignore_messages = { --RandomTiles messages "Trog roars: Now[^!]+!+", "Sif Muna whispers: Become[^!]+!+", "[^:]+ says: Become[^!]+!+", --Debug messages "Dbg:.*", } -- end ignore_messages -- NOTE: No configuration past this point. ATT_NEUTRAL = 1 version = 0.15 if crawl.version then version = tonumber(crawl.version("major")) end los_range = version >= 0.17 and 7 or 8 -- Wrapper of crawl.mpr() that prints text in white by default. if not mpr then mpr = function (msg, color) if not color then color = "white" end crawl.mpr("<" .. color .. ">" .. msg .. "") end end function control(c) return string.char(string.byte(c) - string.byte('a') + 1) end function delta_to_vi(dx, dy) local d2v = { [-1] = { [-1] = 'y', [0] = 'h', [1] = 'b'}, [0] = { [-1] = 'k', [1] = 'j'}, [1] = { [-1] = 'u', [0] = 'l', [1] = 'n'}, } -- hack return d2v[dx][dy] end function reset_rest() if not rstate then rstate = { } rstate.set_slot = false rstate.dir_x = nil rstate.dir_y = nil end rstate.last_acted = nil rstate.wielding = false rstate.resting = false rstate.num_turns = nil rstate.rest_start = nil rstate.start_hp = nil rstate.start_status = { } rstate.start_message = nil end function abort_rest(msg) if msg then mpr(msg, "lightred") end reset_rest() end function crawl_message(i) local msg = crawl.messages(i):gsub("\n", "") msg = msg:gsub("^%c* *", "") msg = msg:gsub(" *%c*?$", "") return msg end function record_status() -- Record starting status to track any status changes. rstate.start_status = { } local status = you.status() for s,_ in pairs(status_messages) do if status:find(s) then rstate.start_status[s] = true end end rstate.start_message = get_last_message() end function in_water() return view.feature_at(0, 0):find("water") and not you.status("flying") end function get_last_message() local rest_type = get_rest_type() local in_water = in_water() -- Ignore these movement messages when walking. local move_patterns = {"There is a[^%.]+here.", "Things that are here:.*", "Items here:.*"} for i = 1,200 do local msg = crawl_message(i) for s,_ in pairs(rstate.start_status) do if type(status_messages[s]) == "table" then for _,p in ipairs(status_messages[s]) do msg = msg:gsub(p, "") end else msg = msg:gsub(status_messages[s], "") end end if rest_type == "walk" then for _,p in ipairs(move_patterns) do -- Also remove any whitespace. msg = msg:gsub(" *" .. p .. " *", "") end end msg = msg:gsub(" *Beep! [^%.]+%. *", "") for _,p in ipairs(ignore_messages) do msg = msg:gsub(p, "") end if msg ~= "" then return msg end end return nil end function wield_swing_item() rstate.wielding = true rstate.last_acted = you.turns() record_status() crawl.sendkeys("w*" .. c_persist.swing_slot) end function find_swing_slot() rstate.set_slot = true if not automatic_slot then c_persist.swing_slot = fallback_slot return end c_persist.swing_slot = nil for _,item in ipairs(items.inventory()) do if item.class() == "Comestibles" or item.class() == "Books" or item.class() == "Wands" or item.class() == "Missiles" or item.class() == "Miscellaneous" or (item.class() == "Hand Weapons" and (item.subtype():find("bow") or item.subtype():find("sling") or item.subtype():find("crossbow") or item.subtype():find("blowgun"))) then c_persist.swing_slot = items.index_to_letter(item.slot) break end end if not c_persist.swing_slot then c_persist.swing_slot = fallback_slot end end function swing_item_wielded() local weapon = items.equipped_at("Weapon") return weapon and c_persist.swing_slot ~= nil and weapon.slot == items.letter_to_index(c_persist.swing_slot) end function hostile_in_los() local have_t1 = false for x = -los_range,los_range do for y = -los_range,los_range do m = monster.get_monster_at(x, y) if m and not m:is_safe() then return true end end end return false end function ponderous_level() local level = 0 for _,item in ipairs(items.inventory()) do local ego = item.ego() if item.equipped and ego == "ponderousness" then level = level + 1 end end return level end -- XXX See if we can move at least some of this into clua, since it's -- recreating play_movement_speed() and player_speed(). The aim is to determine -- if slow move is more regen-efficient and that the saving is worth the -- hassle, this calculates the minimum move speed, which can normally vary at -- random from randomized delay (e.g. water) and the many uses of -- div_rand_round(). function player_move_speed() if you.transform() == "tree" then return 0 end local in_water = in_water() local walk_water = you.race() == "Merfolk" or you.race() == "Octopode" or you.race() == "Barachi" or you.god() == "Beogh" and you.piety_rank() == 5 or you.transform == "ice" -- This is player action speed, based on things that affect all actions. local player_speed = 10 if you.status("slowed") then player_speed = math.floor(player_speed * 3 / 2) end if you.status("berserking") and you.god() ~= "Cheibriados" or you.status("hasted") then player_speed = math.floor(player_speed * 2 / 3) end if you.transform() == "statue" or you.status("petrifying") then player_speed = math.floor(player_speed * 3 / 2) end if in_water and not walk_water then player_speed = math.floor(player_speed * 13 / 10) end -- This is the base player movement speed given all things that affect only -- movement. local move_speed = 10 if you.transform() == "bat" then move_speed = 5 elseif you.transform() == "pig" then move_speed = 7 elseif you.transform() == "porcupine" or you.transform == "wisp" then move_speed = 8 elseif in_water and (you.transform() == "hydra" or you.race() == "Merfolk") then move_speed = 6 elseif you.race() == "Tengu" and you.status("flying") then move_speed = 9 end local boots = items.equipped_at("Boots") local running_level = 0 if boots and not boots.is_melded and boots.ego() and boots.ego() == "running" then running_level = 1 end move_speed = move_speed - running_level move_speed = move_speed + ponderous_level() if you.god() == "Cheibriados" then -- Calculate this based on the minimum piety at the observed rank, -- since we can't know the true piety level. local piety_breaks = { 1, 30, 50, 75, 100, 120, 160 } move_speed = move_speed + 2 + math.floor(math.min(piety_breaks[you.piety_rank() + 1] / 20, 8)) end if you.status("frozen") then move_speed = move_speed + 4 end if you.status("grasped by roots") then move_speed = move_speed + 3 end local speed_mut = you.mutation("speed") local slow_mut = you.mutation("slowness") if speed_mut > 0 then move_speed = move_speed - speed_mut - 1 elseif slow_mut > 0 then move_speed = math.floor(move_speed * (10 + slow_mut * 2) / 10) end if not in_water and you.status("sluggish") then if move_speed >= 8 then move_speed = math.floor(move_speed * 3 / 2) elseif move_speed == 7 then move_speed = math.floor(7 * 6 / 5) end elseif not in_water and you.status("swift") then move_speed = math.floor(move_speed * 3 / 4) end if move_speed < 6 then move_speed = 6 end return math.floor(player_speed * move_speed / 10) end function get_rest_type() if you.race() == "Naga" and naga_always_walk or you.god() == "Cheibriados" or (not swing_item_wielded() and not weapon_can_swap()) then return "walk" else return "item" end end function bad_to_act() local hp, mhp = you.hp() local rest_type = get_rest_type() -- Stop multiple turn action when our hp recovers. if rstate.rest_start and rstate.start_hp < mhp and hp == mhp then mpr("HP restored.") reset_rest() return true end if you.status("manticore barbs") then abort_rest("You must remove the manticore barbs first.") return true end if you.hunger_name() == "fainting" or you.hunger_name() == "starving" then abort_rest("You need to eat!") return true end if hostile_in_los() then if not rstate.rest_start then abort_rest("You can't rest with a hostile monster in view!") else abort_rest() end return true end -- If any unrecognized message occurs, assume we need to stop resting. if rstate.last_acted then local msg = get_last_message() if not msg then abort_rest("Unable to find a previous message!") return true end local wield_pt = "^ *" .. c_persist.swing_slot .. " - .+[%)}] *$" local swing_pt = "^ *You swing at nothing%. *$" local pattern = rstate.wielding and wield_pt or swing_pt if (rest_type == "item" and not msg:find(pattern)) or rest_type == "walk" and msg ~= rstate.start_message then abort_rest() return true end end if rest_type == "walk" and player_move_speed() <= 10 then abort_rest("You cannot walk slowly right now!") return true end return false end function feat_is_open(feat) local fname = feat:lower() -- Unique substrings that identify solid features. local solid_features = {"wall", "grate", "tree", "mangrove", "endless_lava", "open_sea", "statue", "idol", "malign_gateway", "sealed_door", "closed_door", "runed_door", "explore_horizon"} for i,p in ipairs(solid_features) do if fname:find(p) then return false end end return true end function safe_walk_pos(x, y) local in_water = in_water() local pos_is_water = view.feature_at(x, y):find("water") -- Don't allow walking out of water if we're in water return (in_water and pos_is_water -- Don't allow walking into water if we're not in it or not in_water and not pos_is_water) -- Require the destination to be safe. and view.is_safe_square(x, y) end function safe_swing_pos(x, y) return not monster.get_monster_at(x, y) and feat_is_open(view.feature_at(x,y)) end function safe_direction(x, y) if get_rest_type() == "walk" then return safe_walk_pos(x, y) else return safe_swing_pos(x, y) end end function weapon_can_swap() local weapon = items.equipped_at("Weapon") if not weapon then return true end if weapon.cursed then return false end local ego = weapon.ego() -- Some unrands like Plut. sword have no ego. if ego and (ego == "vampirism" or ego == "distortion" and you.god() ~= "Lugonu") then return false end if weapon.artefact then local artp = weapon.artprops return not (artp["*Contam"] or artp["*Curse"] or artp["*Drain"]) end return true end function get_safe_direction() local have_t1 = false for x = -1,1 do for y = -1,1 do if (x ~= 0 or y ~= 0) and safe_direction(x, y) then return x, y end end end return nil end function do_resting() -- Our first turn of resting. if not rstate.rest_start then record_status() rstate.rest_start = you.turns() rstate.start_hp = you.hp() end rstate.last_acted = you.turns() local rest_type = get_rest_type() if rest_type == "item" then crawl.sendkeys(control(delta_to_vi(rstate.dir_x, rstate.dir_y))) else local cur_x = rstate.dir_x local cur_y = rstate.dir_y -- Save the return direction as our next direction. rstate.dir_x = -rstate.dir_x rstate.dir_y = -rstate.dir_y crawl.sendkeys(delta_to_vi(cur_x, cur_y)) crawl.delay(walk_delay) end end function one_turn_rest() rstate.resting = true rstate.num_turns = 1 end function start_resting() rstate.resting = true rstate.num_turns = num_rest_turns end function set_swing_slot() crawl.formatted_mpr("Enter an slot letter for the swing item: ", "prompt") local letter = crawl.c_input_line() local index = items.letter_to_index(letter) if not index or index < 0 then mpr("Must be a letter (a-z or A-Z)!", "lightred") return end c_persist.swing_slot = letter rstate.set_slot = true mpr("Set swing slot to " .. letter .. ".") end function speedrun_rest() local rest_type = get_rest_type() if rest_type == "item" and (not c_persist.swing_slot or you.turns() == 0 and not rstate.set_slot) then find_swing_slot() end if not rstate.resting then return end -- Only act once per turn. if rstate.last_acted == you.turns() then -- An error happened with the 'w' command if rstate.wielding and not swing_item_wielded() then abort_rest("Unable to wield swing item on slot " .. c_persist.swing_slot .. "!") end return end if rstate.last_acted and rstate.rest_start and rstate.last_acted + 1 >= rstate.rest_start + rstate.num_turns then reset_rest() return end if bad_to_act() then return end if rest_type == "item" and not swing_item_wielded() then wield_swing_item() return end rstate.wielding = false if not rstate.dir_x -- Don't try to reuse our position if we were walk resting and did -- something inbetween our last rest. or swing_type == "walk" and rstat.last_acted ~= you.turns() - 1 or not safe_direction(rstate.dir_x, rstate.dir_y) then rstate.dir_x, rstate.dir_y = get_safe_direction() if not rstate.dir_x then abort_rest("No safe direction found!") return end end do_resting() end reset_rest() --------------------------- ---- End speedrun_rest ---- --------------------------- } # The ready() function is needed for code that has to process each turn or UI # action. { function ready() -- Open skill screen on turn 0. if not skills_set and you.turns() == 0 then crawl.sendkeys("m") skills_set = true end -- force_mores.lua force_mores() -- char_defaults.lua char_defaults() --speedrun_rest.lua speedrun_rest() end } ############# ### Debug ### ############# fsim_rounds = 10000