restart_after_game =true pickup_mode =multi tile_viewport_scale =1.2 default_manual_training =true hp_warning =70 autofight_stop = 50 autofight_caught = false autofight_wait = false auto_butcher = true game_seed = 65511121091 bindkey = [;] CMD_AUTOFIGHT_NOMOVE auto_butcher =true auto_eat_chunks =true show_more =false ################ ### RC files ### ################ # Only load RandomTiles.rc under my username. If copying this part of my RC # directly into your own, remove this section including the beginning and # ending lines with '{' and '}'. If you don't want RandomTiles, you don't need # to replace it with anything. If you do want RandomTiles, replace this section # with the line: # # include = RandomTiles.rc # # and see the comments in the ready() function section as well. { if you.name() == "gammafunk" then -- Prevent gammaspeed.rc from trying to load this file twice. gammafunk_rc_loaded = true crawl.read_options("RandomTiles.rc") end } autofight_throw_nomove = true ############### ### 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 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 show_travel_trail =true travel_delay =-1 rest_delay =-1 auto_sacrifice =true sacrifice_before_explore =true show_game_time =true jewellery_prompt =false equip_unequip =true allow_self_target =never confirm_butcher =never easy_eat_gourmand =true sort_menus =true : identified, basename, qualname, charged, equipped wall_jump_move =false # These should match the item_glyph colours exactly when possible. # For menu_colour, the first match ignores subsequent matches. menu := menu_colour menu = 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 autofight_stop = 50 autofight_caught = false autofight_wait = false auto_butcher = true bindkey = [;] CMD_AUTOFIGHT_NOMOVE ############################ ### Travel & Exploration ### ############################ rest_wait_both = true auto_sacrifice = true travel_delay = -1 explore_delay = -1 rest_delay = -1 show_travel_trail = true explore_stop += greedy_pickup_smart 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 ########### ### 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 ---- ----------------------------------- } # 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() -- Enable these only for my username. If copying this portion of my RC into -- your own and you don't want these features, remove everything from the 'if -- you.name() ...' line to the ' end' line corresponding to that 'if' -- statement. If you do want those features, remove only the 'if' line and -- its corresponding ' end' line so that they're not conditioned on my -- username. -- load_message.lua load_message() end } show_more =false stop := runrest_stop_message ignore := runrest_ignore_message # Annoyances : if you.god() == "Jiyva" then ignore += Jiyva gurgles merrily ignore += Jiyva appreciates your sacrifice ignore += Jiyva says: Divide and consume ignore += You hear.*splatter : end ignore ^= You feel.*sick ignore += disappears in a puff of smoke ignore += engulfed in a cloud of smoke ignore += standing in the rain ignore += engulfed in white fluffiness ignore += safely over a trap ignore += A.*toadstool withers and dies ignore += toadstools? grow ignore += You walk carefully through the ignore += chunks of flesh in your inventory.*rotted away runrest_ignore_poison = 5:10 runrest_ignore_monster += ^butterfly:1 # Bad things stop += You fall through a shaft stop += An alarm trap emits a blaring wail stop += (blundered into a|invokes the power of) Zot stop += A huge blade swings out and slices into you! stop += flesh start stop += (starving|feel devoid of blood) stop += wrath finds you stop += lose consciousness stop += watched by something stop += appears from out of your range of vision # Expiring effects stop += You feel yourself slow down stop += less insulated stop += You are starting to lose your buoyancy stop += You lose control over your flight stop += Your hearing returns stop += Your transformation is almost over stop += back to life stop += uncertain stop += time is quickly running out stop += life is in your own hands stop += is no longer charmed stop += You start to feel a little slower stop += You are no longer : if you.race() == "Ghoul" then stop += smell.*(rott(ing|en)|decay) stop += something tasty in your inventory : end : if you.god() == "Xom" then stop += god: :else ignore += god: :end ignore += pray: ignore += talk: ignore += talk_visual: ignore += friend_spell: ignore += friend_enchant: ignore += friend_action: ignore += sound: ########### # prompts # ########### flash_screen_message += You feel strangely unstable flash_screen_message += Strange energies course through your body more := force_more_message # distortion more += Space warps horribly around you more += hits you.*distortion more += Space bends around you\. more += Your surroundings suddenly seem different. more += Its appearance distorts for a moment. # ghost moths/antimagic more += watched by something more += You feel your power leaking # torment/holy wrath more += You convulse # dispel breath more += dispelling energy hits you # early unseen horrors more += It hits you! more += Something hits you more += Something. *misses you. # more += You have reached level more += You fall through a shaft more += Training target.*for.*reached! more += You now have enough gold to buy # abyss convenience prompts more += Found an abyssal rune more += Found a gateway leading out of the Abyss more += Found a gateway leading deeper into the Abyss # necromutation more += Your transformation is almost over. more += You feel yourself coming back to life # summon greater demon more += is no longer charmed # Announcements of timed portal vaults: more += interdimensional caravan more += distant snort more += roar of battle more += wave of frost more += hiss of flowing sand more += sound of rushing water more += oppressive heat about you more += crackle of arcane power more += Found a gateway leading out of the Abyss more += Found .* abyssal rune of Zot more += You feel a terrible weight on your shoulders more += .* resides here # Interrupts more += You don't.* that spell more += You miscast (Controlled Blink|Blink|Death's|Borg|Necromutation) more += You can't (read|drink|do) that more += That item cannot be evoked more += This wand has no charges more += You are held in a net more += You have disarmed more += You don't have any such object more += do not work when you're silenced more += You can't unwield more += enough magic points more += You feel your control is inadequate more += Something interferes with your magic more += You enter a teleport trap # Bad things more += Your surroundings flicker more += You cannot teleport right now more += The writing blurs in front of your eyes more += You fall through a shaft more += A huge blade swings out and slices into you! more += (blundered into a|invokes the power of) Zot more += Ouch! That really hurt! more += dispelling energy hits you more += You convulse more += You are (blasted|electrocuted) more += You are.*confused more += flesh start more += (starving|devoid of blood) more += god:(sends|finds|silent|anger) more += You feel a surge of divine spite more += lose consciousness more += You are too injured to fight blindly more += calcifying dust hits more += Space warps horribly around you more += hits you.*distortion more += Space bends around you\. more += watched by something more += A sentinel's mark forms upon you more += Your limbs have turned to stone more += You are slowing down more += .*LOW HITPOINT WARNING.* more += warns you.*of distortion more += lethally poison more += space bends around your more += wielding.*of (distortion|chaos) # Gods more += you are ready to make a new sacrifice more += mollified more += wrath finds you more += sends forces more += sends monsters more += Vehumet offers # Hell effects # Re-enabled more += "You will not leave this place." more += "Die, mortal!" more += "We do not forgive those who trespass against us!" more += "Trespassers are not welcome here!" more += "You do not belong in this place!" more += "Leave now, before it is too late!" more += "We have you now!" more += You smell brimstone. more += Brimstone rains from above. more += You feel lost and a long, long way from home... more += You shiver with fear. more += You feel a terrible foreboding... more += Something frightening happens. more += You sense an ancient evil watching you... more += You suddenly feel all small and vulnerable. more += You sense a hostile presence. more += A gut-wrenching scream fills the air! more += You hear words spoken in a strange and terrible language... more += You hear diabolical laughter! # Expiring effects more += You feel yourself slow down more += less insulated more += You are starting to lose your buoyancy more += You lose control over your flight 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 += uncertain more += time is quickly running out more += life is in your own hands more += is no longer charmed more += shroud falls apart more += You start to feel a little slower more += You flicker more += You feel less protected from missiles # Skill breakpoints more += skill increases # Others # more += You have reached level more += You have finished your manual of more += Your scales start more += You feel monstrous more += zaps a wand more += carrying a wand more += is unaffected more += Jiyva alters your body # Any uniques and any pan lords - doesn't seem to work more += (?-i:[A-Z]).* comes? into view more += Agnes.*comes? into view. more += Aizul.*comes? into view. more += Antaeus.*comes? into view. more += Arachne.*comes? into view. more += Asmodeus.*comes? into view. more += Asterion.*comes? into view. more += Azrael.*comes? into view. more += Blork the orc.*comes? into view. more += Boris.*comes? into view. more += Cerebov.*comes? into view. more += Crazy Yiuf.*comes? into view. more += Dispater.*comes? into view. more += Dissolution.*comes? into view. more += Donald.*comes? into view. more += Dowan.*comes? into view. more += Duvessa.*comes? into view. more += Edmund.*comes? into view. more += Enchantress.*comes? into view. more += Ereshkigal.*comes? into view. more += Erica.*comes? into view. more += Erolcha.*comes? into view. more += Eustachio.*comes? into view. more += Fannar.*comes? into view. more += Frances.*comes? into view. more += Francis.*comes? into view. more += Frederick.*comes? into view. more += Gastronok.*comes? into view. more += Geryon.*comes? into view. more += Gloorx Vloq.*comes? into view. more += Grinder.*comes? into view. more += Grum.*comes? into view. more += Harold.*comes? into view. more += Ignacio.*comes? into view. more += Ijyb.*comes? into view. more += Ilsuiw.*comes? into view. more += Jorgrun.*comes? into view. more += Jory.*comes? into view. more += Jessica.*comes? into view. more += Joseph.*comes? into view. more += Josephine.*comes? into view. more += Jozef.*comes? into view. more += Khufu.*comes? into view. more += Kirke.*comes? into view. more += Lamia.*comes? into view. more += Lom Lobon.*comes? into view. more += Louise.*comes? into view. more += Mara.*comes? into view. more += Margery.*comes? into view. more += Maud.*comes? into view. more += Maurice.*comes? into view. more += Menkaure.*comes? into view. more += Mennas.*comes? into view. more += Mnoleg.*comes? into view. more += Murray.*comes? into view. more += Natasha.*comes? into view. more += Nergalle.*comes? into view. more += Nessos.*comes? into view. more += Nikola.*comes? into view. more += Norris.*comes? into view. more += Pikel.*comes? into view. more += Polyphemus.*comes? into view. more += Prince Ribbit.*comes? into view. more += Psyche.*comes? into view. more += Purgy.*comes? into view. more += Robin.*comes? into view. more += Roxanne.*comes? into view. more += Rupert.*comes? into view. more += Saint Roka.*comes? into view. more += Sigmund.*comes? into view. more += Snorg.*comes? into view. more += Sojobo.*comes? into view. more += Sonja.*comes? into view. more += Terence.*comes? into view. more += The Lernaean hydra.*comes? into view. more += The royal jelly.*comes? into view. more += The Serpent of Hell.*comes? into view. more += Tiamat.*comes? into view. more += Urug.*comes? into view. more += Vashnia.*comes? into view. more += Wiglaf.*comes? into view. more += Xtahua.*comes? into view. more += 27-headed.*comes? into view. more += .*player ghost.* comes? into view more += .*Ancient Lich.*comes? into view. more += .*Orbs? of Fire.*comes? into view. more += .*Fiend.*comes? into view. more += .*Hellion.*comes? into view. more += .*Tormentor.*comes? into view. more += .*Hell Sentinel.*comes? into view. more += .*Executioner.*comes? into view. more += .*Neqoxec.*comes? into view. more += .*Cacodemon.*comes? into view. more += .*Shining Eye.*comes? into view. more += .*Greater Mummy.*comes? into view. more += .*Mummy Priest.*comes? into view. more += .*Curse Toe.*comes? into view. more += .*Curse Skull.*comes? into view. more += .*('s|s') ghost.*comes? into view. more += .*shrike.*comes? into view. more += .*wretched star.*comes? into view more += .*lurking horror.*comes? into view more += .*Juggernaut.*comes? into view. more += .*Iron Giant.*comes? into view. more += .*Tzitzimimeh.*comes? into view. more += .*Tzitzimitl.*comes? into view. # Paralysis enemies more += .*Floating Eye.*comes? into view. more += .*Lich.*comes? into view. more += .*Ogre Mage.*comes? into view. more += .*a Wizard.*comes? into view. more += .*orc sorcerer.*comes? into view. more += .*sphinx.*comes? into view. more += .*Great orb of eyes.*comes? into view. more += .*Vampire knight.*comes? into view. # Other dangerous enemies more += minotaur.*into view more += *guardian serpent.*comes? into view. more += .*vault sentinel.*comes? into view. more += .*vault warden.*comes? into view. more += .*ironbrand convoker.*comes? into view. # Dancing weapon more += Your.*falls from the air. # Xom is scary : if you.god() == "Xom" then more += god: : end #################### # Autoinscriptions # #################### ai := autoinscribe ai += (vampiric):!w ai += (bad|dangerous)_item.*potion:!q ai += (bad|dangerous)_item.*scroll:!r ai += of faith:!P ai += rod of:!a ai += lightning rod:!a ai += [^r]staff of (conj|energy|power|wizardry):!a ai += manual of:!d ai += dispersal:!f ai += tome of Destruction:!d ai += throwing net:!f ai += curare:!f ai += needle of (frenzy|paralysis|sleeping|confusion):!f ai += ( ration):!d ai += figurine:!* : if you.god() ~= "Lugonu" then ai += (distortion):!w :end ai += of identify:@r1 ai += remove curse:@r2 ai += curing:@q1 ai += potions? of heal wounds:@q2 ai += wand of heal wounds:@v2 ai += wand of hasting:@v3 ai += potions? of haste:@q3 ai += scrolls? of teleportation:@r4 ai += wand of teleportation:@v4 ai += potions? of blood:@q0 #################### # Mute some messages # #################### msc := message_colour # Muted - unnecessary msc += mute:The (bush|fungus|plant) is engulfed msc += mute:The (bush|fungus|plant) is struck by lightning msc += mute:Cast which spell msc += mute:Use which ability msc += mute:Evoke which item msc += mute:Confirm with # msc += mute:(Casting|Aiming|Aim|Zapping)\: msc += mute:Throwing.*\: msc += mute:You can\'t see any susceptible monsters within range msc += mute:Press\: \? \- help, Shift\-Dir \- straight line, f \- you msc += mute:for a list of commands and other information msc += mute:Firing \(i msc += mute:Fire\/throw which item\? msc += mute:You swap places msc ^= mute:is lightly (damaged|wounded) msc ^= mute:is moderately (damaged|wounded) msc ^= mute:is heavily (damaged|wounded) msc ^= mute:is severely (damaged|wounded) msc ^= mute:is almost (dead|destroyed) msc += mute:Was it this warm in here before msc += mute:The flames dance msc += mute:Your shadow attacks msc += mute:Marking area around msc += mute:Placed new exclusion msc += mute:Reduced exclusion size to a single square msc += mute:Removed exclusion msc += mute:You can access your shopping list by pressing msc += mute:for starvation awaits msc += mute:You offer a prayer to Elyvilon msc += mute:You offer a prayer to Nemelex Xobeh msc += mute:You offer a prayer to Okawaru msc += mute:You offer a prayer to Makhleb msc += mute:Okawaru is noncommittal msc += mute:Nemelex Xobeh is (noncommittal|pleased) msc += mute:The plant looks sick msc += mute:You start butchering msc += mute:You continue butchering msc += mute:This raw flesh tastes terrible : if string.find(you.god(), "Jiyva") then msc += mute:You hear a.*slurping noise msc += mute:You hear a.*squelching noise msc += mute:You feel a little less hungry : end { -- change this between "false" and "true" to change the default mode. auto = true mk = 1 _pillar = nil _path = nil _seqForward = nil _seqBackward = nil _seqidx = 0 _dir = true _pillarDance = false function inputTogglePillar() local px, py = crawl.get_target() local wx, wy = travel.waypoint_delta(7) if _pillar ~= nil and wx ~= nil then for i, xy in ipairs(_pillar) do if xy[1] == wx + px and xy[2] == wy + py then killPillar() crawl.mpr("Previously selected pillar killed.") return end end end killPillar() doSearch(px, py) travel.set_waypoint(7, 0, 0) end function invertAuto() auto = not auto if auto then crawl.mpr("Switched into auto mode.") else crawl.mpr("Switched into manual mode.") end end function inputPillar() local x, y = crawl.get_target() killPillar() doSearch(x, y) travel.set_waypoint(7, 0, 0) end function dancePillar() if (not auto) and _pillarDance then crawl.mpr("Stopping manual dance.") stopPillarDance() if _nextAction ~= nil then -- kill the exclude we set after our last step local x, y = getOffset(_nextAction) travel.del_exclude(x, y, 0) end return end if _path == nil then crawl.mpr("No search selected!") return end -- find which tile (if any) of the path we are standing on local x, y = travel.waypoint_delta(7) local path = _path local idx = 0 for i, xy in ipairs(path) do if xy[1] == x and xy[2] == y then idx = i break end end if idx == 0 then crawl.mpr("Please step on one of the excluded tiles.") return end startPillarDance() _seqForward = getSeq(path) _seqBackward = getSeqBackwards(path) _seqidx = idx if (not auto) then crawl.mpr("Starting manual dance.") end end do local toOff function getOffset(cmd) toOff = toOff or { CMD_MOVE_UP_LEFT = { -1, -1 }, CMD_MOVE_UP_RIGHT = { 1, -1 }, CMD_MOVE_DOWN_LEFT = { -1, 1 }, CMD_MOVE_DOWN_RIGHT = { 1, 1 }, CMD_MOVE_UP = { 0, -1 }, CMD_MOVE_DOWN = { 0, 1 }, CMD_MOVE_LEFT = { -1, 0 }, CMD_MOVE_RIGHT = { 1, 0 } } return unpack(toOff[cmd]) end end function checkDance() -- checks whether we should dance, based on hp, mp, monster positioning, ... and also changes our direction -- if necessary (i.e. a monster is blocking our path). if not shouldDance() then stopPillarDance() return false end assert(_seqForward ~= nil) assert(_seqBackward ~= nil) assert(_seqidx ~= 0) local monsters = getAllMonsters() -- fast monsters and ranged monsters + casters are bad for health -- TODO allow user to override, could be useful for high-regen users vs. low level fast mons for i, mon in ipairs(monsters) do if string.find(mon:speed_description(), "fast") then crawl.mpr("Fast monster in LOS!") stopPillarDance() return false end if #(mon:spells()) ~= 0 or mon:has_known_ranged_attack() then crawl.mpr("Spellcaster/ranged/abilityperson in LOS!") stopPillarDance() return false end if mon:status("fast") or mon:status("covering ground quickly") then crawl.mpr("Hasted/berserked/swifting monster in LOS!") stopPillarDance() return false end end local xdir, ydir, xndir, yndir local x, y = getOffset(getNextAction()) xdir, ydir = x, y -- the below pile of ifs checks whether we should keep going in current direction, switch direction, or stop -- altogether. It favors moving away from monsters to moving not away, and will never move towards monsters or -- within one tile of a monster. if not tileIsBetter(x, y, monsters) then _dir = not _dir x, y = getOffset(getNextAction()) xndir, yndir = x, y if not tileIsBetter(x, y, monsters) then _dir = not _dir x, y = xdir, ydir if not tileIsGood(x, y, monsters) then _dir = not _dir x, y = xndir, yndir if not tileIsGood(x, y, monsters) then crawl.mpr("No good direction to walk!") stopPillarDance() return false end end end end return true end function getNextAction() if _dir then return _seqForward[_seqidx] else return _seqBackward[_seqidx] end end function doSeqAction() local nextAction = getNextAction() -- actually performs the action and pushes the sequence pointer in the correct direction. if _dir then _seqidx = _seqidx + 1 if _seqidx > #_seqForward then _seqidx = 1 end else _seqidx = _seqidx - 1 if _seqidx < 1 then _seqidx = #_seqBackward end end local x, y = getOffset(nextAction) if not travel.feature_traversable(view.feature_at(x, y)) then crawl.mpr("Unexpected blockage of path! Did you close a door or move mid-dance?") stopPillarDance() return end crawl.do_commands({ nextAction }) end max_steps = 500 _stepcount = 0 function shouldDance() if (not auto) then -- user knows what they want return true end if _stepcount < max_steps then _stepcount = _stepcount + 1 local hp, mhp = you.hp() local mp, mmp = you.mp() return hp ~= mhp or mp ~= mmp end crawl.mpr("Hit max number of steps! You might have found an edge case. If you want to continue dancing, press your dance macro again.") return false end function showPillar() if _path == nil then return end local x, y = travel.waypoint_delta(7) if x == nil then return end local pathTiles = {} for i, t in ipairs(_path) do pathTiles[t] = true end showTiles(pathTiles, -x, -y) end function hidePillar() if _path == nil then return end local x, y = travel.waypoint_delta(7) if x == nil then return end local pathTiles = {} for i, t in ipairs(_path) do pathTiles[t] = true end hideTiles(pathTiles, -x, -y) end function startPillarDance() hidePillar() _pillarDance = true _nextAction = nil _stepcount = 0 end function stopPillarDance() showPillar() _pillarDance = false end function killPillar() hidePillar() _pillarDance = false _pillar = nil _path = nil _seqForward = nil _seqBackward = nil _seqidx = nil _dir = true end _nextAction = nil function doStep() if (not auto) and _pillarDance then if not checkDance() then -- dance stopped, delete "next step" exclusion and stop dancing if _nextAction ~= nil then local x, y = getOffset(_nextAction) travel.del_exclude(x, y) end stopPillarDance() return end if _nextAction ~= nil then -- if an enemy came into view, our next action may have changed, do nothing and just switch next action local confirmNextAction = getNextAction() if _nextAction == confirmNextAction then doSeqAction() -- after the action happens, the "next action" exclude should be under us travel.del_exclude(0, 0) else crawl.mpr("Direction swapped!") local x, y = getOffset(_nextAction) travel.del_exclude(x, y) end end _nextAction = getNextAction() if _nextAction ~= nil then local x, y = getOffset(_nextAction) travel.set_exclude(x, y, 0) end end end function c_answer_prompt() if (not auto) and _pillarDance then return true end end function ready() if auto and _pillarDance and checkDance() then doSeqAction() end end function can_walk_towards(m) -- it's ok to walk towards: neutral monsters, harmless monsters, and firewood. -- this is separate from can_walk_through because if they are in our direct path, we can't walk towards them. if not m or m:attitude() > 1 then return true end if m:name() == "butterfly" then return true end if m:is_firewood() then if string.find(m:name(), "ballistomycete") then return false end return true end return false end function can_walk_through(m) -- we can only walk through, and thus completely ignore, friendly non-stationary non-trapped monsters. return not m or (m:attitude() == 4 and not (m:is_stationary() or m:is_constricted() or m:is_caught())) end function getAllMonsters() local monsters = {} local los = you.los() for x_off = -los, los do for y_off = -los, los do if view.cell_see_cell(0, 0, x_off, y_off) then local mon = monster.get_monster_at(x_off, y_off) if not can_walk_through(mon) then monsters[#monsters + 1] = mon end end end end return monsters end function tileIsGood(x, y, monsters) -- a tile is "good" if it does not take us closer to any monsters. for i, mon in ipairs(monsters) do local newDist = getDist(x, y, mon:x_pos(), mon:y_pos()) if newDist == 0 then return false end if (not can_walk_towards(mon)) and (newDist <= 1 or (getDist(0, 0, mon:x_pos(), mon:y_pos()) > newDist and view.cell_see_cell(x, y, mon:x_pos(), mon:y_pos()))) then return false end end return true end function tileIsBetter(x, y, monsters) -- a tile is "better" if it takes us farther away from all monsters. for i, mon in ipairs(monsters) do local newDist = getDist(x, y, mon:x_pos(), mon:y_pos()) if newDist == 0 then return false end if (not can_walk_towards(mon)) and (newDist <= 1 or (getDist(0, 0, mon:x_pos(), mon:y_pos()) >= newDist and view.cell_see_cell(x, y, mon:x_pos(), mon:y_pos()))) then return false end end return true end function doSearch(x, y) -- first, get the outline (orthogonally adjacent tiles) of the pillar. local pillar = create(x, y) if pillar == nil then return nil end local xmin, ymin, xmax, ymax = getBbox(pillar.floors) -- find two distinct tiles on the outline which are on the outline's axis aligned bounding box. local x1, y1, x2, y2 = findTwoTilesOnBorder(pillar.floors, xmin, ymin, xmax, ymax) local tileset = getTilesInBox(xmin, ymin, xmax, ymax) -- find the shortest path between those two tiles. local path = getPath(tileset, x1, y1, x2, y2) local next = path[2] -- block that path. if x1 == xmin then mk.put(tileset, x1, next[2], false) mk.put(tileset, x1 + 1, next[2], false) elseif x1 == xmax then mk.put(tileset, x1, next[2], false) mk.put(tileset, x1 - 1, next[2], false) elseif y1 == ymin then mk.put(tileset, next[1], y1, false) mk.put(tileset, next[1], y1 + 1, false) elseif y1 == ymax then mk.put(tileset, next[1], y1, false) mk.put(tileset, next[1], y1 - 1, false) end -- find the shortest path again. This will be forced to be the other path because of the blocked tiles. local path2 = getPath(tileset, x1, y1, x2, y2) -- flip the second path, concatenate it to the original path. Now we have a full cyclic path around the pillar. reverse(path2) local prevlen = #path for i = 2, (#path2 - 1) do path[prevlen + i - 1] = path2[i] end local pathTiles = {} for i, t in ipairs(path) do pathTiles[t] = true end showTiles(pathTiles) _path = path _pillar = pillar.walls crawl.mpr("Pillar chosen. Step on one of the excluded tiles and use your pillar dance macro to continue.") return path end function getSeq(path) local seq = {} for i, s in ipairs(path) do local t = path[(i + 1)] if i == #path then t = path[1] end -- this could be a lookup table, but i already wrote it local n if t[1] == s[1] and t[2] == s[2] + 1 then n = "CMD_MOVE_DOWN" elseif t[1] == s[1] and t[2] == s[2] - 1 then n = "CMD_MOVE_UP" elseif t[1] == s[1] + 1 and t[2] == s[2] then n = "CMD_MOVE_RIGHT" elseif t[1] == s[1] - 1 and t[2] == s[2] then n = "CMD_MOVE_LEFT" elseif t[1] == s[1] + 1 and t[2] == s[2] + 1 then n = "CMD_MOVE_DOWN_RIGHT" elseif t[1] == s[1] + 1 and t[2] == s[2] - 1 then n = "CMD_MOVE_UP_RIGHT" elseif t[1] == s[1] - 1 and t[2] == s[2] + 1 then n = "CMD_MOVE_DOWN_LEFT" elseif t[1] == s[1] - 1 and t[2] == s[2] - 1 then n = "CMD_MOVE_UP_LEFT" end seq[i] = n end return seq end function getSeqBackwards(path) local seq = {} for i, s in ipairs(path) do local t = path[(i - 1)] if i == 1 then t = path[#path] end local n -- lol if t[1] == s[1] and t[2] == s[2] + 1 then n = "CMD_MOVE_DOWN" elseif t[1] == s[1] and t[2] == s[2] - 1 then n = "CMD_MOVE_UP" elseif t[1] == s[1] + 1 and t[2] == s[2] then n = "CMD_MOVE_RIGHT" elseif t[1] == s[1] - 1 and t[2] == s[2] then n = "CMD_MOVE_LEFT" elseif t[1] == s[1] + 1 and t[2] == s[2] + 1 then n = "CMD_MOVE_DOWN_RIGHT" elseif t[1] == s[1] + 1 and t[2] == s[2] - 1 then n = "CMD_MOVE_UP_RIGHT" elseif t[1] == s[1] - 1 and t[2] == s[2] + 1 then n = "CMD_MOVE_DOWN_LEFT" elseif t[1] == s[1] - 1 and t[2] == s[2] - 1 then n = "CMD_MOVE_UP_LEFT" end seq[i] = n end return seq end function getTilesInBox(xmin, ymin, xmax, ymax) local tiles = mk.new() for x = xmin, xmax do for y = ymin, ymax do mk.put(tiles, x, y, travel.feature_traversable(view.feature_at(x, y))) end end return tiles end function create(x, y) -- flood fill the wall tiles of the pillar, then get the orthogonally neighboring walkable tiles local walls = floodFillWalls(x, y) if walls == nil then return nil end return {walls=walls, floors=getNeighboringFloors(walls)} end function showTiles(tiles, offx, offy) offx = offx or 0 offy = offy or 0 for xy, _ in pairs(tiles) do local x = xy[1] + offx local y = xy[2] + offy travel.set_exclude(x, y, 0) end end function printTiles(tiles) -- helper debugging function for i, xy in ipairs(tiles) do crawl.mpr("(" .. xy[1] .. ", " .. xy[2] .. ")") end end function hideTiles(tiles, offx, offy) offx = offx or 0 offy = offy or 0 for xy, _ in pairs(tiles) do local x = xy[1] + offx local y = xy[2] + offy travel.del_exclude(x, y) end end function extendSet(a, b) for k, _ in pairs(b) do a[k] = true end end function has(l, t) for k, _ in pairs(l) do print(k) if k[1] == t[1] and k[2] == t[2] then return true end end return false end function newTupleSet() return {} end function floodFillWalls(x, y) if travel.feature_traversable(view.feature_at(x, y)) then return nil end local counter = 1 local used = mk.new() used:put(x, y, true) local queue = {} queue[1] = {x, y} local queue_next = 1 local n_pos = {} n_pos[{ 0, 1 }] = true n_pos[{ 0, -1 }] = true n_pos[{ 1, 0 }] = true n_pos[{ -1, 0 }] = true while queue_next <= #queue do local x, y = unpack(queue[queue_next]) queue_next = queue_next + 1 for oxoy, _ in pairs(n_pos) do local off_x, off_y = unpack(oxoy) if mk.get(used, x + off_x, y + off_y) == nil and not travel.feature_traversable(view.feature_at(x + off_x, y + off_y)) then counter = counter + 1 if counter > 1000 then crawl.mpr("Pillar too large! Did you select a map border?") return nil end used:put(x + off_x, y + off_y, true) queue[#queue + 1] = {x + off_x, y + off_y } end end end return queue end function getNeighboringFloors(tiles) local neighbors = {} local n_pos = newTupleSet() n_pos[{ 0, 1 }] = true n_pos[{ 0, -1 }] = true n_pos[{ 1, 0 }] = true n_pos[{ -1, 0 }] = true for _, xy in ipairs(tiles) do local x, y = unpack(xy) for oxoy, _ in pairs(n_pos) do local off_x, off_y = unpack(oxoy) if travel.feature_traversable(view.feature_at(x + off_x, y + off_y)) then neighbors[{ x + off_x, y + off_y }] = true end end end return neighbors end function getBbox(tiles) local xmin = math.huge local xmax = -math.huge local ymin = math.huge local ymax = -math.huge for tile, _ in pairs(tiles) do local x = tile[1] local y = tile[2] if x > xmax then xmax = x end if x < xmin then xmin = x end if y > ymax then ymax = y end if y < ymin then ymin = y end end return xmin, ymin, xmax, ymax end function tilesOneApart(x1, y1, x2, y2) return math.abs(x1 - x2) <= 1 and math.abs(y1 - y2) <= 1 end function findTwoTilesOnBorder(tiles, xmin, ymin, xmax, ymax) local t1found = false local t1 = nil for tile, _ in pairs(tiles) do local x = tile[1] local y = tile[2] if (x == xmin or x == xmax or y == ymin or y == ymax) then if t1found then if not tilesOneApart(t1[1], t1[2], x, y) then return t1[1], t1[2], x, y end else t1found = true t1 = { x, y } end end end return nil end function getDist(x1, y1, x2, y2) return math.max(math.abs(x1 - x2), math.abs(y1 - y2)) end function getNeighboring(tileset, x, y) local neighbors = {} for ox = -1, 1 do for oy = -1, 1 do if mk.get(tileset, x + ox, y + oy) ~= nil and not (ox == 0 and oy == 0) then neighbors[{ x + ox, y + oy }] = true end end end return neighbors end function getMinTile(nodes, dist) local min = math.huge local tile for _, x, y, __ in mk.tuples(nodes) do local cost = mk.get(dist, x, y) if cost ~= nil and cost <= min then min = cost tile = { x, y } end end return tile end function getPath(tileset, x1, y1, x2, y2) -- we just use dijkstra's to get the shortest path local pq = newPriorityQueue() local dist = mk.new() local nodes = mk.new() local prev = mk.new() for _, x, y, trav in mk.tuples(tileset) do if trav then dist:put(x, y, math.huge) nodes:put(x, y, true) end end pq:insert({val={x1, y1}, cost=0}) dist:put(x1, y1, 0) while not pq:empty() do local u = pq:popMin().val assert(u ~= nil) mk.put(nodes, u[1], u[2], nil) if u[1] == x2 and u[2] == y2 then assert(prev:get(u[1], u[2]) ~= nil) return parsePrev(prev, x1, y1, x2, y2) end for v, _ in pairs(getNeighboring(nodes, u[1], u[2])) do local altDist = dist:get(u[1], u[2]) + 1 if dist:get(v[1], v[2]) == nil or altDist < dist:get(v[1], v[2]) then dist:put(v[1], v[2], altDist) prev:put(v[1], v[2], u) pq:insert({val=v, cost=altDist}) end end end end function parsePrev(prev, x1, y1, x2, y2) -- goes backwards along the prev chain to find the tiles in the shortest path local path = {} local u = { x2, y2 } path[1] = u while not (u[1] == x1 and u[2] == y1) do u = mk.get(prev, u[1], u[2]) assert(u ~= nil) path[#path + 1] = u end reverse(path) return path end function reverse(arr) local i, j = 1, #arr while i < j do arr[i], arr[j] = arr[j], arr[i] i = i + 1 j = j - 1 end end local function getMk() -- i don't know enough lua to mess around with metatables and do this myself, so i yoinked this from somewhere -- simple table adaptor for using multiple keys in a lookup table -- cache some global functions/tables for faster access local assert = assert local select = assert(select) local next = assert(next) local setmetatable = assert(setmetatable) -- sentinel values for the key tree, nil keys, and nan keys local KEYS, NIL, NAN = {}, {}, {} local M = {} local M_meta = { __index = M } function M.new() return setmetatable({ [KEYS] = {} }, M_meta) end setmetatable(M, { __call = M.new }) function M.clear(t) for k in next, t do t[k] = nil end return t end -- local helper function to map a vararg of keys to the real key local function get_key(key, ...) for i = 1, select('#', ...) do if key == nil then break end local e = select(i, ...) if e == nil then e = NIL elseif e ~= e then -- can only happen for NaNs e = NAN end key = key[e] end return key end function M.get(t, ...) local key = get_key(t[KEYS], ...) if key ~= nil then return t[key] end return nil end -- local helper function for both put variants below local function put(t, idx, val, n, ...) for i = 1, n do local e = select(i, ...) if e == nil then e = NIL elseif e ~= e then -- can only happen for NaNs e = NAN end local nextidx = idx[e] if not nextidx then nextidx = {} idx[e] = nextidx end idx = nextidx end t[idx] = val end -- returns true if tab can be removed from the parent table local function del(t, idx, n, ...) if n > 0 then local e = ... if e == nil then e = NIL elseif e ~= e then -- can only happen for NaNs e = NAN end local nextidx = idx[e] if nextidx and del(t, nextidx, n - 1, select(2, ...)) then idx[e] = nil return t[idx] == nil and next(idx) == nil end return false else t[idx] = nil return next(idx) == nil end end function M.put(t, ...) local n, keys, val = select('#', ...), t[KEYS], nil if n > 0 then val = select(n, ...) n = n - 1 end if val == nil then if keys ~= nil then del(t, keys, n, ...) end else if keys == nil then keys = {} t[KEYS] = keys end put(t, keys, val, n, ...) end return t end -- same as M.put, but value comes first not last function M.putv(t, val, ...) local keys = t[KEYS] if val == nil then if keys ~= nil then del(t, keys, select('#', ...), ...) end else if keys == nil then keys = {} t[KEYS] = keys end put(t, keys, val, select('#', ...), ...) end return t end -- iteration is only available with coroutine support if coroutine ~= nil then local unpack = assert(unpack or table.unpack) local pairs = assert(pairs) local ipairs = assert(ipairs) local co_yield = assert(coroutine.yield) local co_wrap = assert(coroutine.wrap) -- internal iterator function local function iterate(iter, t, key, keystack, n) if t[key] ~= nil then keystack[n + 1] = t[key] co_yield(unpack(keystack, 1, n + 1)) end for k, v in iter(key) do if k == NIL then k = nil elseif k == NAN then k = 0 / 0 end keystack[n + 1] = k iterate(iter, t, v, keystack, n + 1) end return nil end -- iterator similar to pairs, but since we have multiple keys ... function M.tuples(t, ...) local vals, n = { true, ... }, select('#', ...) + 1 return co_wrap(function() local key = get_key(t[KEYS], unpack(vals, 2, n)) if key ~= nil then return iterate(pairs, t, key, vals, n) end end) end function M.ituples(t, ...) local vals, n = { ... }, select('#', ...) return co_wrap(function() local key = get_key(t[KEYS], unpack(vals, 1, n)) if key ~= nil then return iterate(ipairs, t, key, vals, n) end end) end -- Lua 5.2 metamethods for iteration M_meta.__pairs = M.tuples M_meta.__ipairs = M.ituples end return M end function newPriorityQueue() local lt = function(a, b) return a.cost < b.cost end return MinHeap.new(lt) end MinHeap = {} MH_meta = { __index = MinHeap } function getminfn(ltfn) return function(a, b) if ltfn(a, b) then return a else return b end end end function MinHeap.new(ltfn) if ltfn == nil then ltfn = function(a, b) return a < b end end local minfn = getminfn(ltfn) local t = setmetatable({}, MH_meta) t.lt = ltfn t.min = minfn return t end function MinHeap.getMin(heap) return heap[1] end function MinHeap.popMin(heap) local min = heap[1] heap[1] = heap[#heap] heap[#heap] = nil downheap(heap, 1) return min end function MinHeap.insert(heap, val) heap[#heap + 1] = val upheap(heap, #heap) end function MinHeap.empty(heap) return #heap == 0 end function downheap(heap, idx) local l = 2 * idx local r = 2 * idx + 1 if heap[l] ~= nil then if heap[r] ~= nil then local minchild = heap.min(heap[l], heap[r]) if not heap.lt(minchild, heap[idx]) then return end local swap if minchild == heap[l] then swap = l else swap = r end heap[swap] = heap[idx] heap[idx] = minchild downheap(heap, swap) else if not heap.lt(heap[l], heap[idx]) then return end local temp = heap[l] heap[l] = heap[idx] heap[idx] = temp end end end function upheap(heap, idx) if idx == 1 then return end local parent = math.floor(idx / 2) if not heap.lt(heap[idx], heap[parent]) then return end local temp = heap[parent] heap[parent] = heap[idx] heap[idx] = heap[temp] upheap(heap, parent) end mk = getMk() } macros += M p ===inputTogglePillar macros += M K ===dancePillar