Last active
December 6, 2017 03:09
-
-
Save francismhw/c6344fe393c4e26f95bb7e01f81ac196 to your computer and use it in GitHub Desktop.
qw DCSS bot configured for 15 rune 27 level Ziggurat ascension
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This rcfile is elliptic's DCSS bot "qw", the first (and thus far only) bot | |
| # to win DCSS with no human assistance. A substantial amount of code here was | |
| # contributed by elliott or borrowed from N78291's bot "xw", and many others | |
| # have contributed as well. | |
| # For brief instructions and the most up-to-date version of qw, see | |
| # https://github.com/elliptic/qw. | |
| ## qw's settings: | |
| # | |
| # Set this to true when playing online. | |
| : DELAYED = false | |
| # Delay per action in milliseconds. | |
| # Set this to at least 100 or so when playing online. | |
| : DELAY_TIME = 0 | |
| # whether to start playing immediately when a new game is started | |
| # unfortunately this doesn't work if the game starts with a -more- | |
| : AUTO_START = true | |
| # experimental: do second lair rune branch before depths | |
| : EARLY_SECOND_RUNE = true | |
| # experimental: wait to do Orc until after D:15 | |
| : LATE_ORC = true | |
| # lair rune preferences, current options are: | |
| # * random - no preference, chooses randomly | |
| # * nowater - does Snake/Spider first | |
| # * smart - currently prefers Spider > Snake/Swamp > Shoals | |
| # * dsmart - Swamp/Spider > Snake > Shoals (DsBe data) | |
| : RUNE_PREFERENCE = "random" | |
| # plan for post-V:5, should end with zot | |
| # maximal example: "tso,tomb,hells,pan,slime,abyss,zig,zot" | |
| : ENDGAME_PLAN = "tso,tomb,hells,pan,slime,abyss,zig,zot" | |
| # depth to attempt to dive in ziggurats | |
| : ZIG_DIVE = 27 | |
| # burn books with Trog, may theoretically cause navigation problems | |
| : BURN_BOOKS = true | |
| # enable certain somewhat spammy notes | |
| : SPAM = false | |
| # tab just takes a single action (for testing) | |
| : SINGLE_STEP = false | |
| # panic (stop) at full inventory | |
| : FULL_INVENTORY_PANIC = true | |
| # quit after this number of turns while stuck | |
| : QUIT_TURNS = 1000 | |
| # use a shield at all cost | |
| : SHIELD_CRAZY = true | |
| # acceptable gods (join whichever one we find first) | |
| # gods who have been at least partially implemented: BCHLMOQRTUXY1 | |
| # gods who are actually pretty decent on qw: CMORT | |
| : GOD_LIST = { "Okawaru","TSO" } | |
| # use faded altar, abandoning if we don't get one of the gods in GOD_LIST | |
| : FADED_ALTAR = false | |
| # cycle through combos using c_persist.options | |
| : COMBO_CYCLE = false | |
| # combos to cycle through - uses the same syntax as the regular combo option | |
| # with an optional "^<god initials>" suffix to overwrite the GOD_LIST | |
| # list above. Here are some sample choices: | |
| : COMBO_CYCLE_LIST = "DDFi.waraxe^M, DDBe.handaxe^T, GrBe.handaxe^T, GrFi.waraxe^O, MiFi.waraxe^O" | |
| # Choose randomly between these combos. This will not apply if the COMBO_CYCLE | |
| # option was on in your _previous_ game. | |
| combo = GrFi.waraxe | |
| # For random berserkers, use these combos: | |
| #combo = CeBe.handaxe, DDBe.handaxe, DEBe.handaxe, DrBe.handaxe, DsBe.handaxe | |
| #combo += FeBe.claws, FoBe.handaxe, GrBe.handaxe, GhBe.claws, HaBe.falchion | |
| #combo += HEBe.falchion, HOBe.handaxe, HuBe.handaxe, KoBe.mace, MfBe.spear | |
| #combo += MiBe.handaxe, MuBe.handaxe, NaBe.handaxe, OgBe.mace, OpBe.handaxe | |
| #combo += SpBe.shortsword, TeBe.handaxe, TrBe.claws, VSBe.handaxe, VpBe.handaxe | |
| # For a totally random combo (with chosen weapon type), hyperqwcombo.rc can | |
| # be created using hyperqwcombogen.sh and used: | |
| #combo = | |
| #include += hyperqwcombo.rc | |
| ## The accomplishments of qw: | |
| # online wins: CeFi^O DDBe DDFi^C DDFi^O DDFi^Q DDFi^Y DDGl^M DDGl^R DrFi^O | |
| # DsBe FoFi^O GhFi^O GrBe HOBe HuBe MfBe MiBe MiFi^C MiFi^O | |
| # MiFi^Q MiFi^R MiFi^Y MuFi^O NaBe OgFi^O TeBe TrBe VSBe | |
| # offline wins: HaFi^O HEBe HEFi^O VpBe VpFi^O | |
| # (offline) runes: DgFi FeFi^O KoBe KoFi^O OpBe OpFi^O | |
| ##################################### | |
| # enum values :/ | |
| : ENUM_MONS_PANDEMONIUM_LORD = 344 | |
| : ATT_FRIENDLY = 4 | |
| : ATT_NEUTRAL = 1 | |
| : ATT_HOSTILE = 0 | |
| : LOS = 7 | |
| : if you.race() == "Barachi" then | |
| : LOS = 8 | |
| : end | |
| ##################################### | |
| # miscellaneous simple options | |
| name = qw | |
| restart_after_game = true | |
| bold_brightens_foreground = true | |
| equip_bar = true | |
| ability_menu = false | |
| view_delay = 0 | |
| use_animations = | |
| darken_beyond_range = false | |
| clear_messages = true | |
| travel_delay = -1 | |
| explore_delay = -1 | |
| rest_delay = -1 | |
| travel_key_stop = false | |
| default_manual_training = true | |
| autopickup_no_burden = false | |
| auto_exclude = | |
| hp_warning = 0 | |
| show_more = false | |
| show_newturn_mark = false | |
| list_rotten = false | |
| force_more_message = | |
| show_travel_trail = false | |
| skill_focus = false | |
| autoinscribe += slay:mikee | |
| autoinscribe += (inedible|mutagenic|forbidden).*(chunk|corpse):noeat | |
| flush.failure = false | |
| char_set = ascii | |
| cset = item_orb:0 | |
| use_fake_player_cursor = true | |
| equip_unequip = true | |
| dump_order = header,hiscore,stats,misc,mutations,skills,spells,inventory | |
| dump_order += overview | |
| dump_order += messages,screenshot,monlist,kills,notes,vaults,skill_gains,action_counts | |
| dump_order += xp_by_level | |
| ood_interesting = 6 | |
| note_hp_percent = 25 | |
| note_skill_levels = 1,3,6,9,12,15,18,21,24,27 | |
| note_all_spells = true | |
| fire_order = launcher, rock, javelin, tomahawk | |
| auto_eat_chunks = false | |
| read_persist_options = true | |
| message_colour ^= mute:Unknown command | |
| #################################### | |
| # not sure exactly how important or correct these settings are | |
| explore_stop = | |
| explore_stop += items,branches,portals,stairs,altars | |
| explore_stop += greedy_visited_item_stack,greedy_pickup_smart | |
| stop := runrest_stop_message | |
| ignore := runrest_ignore_message | |
| stop = | |
| ignore = | |
| ignore += .* | |
| runrest_ignore_poison = 3:15 | |
| runrest_ignore_monster += butterfly:1 | |
| runrest_ignore_monster += orb of destruction:1 | |
| #################################### | |
| # These keys are useful to answer prompts and aren't critical for manual play | |
| bindkey = [Y] CMD_NO_CMD_DEFAULT | |
| bindkey = [N] CMD_NO_CMD_DEFAULT | |
| bindkey = [B] CMD_NO_CMD_DEFAULT | |
| bindkey = [C] CMD_NO_CMD_DEFAULT | |
| bindkey = [.] CMD_NO_CMD_DEFAULT | |
| bindkey = [^D] CMD_LUA_CONSOLE | |
| #################################### | |
| # Don't get interrupted! | |
| : chk_interrupt_activity["blurry vision"] = function (iname, cause, extra) | |
| : return nil | |
| : end | |
| #################################### | |
| # autopickup is all handled via lua | |
| autopickup = | |
| autopickup_exceptions = | |
| ################################################################ | |
| # now the lua, beginning with global variables | |
| { | |
| -- some global variables: | |
| local initialized = false | |
| local time_passed | |
| local update_coroutine | |
| local do_dummy_action | |
| local dump_count = you.turns() + 100 - (you.turns() % 100) | |
| local skill_count = you.turns() - (you.turns() % 5) | |
| local danger | |
| local immediate_danger | |
| local cloudy | |
| local where | |
| local where_shafted_from = nil | |
| local expect_new_location | |
| local expect_portal | |
| local automatic = false | |
| local ignore_list = { } | |
| local failed_move = { } | |
| local invisi_count = 0 | |
| local next_delay = 100 | |
| local dd_hw_meter = 0 | |
| local sigmund_dx = 0 | |
| local sigmund_dy = 0 | |
| local invisi_sigmund = false | |
| local sgd_timer = -200 | |
| local stuck_turns = 0 | |
| local stepped_on_lair = false | |
| local stepped_on_tomb = false | |
| local lair_step_mode = false | |
| -- are these still necessary? | |
| local did_move = false | |
| local move_count = 0 | |
| local did_move_towards_monster = 0 | |
| local target_memory_x | |
| local target_memory_y | |
| local last_wait = 0 | |
| local wait_count = 0 | |
| local old_turn_count = you.turns()-1 | |
| local hiding_turn_count = -100 | |
| local travel_destination = nil | |
| local game_status = "normal" | |
| local have_message = false | |
| local read_message = true | |
| local monster_array | |
| local enemy_list | |
| local upgrade_phase = false | |
| local tactical_step | |
| local tactical_reason | |
| local is_waiting | |
| local did_first_turn = false | |
| local stairdance_count = {} | |
| local clear_exclusion_count = {} | |
| local v5_entry_turn | |
| local last_swamp_fail_count = -1 | |
| local swamp_rune_reachable = false | |
| local offlevel_travel = true | |
| local last_min_delay_skill = 18 | |
| local only_linear_resists = false | |
| local no_spells = false | |
| local level_map | |
| local stair_dists | |
| local waypoint_parity | |
| local cur_where | |
| local prev_where | |
| local did_waypoint = false | |
| local good_stair_list | |
| local target_stair | |
| local last_flee_turn = -100 | |
| local ABYSSAL_RUNE = false | |
| local SLIMY_RUNE = false | |
| local PAN_RUNE = false | |
| local HELL_RUNE = false | |
| local GOLDEN_RUNE = false | |
| local TSO_CONVERSION = false | |
| local LUGONU_CONVERSION = false | |
| local WILL_ZIG = false | |
| local MIGHT_BE_GOOD = false | |
| local endgame_plan_list = {} | |
| local which_endgame_plan = 1 | |
| local dislike_pan_level = false | |
| local prev_hatch_dist = 1000 | |
| local prev_hatch_x | |
| local prev_hatch_y | |
| -- options to set while qw is running | |
| -- maybe should add more mutes for watchability | |
| function set_options() | |
| crawl.setopt("confirm_butcher = always") | |
| crawl.setopt("pickup_mode = multi") | |
| crawl.setopt("message_colour += mute:Search for what") | |
| crawl.setopt("message_colour += mute:Can't find anything") | |
| crawl.setopt("message_colour += mute:Drop what") | |
| crawl.setopt("message_colour += mute:Okay, then") | |
| crawl.setopt("message_colour += mute:Use which ability") | |
| crawl.setopt("message_colour += mute:Read which item") | |
| crawl.setopt("message_colour += mute:Drink which item") | |
| crawl.setopt("message_colour += mute:not good enough") | |
| crawl.setopt("message_colour += mute:Attack whom") | |
| crawl.setopt("message_colour += mute:move target cursor") | |
| crawl.setopt("message_colour += mute:Aim:") | |
| crawl.setopt("message_colour += mute:You reach to attack") | |
| crawl.enable_more(false) | |
| end | |
| function unset_options() | |
| crawl.setopt("always_confirm_butcher = auto") | |
| crawl.setopt("pickup_mode = auto") | |
| crawl.setopt("message_colour -= mute:Search for what") | |
| crawl.setopt("message_colour -= mute:Can't find anything") | |
| crawl.setopt("message_colour -= mute:Drop what") | |
| crawl.setopt("message_colour -= mute:Okay, then") | |
| crawl.setopt("message_colour -= mute:Use which ability") | |
| crawl.setopt("message_colour -= mute:Read which item") | |
| crawl.setopt("message_colour -= mute:Drink which item") | |
| crawl.setopt("message_colour -= mute:not good enough") | |
| crawl.setopt("message_colour -= mute:Attack whom") | |
| crawl.setopt("message_colour -= mute:move target cursor") | |
| crawl.setopt("message_colour -= mute:Aim:") | |
| crawl.setopt("message_colour -= mute:You reach to attack") | |
| crawl.enable_more(true) | |
| end | |
| ------------------------------------- | |
| -- equipment valuation and autopickup | |
| -- We assign a numerical value to all armour/weapon/jewellery, which | |
| -- is used both for autopickup (so it has to work for unIDed items) and | |
| -- for equipment selection. A negative value means we prefer an empty slot. | |
| -- The valuation functions either return a pair of numbers - minimum | |
| -- minimum and maximum potential value - or the current value. Here | |
| -- value should be viewed as utility relative to not wearing anything in | |
| -- that slot. For the current value calculation, we can specify an equipped | |
| -- item and try to simulate not wearing it (for resist values). | |
| -- We pick up an item if its max value is greater than our currently equipped | |
| -- item's min value. We swap to an item if it has a greater cur value. | |
| -- if cur, return the current value instead of minmax | |
| -- if it2, pretend we aren't equipping it2 | |
| -- if sit = "hydra", assume we are fighting a hydra at lowish XL | |
| -- = "extended", assume we are in (or about to enter) Pan | |
| -- if TSO_CONVERSION, we need this weapon to be TSO-friendly | |
| -- = "bless", assume we want to bless the weapon with TSO eventually | |
| function equip_value(it, cur, it2, sit) | |
| if not it then | |
| return 0,0 | |
| end | |
| local class = it.class(true) | |
| if class == "armour" then | |
| return armour_value(it, cur, it2) | |
| elseif class == "weapon" then | |
| return weapon_value(it, cur, it2, sit) | |
| elseif class == "jewellery" then | |
| if equip_slot(it) == "Amulet" then | |
| return amulet_value(it, cur, it2) | |
| else | |
| return ring_value(it, cur, it2) | |
| end | |
| end | |
| return -1,-1 | |
| end | |
| -- Returns the amount of an artprop granted by an item - not all artprops are | |
| -- currently handled here. | |
| function item_resist(str, it) | |
| if not it then | |
| return 0 | |
| end | |
| if it.artefact and it.artprops and it.artprops[str] then | |
| return it.artprops[str] | |
| else | |
| local name = it.name() | |
| local ego = it.ego() | |
| local subtype = it.subtype() | |
| if str == "rF" then | |
| if name:find("fire dragon") then | |
| return 2 | |
| elseif ego == "fire resistance" or ego == "resistance" | |
| or subtype == "ring of protection from fire" | |
| or name:find("gold dragon") or subtype == "ring of fire" then | |
| return 1 | |
| elseif name:find("ice dragon") or subtype == "ring of ice" then | |
| return -1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "rC" then | |
| if name:find("ice dragon") then | |
| return 2 | |
| elseif ego == "cold resistance" or ego == "resistance" | |
| or subtype == "ring of protection from cold" | |
| or name:find("gold dragon") or subtype == "ring of ice" then | |
| return 1 | |
| elseif name:find("fire dragon") or subtype == "ring of fire" then | |
| return -1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "rElec" then | |
| if name:find("storm dragon") then | |
| return 1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "rPois" then | |
| if ego == "poison resistance" or subtype == "ring of poison resistance" | |
| or name:find("swamp dragon") or name:find("gold dragon") then | |
| return 1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "rN" then | |
| if ego == "positive energy" or subtype == "ring of positive energy" | |
| or name:find("pearl dragon") then | |
| return 1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "MR" then | |
| if ego == "magic resistance" or subtype == "ring of protection from magic" | |
| or name:find("quicksilver dragon") then | |
| return 1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "rCorr" then | |
| if subtype == "ring of resist corrosion" or name:find("acid dragon") then | |
| return 1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "SInv" then | |
| if ego == "see invisible" or subtype == "ring of see invisible" then | |
| return 1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "Spirit" then | |
| if ego == "spirit shield" or subtype == "amulet of guardian spirit" then | |
| return 1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "Str" then | |
| if subtype == "ring of strength" then | |
| return it.plus or 0 | |
| elseif ego == "strength" then | |
| return 3 | |
| end | |
| elseif str == "Dex" then | |
| if subtype == "ring of dexterity" then | |
| return it.plus or 0 | |
| elseif ego == "dexterity" then | |
| return 3 | |
| end | |
| elseif str == "Int" then | |
| if subtype == "ring of intelligence" then | |
| return it.plus or 0 | |
| elseif ego == "intelligence" then | |
| return 3 | |
| end | |
| elseif str == "Slay" then | |
| if subtype == "ring of slaying" then | |
| return it.plus or 0 | |
| end | |
| elseif str == "AC" then | |
| if subtype == "ring of protection" then | |
| return it.plus or 0 | |
| elseif ego == "protection" then | |
| return 3 -- wrong for weapons but we scale things differently for weapons | |
| end | |
| elseif str == "EV" then | |
| if subtype == "ring of evasion" then | |
| return it.plus or 0 | |
| end | |
| elseif str == "SH" then | |
| if subtype == "amulet of reflection" then | |
| return it.plus or 0 | |
| end | |
| end | |
| end | |
| return 0 | |
| end | |
| -- Returns the player's intrinsic level of an artprop string. | |
| function intrinsic_resist(str) | |
| if str == "rF" then | |
| return you.mutation("fire resistance") | |
| elseif str == "rC" then | |
| return you.mutation("cold resistance") | |
| elseif str == "rElec" then | |
| return you.mutation("electricity resistance") | |
| elseif str == "rPois" then | |
| if intrinsic_rpois() or (you.mutation("poison resistance") > 0) then | |
| return 1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "rN" then | |
| local val = you.mutation("negative energy resistance") | |
| if you.god() == "the Shining One" then | |
| val = val + math.floor(you.piety_rank() / 3) | |
| end | |
| return val | |
| elseif str == "MR" then | |
| return you.mutation("magic resistance") + ((you.god() == "Trog") and 1 or 0) | |
| elseif str == "rCorr" then | |
| return 0 | |
| elseif str == "SInv" then | |
| if intrinsic_sinv() or (you.mutation("see invisible") > 0) then | |
| return 1 | |
| else | |
| return 0 | |
| end | |
| elseif str == "Spirit" then | |
| return ((you.race() == "Vine Stalker") and 1 or 0) | |
| end | |
| return 0 | |
| end | |
| -- Returns the current level of "resistance" to an artprop string. If an | |
| -- item is provided, assume the item is equipped and try to pretend that | |
| -- it is unequipped. Does not include some temporary effects. | |
| function player_resist(str, it) | |
| local it_res = it and it.equipped and item_resist(str, it) or 0 | |
| if str == "Str" then | |
| stat,_ = you.strength() | |
| return stat-it_res | |
| elseif str == "Dex" then | |
| stat,_ = you.dexterity() | |
| return stat-it_res | |
| elseif str == "Int" then | |
| stat,_ = you.intelligence() | |
| return stat-it_res | |
| end | |
| local other_res = intrinsic_resist(str) | |
| for it2 in inventory() do | |
| if it2.equipped and slot(it2) ~= slot(it) then | |
| other_res = other_res + item_resist(str, it2) | |
| end | |
| end | |
| if str == "rF" or str == "rC" or str == "rN" or str == "MR" then | |
| return other_res | |
| else | |
| return ((other_res > 0) and 1 or 0) | |
| end | |
| end | |
| function equip_slot(it) | |
| local class = it.class(true) | |
| if class == "armour" then | |
| return good_slots[it.subtype()] | |
| elseif class == "weapon" then | |
| return "Weapon" | |
| elseif class == "jewellery" then | |
| local sub = it.subtype() | |
| if sub and sub:find("amulet") or not sub and it.name():find("amulet") then | |
| return "Amulet" | |
| else | |
| return "Ring" -- not the actual slot name | |
| end | |
| end | |
| return | |
| end | |
| -- The current utility of having a given amount of an artprop. | |
| function absolute_resist_value(str, n) | |
| if str == "Str" or str == "Int" or str == "Dex" then | |
| if n > 4 then | |
| return 0 -- handled by linear_resist_value() | |
| elseif n > 2 then | |
| return -100 | |
| elseif n > 0 then | |
| return -250 | |
| else | |
| return -10000 | |
| end | |
| end | |
| if n == 0 then | |
| return 0 | |
| end | |
| if slime_soon() and (str == "rF" or str == "rElec" or str == "rPois" or | |
| str == "rN" or str == "MR" or str == "SInv") then | |
| return 0 | |
| end | |
| local val = 0 | |
| if str == "rF" or str == "rC" then | |
| if n < 0 then | |
| val = -150 | |
| elseif n == 1 then | |
| val = 125 | |
| elseif n == 2 then | |
| val = 200 | |
| elseif n >= 3 then | |
| val = 250 | |
| end | |
| if str == "rF" and zot_soon() then | |
| val = val * 2.5 | |
| elseif str == "rC" and slime_soon() then | |
| val = val * 1.5 | |
| end | |
| return val | |
| elseif str == "rElec" then | |
| return 75 | |
| elseif str == "rPois" then | |
| return ((easy_runes() < 2) and 225 or 75) | |
| elseif str == "rN" then | |
| return 25*n | |
| elseif str == "MR" then | |
| if n <= 2 then | |
| return 75*n | |
| else | |
| return 200 | |
| end | |
| elseif str == "rCorr" then | |
| return (slime_soon() and 1200 or 50) | |
| elseif str == "SInv" then | |
| return 200 | |
| elseif str == "Spirit" then | |
| return ((you.race() == "Deep Dwarf") and -10000 or 75) | |
| end | |
| return 0 | |
| end | |
| function max_resist_value(str, d) | |
| if d <= 0 then | |
| return 0 | |
| end | |
| local val = 0 | |
| local ires = intrinsic_resist(str) | |
| if str == "rF" or str == "rC" then | |
| if d == 1 then | |
| val = 150 | |
| elseif d == 2 then | |
| val = 275 | |
| elseif d == 3 then | |
| val = 350 | |
| end | |
| if str == "rF" then | |
| val = val * 2.5 | |
| elseif str == "rC" and SLIMY_RUNE then | |
| val = val * 1.5 | |
| end | |
| return val | |
| elseif str == "rElec" then | |
| return ((ires < 1) and 75 or 0) | |
| elseif str == "rPois" then | |
| return ((ires < 1) and ((easy_runes() < 2) and 225 or 75) or 0) | |
| elseif str == "rN" then | |
| return ((ires < 3) and 25*d or 0) | |
| elseif str == "MR" then | |
| return 75*d | |
| elseif str == "rCorr" then | |
| return (ires < 1 and (SLIMY_RUNE and 1200 or 50) or 0) | |
| elseif str == "SInv" then | |
| return ((ires < 1) and 200 or 0) | |
| elseif str == "Spirit" then | |
| if ires > 0 then | |
| return 0 | |
| elseif you.race() == "Deep Dwarf" then | |
| return -10000 | |
| else | |
| return 75 | |
| end | |
| end | |
| return 0 | |
| end | |
| function min_resist_value(str, d) | |
| if str == "Spirit" and you.race() == "Deep Dwarf" then | |
| return -10000 | |
| end | |
| if d >= 0 then | |
| return 0 | |
| end | |
| if str == "rF" then | |
| return -375 | |
| elseif str == "rC" then | |
| return (SLIMY_RUNE and -225 or -150) | |
| elseif str == "MR" then | |
| return 75*d | |
| end | |
| return 0 | |
| end | |
| function resist_value(str, it, cur, it2) | |
| local d = item_resist(str, it) | |
| if d == 0 then | |
| return 0,0 | |
| end | |
| if cur then | |
| local c = player_resist(str, it2) | |
| local diff = absolute_resist_value(str,c+d) - absolute_resist_value(str,c) | |
| return diff,diff | |
| else | |
| return min_resist_value(str,d),max_resist_value(str,d) | |
| end | |
| end | |
| function linear_resist_value(str) | |
| if str == "Slay" or str == "AC" or str == "EV" then | |
| return 50 | |
| elseif str == "SH" then | |
| return 40 | |
| elseif str == "Str" then | |
| return 30 | |
| elseif str == "Dex" then | |
| return 20 | |
| end | |
| return 0 | |
| end | |
| function total_resist_value(it, cur, it2) | |
| resistlist = { "rF", "rC", "rElec", "rPois", "rN", "MR", "rCorr", "SInv", "Spirit", "Str", "Dex", "Int" } | |
| linearlist = { "Str", "Dex", "Slay", "AC", "EV", "SH" } | |
| local val = 0 | |
| for _,str in ipairs(linearlist) do | |
| val = val + item_resist(str, it)*linear_resist_value(str) | |
| end | |
| local val1, val2 = val,val | |
| if not only_linear_resists then | |
| for _,str in ipairs(resistlist) do | |
| local a,b = resist_value(str, it, cur, it2) | |
| val1 = val1 + a | |
| val2 = val2 + b | |
| end | |
| end | |
| return val1,val2 | |
| end | |
| function resist_vec(it) | |
| local resistlist = { "rF", "rC", "rElec", "rPois", "rN", "MR", "rCorr", "SInv", "Spirit" } | |
| local vec = { } | |
| for _,str in ipairs(resistlist) do | |
| local a,b = resist_value(str, it) | |
| table.insert(vec, b > 0 and b or a) | |
| end | |
| return vec | |
| end | |
| function base_equip_value(it) | |
| only_linear_resists = true | |
| local val1, val2 = equip_value(it) | |
| only_linear_resists = false | |
| return val1,val2 | |
| end | |
| -- complicated check: | |
| -- is it going to be worse than it2 no matter what other resists we have? | |
| function resist_dominated(it,it2) | |
| local bmin, bmax = base_equip_value(it) | |
| local bmin2, bmax2 = base_equip_value(it2) | |
| local diff = bmin2 - bmax | |
| if diff < 0 then | |
| return false | |
| end | |
| local vec = resist_vec(it) | |
| local vec2 = resist_vec(it2) | |
| local l = #vec | |
| for i = 1,l do | |
| if vec[i] > vec2[i] then | |
| diff = diff - (vec[i] - vec2[i]) | |
| end | |
| end | |
| return (diff >= 0) | |
| end | |
| function rune_goal() | |
| return 3 + (ABYSSAL_RUNE and 1 or 0) + (SLIMY_RUNE and 1 or 0) + (PAN_RUNE and 5 or 0) + (HELL_RUNE and 4 or 0) + (GOLDEN_RUNE and 1 or 0) | |
| end | |
| function easy_runes() | |
| return (you.have_rune("decaying") and 1 or 0) | |
| + (you.have_rune("serpentine") and 1 or 0) | |
| + (you.have_rune("barnacled") and 1 or 0) | |
| + (you.have_rune("gossamer") and 1 or 0) | |
| end | |
| function update_game_status() | |
| if you.have_orb() then | |
| game_status = "orbrun" | |
| return | |
| end | |
| if game_status == "normal" and you.have_rune("silver") | |
| and not where:find("Vaults") and not where:find("Abyss") then | |
| game_status = "shopping" | |
| end | |
| if game_status == "shopping" and c_persist.done_shopping then | |
| game_status = endgame_plan_list[1] | |
| end | |
| while (game_status == "slime" and you.have_rune("slimy") and not where:find("Slime") | |
| or game_status == "pan" and have_pan_runes() and where ~= "Pan" | |
| and not where:find("Abyss") | |
| or game_status == "abyss" and you.have_rune("abyssal") and not where:find("Abyss")) | |
| or game_status == "hells" and have_hell_runes() and where ~= "Hell" | |
| and not where:find("Dis") and not where:find("Geh") | |
| and not where:find("Coc") and not where:find("Tar") | |
| and not where:find("Abyss") | |
| or game_status == "tomb" and you.have_rune("golden") | |
| and not where:find("Tomb") and not where:find("Abyss") | |
| or game_status == "tso" and you.god() == "the Shining One" | |
| or game_status == "zig" and c_persist.entered_zig and not where:find("Zig") do | |
| which_endgame_plan = which_endgame_plan + 1 | |
| game_status = endgame_plan_list[which_endgame_plan] | |
| end | |
| end | |
| function zot_soon() | |
| return (game_status == "zot") | |
| end | |
| function slime_soon() | |
| return (game_status == "slime") | |
| end | |
| function in_extended() | |
| return (game_status == "pan" or game_status == "hells" or game_status == "tomb") | |
| end | |
| -- list of armour slots, this is used to normalize names for them and also | |
| -- to iterate over the slots | |
| good_slots = {cloak="Cloak", helmet="Helmet", | |
| gloves="Gloves", boots="Boots", body="Armour", shield="Shield"} | |
| function armour_value(it, cur, it2) | |
| local name = it.name() | |
| local value = 0 | |
| local val1,val2 = total_resist_value(it, cur, it2) | |
| local ego = it.ego() | |
| if it.artefact then | |
| if not it.fully_identified then -- could be good or bad | |
| val2 = val2 + 400 | |
| val1 = val1 + (cur and 400 or -400) | |
| end | |
| ap = it.artprops | |
| if ap and (ap["-Tele"] or ap["*Tele"]) and you.race() ~= "Formicid" then | |
| return -1,-1 | |
| end | |
| if ap and ap["*Rage"] and you.race() ~= "Mummy" | |
| and you.race() ~= "Ghoul" and you.race() ~= "Formicid" then | |
| return -1,-1 | |
| end | |
| if name:find("Pondering") or name:find("hauberk") then | |
| return -1,-1 | |
| end | |
| if ap and ap["Fragile"] then | |
| return -1,-1 | |
| end | |
| if ap and ap["*Slow"] and you.race() ~= "Formicid" then | |
| value = value - 100 | |
| end | |
| if ap and ap["*Corrode"] then | |
| value = value - 100 | |
| end | |
| elseif name:find("runed") or name:find("glowing") or name:find("dyed") or | |
| name:find("embroidered") or name:find("shiny") then | |
| val2 = val2 + 400 | |
| val1 = val1 + (cur and 400 or -200) | |
| elseif ego then -- names in armour_ego_name() | |
| if ego == "running" then | |
| value = value + 25 | |
| if you.god() == "Cheibriados" then | |
| return -1,-1 | |
| end | |
| elseif ego == "flying" and not intrinsic_flight() then | |
| if not intrinsic_flight() then | |
| value = value + 200 | |
| end | |
| elseif ego == "ponderousness" then | |
| return -1,-1 | |
| elseif ego == "repulsion" then | |
| value = value + 200 | |
| end | |
| end | |
| value = value + 50*expected_armour_multiplier()*it.ac | |
| if it.plus then | |
| value = value + 50*it.plus | |
| end | |
| st, _ = it.subtype() | |
| if good_slots[st] == "Shield" then | |
| if it.encumbrance == 0 then | |
| if not want_buckler() then | |
| return -1,-1 | |
| end | |
| elseif (not want_shield()) and (have_two_hander() or you.base_skill("Shields") == 0) then | |
| return -1,-1 | |
| end | |
| end | |
| -- name always starts with {boots armour} here | |
| -- ^ is no longer true I think? | |
| if good_slots[st] == "Boots" then | |
| if you.race() == "Centaur" then | |
| if not (name:find("centaur barding") | |
| or name:find("horse barding")) then | |
| return -1,-1 | |
| end | |
| elseif you.race() == "Naga" then | |
| if not (name:find("naga barding") | |
| or name:find("lightning scales")) then | |
| return -1,-1 | |
| end | |
| else | |
| if name:find("barding") then | |
| return -1,-1 | |
| end | |
| end | |
| end | |
| if good_slots[st] == "Armour" then | |
| if unfitting_armour() then | |
| value = value - 25*it.ac | |
| end | |
| evp = it.encumbrance | |
| ap = armour_plan() | |
| if ap == "heavy" or ap == "large" then | |
| if evp >= 20 then | |
| value = value - 100 | |
| elseif name:find("pearl dragon") then | |
| value = value + 100 | |
| end | |
| elseif ap == "dodgy" then | |
| if evp > 11 then | |
| return -1,-1 | |
| elseif evp > 7 then | |
| value = value - 100 | |
| end | |
| else | |
| if evp > 7 then | |
| return -1,-1 | |
| elseif evp > 4 then | |
| value = value - 100 | |
| end | |
| end | |
| end | |
| return value+val1,value+val2 | |
| end | |
| function weapon_value(it, cur, it2, sit) | |
| local hydra_swap = (sit == "hydra") | |
| local extended = (sit == "extended") | |
| local tso = you.god() == "the Shining One" or extended and TSO_CONVERSION | |
| or you.god() == "Elyvilon" | |
| or you.god() == "Zin" | |
| or you.god() == "No God" and MIGHT_BE_GOOD | |
| if it.class(true) ~= "weapon" then | |
| return -1,-1 | |
| end | |
| local name = it.name() | |
| local value = 1000 | |
| if it.weap_skill ~= wskill() then | |
| weap = items.equipped_at("Weapon") | |
| if weap and weap.weap_skill == wskill() or wskill() == "Unarmed Combat" | |
| or it.weap_skill == "Crossbows" or it.weap_skill == "Bows" | |
| or it.weap_skill == "Slings" then | |
| if not (hydra_swap and (it.weap_skill == "Maces & Flails" and wskill() == "Axes" or it.weap_skill == "Short Blades" and wskill() == "Long Blades")) then | |
| return -1,-1 | |
| end | |
| end | |
| end | |
| if it.hands == 2 and want_buckler() then | |
| return -1,-1 | |
| end | |
| if sit == "bless" then | |
| local val1, val2 = 0, 0 | |
| if it.artefact then | |
| return -1,-1 | |
| elseif name:find("runed") or name:find("glowing") | |
| or name:find("enchanted") | |
| or it.ego() and not it.fully_identified then | |
| val2 = val2 + 150 | |
| val1 = val1 + (cur and 150 or -150) | |
| end | |
| if it.plus then | |
| value = value + 30*it.plus | |
| end | |
| delay_estimate = min(7,math.floor(it.delay / 2)) | |
| if it.weap_skill == "Short Blades" and delay_estimate > 5 then | |
| delay_estimate = 5 | |
| end | |
| value = value + 1200*it.damage/delay_estimate | |
| return value+val1, value+val2 | |
| end | |
| if you.god() == "Cheibriados" and name:find("quick blade") then | |
| return -1,-1 | |
| end | |
| if tso and name:find("demon") and not name:find("eudemon") then | |
| return -1,-1 | |
| end | |
| if it.artefact then | |
| ap = it.artprops | |
| if ap and (ap["-Tele"] or ap["*Tele"]) and you.race() ~= "Formicid" then | |
| return -1,-1 | |
| end | |
| if ap and ap["*Rage"] and you.race() ~= "Mummy" | |
| and you.race() ~= "Ghoul" and you.race() ~= "Formicid" then | |
| return -1,-1 | |
| end | |
| if ap and ap["Fragile"] then | |
| return -1,-1 | |
| end | |
| if ap and ap["*Slow"] and you.race() ~= "Formicid" then | |
| value = value - 100 | |
| end | |
| if ap and ap["*Corrode"] then | |
| value = value - 100 | |
| end | |
| if (intrinsic_evil() or you.god() == "Yredelemnul") and name:find("holy") then | |
| return -1,-1 | |
| end | |
| if name:find("obsidian axe") then | |
| value = value + 300 | |
| if tso then | |
| return -1,-1 | |
| end | |
| end | |
| end | |
| local val1,val2 = total_resist_value(it, cur, it2) | |
| if it.artefact and not it.fully_identified or | |
| name:find("runed") or name:find("glowing") then | |
| val2 = val2 + 500 | |
| val1 = val1 + (cur and 500 or -250) | |
| end | |
| local ego = it.ego() | |
| if hydra_swap then | |
| if ego == "vampirism" and not intrinsic_undead() then | |
| return -1,-1 | |
| else | |
| local hydra_quality = hydra_weapon_status(it) | |
| if hydra_quality == -1 then | |
| return -1,-1 | |
| elseif hydra_quality == 1 then | |
| value = value + 500 | |
| end | |
| end | |
| end | |
| if ego then -- names are mostly in weapon_brands_verbose[] | |
| if ego == "distortion" then | |
| return -1,-1 | |
| elseif ego == "holy wrath" then | |
| if intrinsic_evil() or you.god() == "Yredelemnul" then | |
| return -1,-1 | |
| end | |
| if extended then | |
| value = value + 500 | |
| end | |
| elseif ego == "vampirism" then | |
| value = value + 500 -- this is what we want | |
| if tso then | |
| return -1,-1 | |
| end | |
| if extended then | |
| value = value - 400 | |
| end | |
| elseif ego == "speed" then | |
| value = value + 300 -- this is good too | |
| if you.god() == "Cheibriados" then | |
| return -1,-1 | |
| end | |
| elseif ego == "electrocution" then | |
| value = value + 150 -- not bad | |
| elseif ego == "draining" then | |
| if not extended then | |
| value = value + 75 | |
| end | |
| if tso then | |
| return -1,-1 | |
| end | |
| elseif ego == "flaming" or ego == "freezing" | |
| or ego == "crushing" or ego == "slicing" | |
| or ego == "piercing" or ego == "chopping" or ego == "slashing" then | |
| value = value + 75 | |
| elseif ego == "venom" then | |
| if not extended then | |
| value = value + 50 | |
| end | |
| elseif ego == "antimagic" then | |
| if you.race() == "Vine Stalker" then | |
| value = value - 300 | |
| else | |
| value = value + 75 | |
| end | |
| elseif ego == "pain" and tso then | |
| return -1,-1 | |
| elseif ego == "chaos" and (tso or you.god() == "Cheibriados") then | |
| return -1,-1 | |
| end | |
| end | |
| if it.plus then | |
| value = value + 30*it.plus | |
| end | |
| delay_estimate = min(7,math.floor(it.delay / 2)) | |
| if it.weap_skill == "Short Blades" and delay_estimate > 5 then | |
| delay_estimate = 5 | |
| end | |
| -- we might be delayed by a shield or not yet at min delay, so add a little | |
| delay_estimate = delay_estimate + 1 | |
| value = value + 1200*it.damage/delay_estimate | |
| -- subtract a bit for very slow weapons because of how much skill they require to reach min delay | |
| if it.delay > 17 then | |
| value = value - 120*(it.delay - 17) | |
| end | |
| if it.weap_skill ~= wskill() then | |
| value = value / 10 | |
| val1 = val1 / 10 | |
| val2 = val2 / 10 | |
| end | |
| return value+val1,value+val2 | |
| end | |
| function amulet_value(it, cur, it2) | |
| local name = it.name() | |
| local subtype = it.subtype() | |
| local value = 0 | |
| if it.artefact then | |
| ap = it.artprops | |
| if ap and (ap["-Tele"] or ap["*Tele"]) and you.race() ~= "Formicid" then | |
| return -1,-1 | |
| end | |
| if ap and ap["*Rage"] and you.race() ~= "Mummy" | |
| and you.race() ~= "Ghoul" and you.race() ~= "Formicid" then | |
| return -1,-1 | |
| end | |
| if name:find("macabre finger necklace") then | |
| return -1,-1 | |
| end | |
| if ap and ap["Fragile"] then | |
| return -1,-1 | |
| end | |
| if ap and ap["*Slow"] and you.race() ~= "Formicid" then | |
| value = value - 100 | |
| end | |
| if ap and ap["*Corrode"] then | |
| value = value - 100 | |
| end | |
| end | |
| if subtype == "amulet of faith" then | |
| if you.god() == "Cheibriados" or you.god() == "Beogh" or you.god() == "Qazlal" or you.god() == "Hepliaklqana" then | |
| return -1,-1 -- we don't use piety much on these gods at the moment | |
| elseif you.god() ~= "Ru" and you.god() ~= "Xom" then | |
| return 1000,1000 -- fixed value so we don't unequip for a randart one | |
| end | |
| end | |
| if it.artefact and not it.fully_identified or not (it.artefact or name:find("amulet of")) then | |
| if cur then | |
| return 800,800 | |
| else | |
| return -1,1000 | |
| end | |
| end | |
| local val1,val2 = total_resist_value(it, cur, it2) | |
| if subtype == "amulet of reflection" then | |
| value = value + 20 -- for reflection | |
| if not it.artefact and not it.plus then | |
| value = value + 6*40 | |
| if not cur then | |
| val1 = val1 - 4*40 | |
| end | |
| end | |
| elseif subtype == "amulet of regeneration" and you.race() ~= "Deep Dwarf" then | |
| value = value + 50 | |
| elseif subtype == "amulet of inaccuracy" then | |
| value = value - 250 | |
| elseif subtype == "amulet of harm" then | |
| value = value - 150 | |
| end | |
| return value+val1,value+val2 | |
| end | |
| function ring_value(it, cur, it2) | |
| local name = it.name() | |
| local subtype = it.subtype() | |
| local value = 0 | |
| if it.artefact then | |
| ap = it.artprops | |
| if ap and (ap["-Tele"] or ap["*Tele"]) and you.race() ~= "Formicid" then | |
| return -1,-1 | |
| end | |
| if ap and ap["*Rage"] and you.race() ~= "Mummy" | |
| and you.race() ~= "Ghoul" and you.race() ~= "Formicid" then | |
| return -1,-1 | |
| end | |
| if ap and ap["Fragile"] then | |
| return -1,-1 | |
| end | |
| if ap and ap["*Slow"] and you.race() ~= "Formicid" then | |
| value = value - 100 | |
| end | |
| if ap and ap["*Corrode"] then | |
| value = value - 100 | |
| end | |
| end | |
| if subtype == "ring of teleportation" and you.race() ~= "Formicid" then | |
| return -1,-1 | |
| end | |
| if it.artefact and not it.fully_identified or not (it.artefact or name:find("ring of")) then | |
| if cur then | |
| return 5000,5000 | |
| else | |
| return -1,5000 | |
| end | |
| end | |
| local val1,val2 = total_resist_value(it, cur, it2) | |
| if not it.artefact and not it.plus then | |
| local linval = 0 | |
| if subtype == "ring of slaying" or subtype == "ring of protection" | |
| or subtype == "ring of evasion" then | |
| linval = 50 | |
| elseif subtype == "ring of strength" then | |
| linval = 30 | |
| elseif subtype == "ring of dexterity" then | |
| linval = 20 | |
| end | |
| value = value + 6*linval | |
| if not cur then | |
| val1 = val1 - 12*linval | |
| end | |
| end | |
| return value+val1,value+val2 | |
| end | |
| function count_charges(wand_type, ignore_it) | |
| count = 0 | |
| for it in inventory() do | |
| if it.class(true) == "wand" | |
| and (ignore_it == nil or slot(it) ~= slot(ignore_it)) | |
| and it.subtype() == wand_type then | |
| if it.plus then | |
| count = count + it.plus | |
| elseif it.plus == nil and not it.name():find("empty") then | |
| if it.name():find("zapped") then | |
| count = count + 3 | |
| else | |
| count = count + 6 | |
| end | |
| end | |
| end | |
| end | |
| return count | |
| end | |
| function want_wand(it) | |
| if you.mutation("inability to use devices") > 0 then | |
| return false | |
| end | |
| only_wand = true | |
| for it2 in inventory() do | |
| if only_wand and it2.class(true) == "wand" and slot(it2) ~= slot(it) then | |
| only_wand = false | |
| end | |
| end | |
| if only_wand then | |
| return true -- for Evo training | |
| end | |
| sub = it.subtype() | |
| if sub == nil then | |
| return true | |
| end | |
| if sub ~= "digging" then | |
| return false | |
| end | |
| if it.name():find("empty") or it.plus == 0 then | |
| for it2 in inventory() do | |
| if it2.class(true) == "wand" and slot(it2) ~= slot(it) and | |
| it2.subtype() == sub then | |
| return false | |
| end | |
| end | |
| elseif sub == "digging" then | |
| return (count_charges("digging", it) < 18) | |
| end | |
| return true | |
| end | |
| function want_potion(it) | |
| sub = it.subtype() | |
| if sub == nil then | |
| return true | |
| end | |
| if sub == "blood" and you.race() == "Vampire" then | |
| return true | |
| end | |
| if sub ~= "curing" and sub ~= "heal wounds" and sub ~= "haste" | |
| and sub ~= "resistance" and sub ~= "experience" and sub ~= "might" | |
| and sub ~= "mutation" and sub ~= "cancellation" then | |
| return false | |
| end | |
| return true | |
| end | |
| function want_scroll(it) | |
| sub = it.subtype() | |
| if sub == nil then | |
| return true | |
| end | |
| if sub ~= "identify" and sub ~= "teleportation" and sub ~= "remove curse" | |
| and sub ~= "enchant weapon" and sub ~= "enchant armour" | |
| and sub ~= "acquirement" and sub ~= "recharging" | |
| and sub ~= "brand weapon" | |
| and (not WILL_ZIG or sub ~= "fog" and sub ~= "blinking") then | |
| return false | |
| end | |
| return true | |
| end | |
| -- This doesn't handle rings correctly at the moment, but right now we are | |
| -- only using this for weapons anyway. | |
| -- Also maybe this should check resist_dominated too? | |
| function item_is_sit_dominated(it,sit) | |
| local slotname = equip_slot(it) | |
| local minv,maxv = equip_value(it, nil, nil, sit) | |
| if maxv <= 0 then | |
| return true | |
| end | |
| for it2 in inventory() do | |
| if equip_slot(it2) == slotname and slot(it2) ~= slot(it) then | |
| local minv2,maxv2 = weapon_value(it2, nil, nil, sit) | |
| if minv2 >= maxv and not | |
| (slotname == "Weapon" and you.base_skill("Shields") > 0 | |
| and it.hands == 1 and it2.hands == 2) then | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function item_is_dominated(it) | |
| local slotname = equip_slot(it) | |
| if slotname == "Weapon" and you.xl() < 18 | |
| and not item_is_sit_dominated(it, "hydra") then | |
| return false | |
| elseif (PAN_RUNE or HELL_RUNE or GOLDEN_RUNE) and slotname == "Weapon" | |
| and not item_is_sit_dominated(it, "extended") then | |
| return false | |
| elseif slotname == "Weapon" | |
| and (you.god() == "the Shining One" and not you.one_time_ability_used() | |
| or you.god() ~= "the Shining One" and TSO_CONVERSION) | |
| and not item_is_sit_dominated(it, "bless") then | |
| return false | |
| end | |
| local minv,maxv = equip_value(it) | |
| if maxv <= 0 then | |
| return true | |
| end | |
| local num_slots = 1 | |
| if slotname == "Ring" then | |
| num_slots = max_rings() | |
| end | |
| for it2 in inventory() do | |
| if equip_slot(it2) == slotname and slot(it2) ~= slot(it) then | |
| local minv2,maxv2 = equip_value(it2) | |
| if minv2 >= maxv or | |
| minv2 >= minv and maxv2 >= maxv and resist_dominated(it,it2) then | |
| num_slots = num_slots - 1 | |
| if num_slots == 0 then | |
| return true | |
| end | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function should_drop(it) | |
| return item_is_dominated(it) | |
| end | |
| -- assumes old_it is equipped | |
| function should_upgrade(it,old_it,sit) | |
| if not old_it then | |
| return should_equip(it,sit) | |
| end | |
| if not it.fully_identified and not should_drop(it) then | |
| if equip_slot(it) == "Weapon" and it.weap_skill ~= wskill() then | |
| return (count_item("scroll", "remove curse") > 0) | |
| end | |
| return (old_it.subtype() ~= "amulet of faith") | |
| end | |
| return (equip_value(it,true,old_it,sit) > equip_value(old_it,true,old_it,sit)) | |
| end | |
| -- assumes it is not equipped and an empty slot is available | |
| function should_equip(it,sit) | |
| return (equip_value(it,true,nil,sit) > 0) | |
| end | |
| -- assumes it is equipped | |
| function should_remove(it) | |
| return (equip_value(it,true,it) <= 0) | |
| end | |
| function want_missile(it) | |
| st = it.subtype() | |
| if not no_spells and st == "stone" and starting_spell() == "Sandblast" then | |
| return true | |
| end | |
| return (st == "large rock" and (you.race() == "Troll" or you.race() == "Ogre") or st == "javelin" and you.xl() < 21 or st == "tomahawk" and you.xl() < 15) | |
| end | |
| function autopickup(it, name) | |
| if name:find("of Zot") then | |
| return true | |
| end | |
| if it.is_useless then | |
| return false | |
| end | |
| local class = it.class(true) | |
| old_value = 0 | |
| new_value = 0 | |
| ring = false | |
| if class == "armour" or class == "weapon" or class == "jewellery" then | |
| return not item_is_dominated(it) | |
| elseif class == "food" or class == "gold" then | |
| return true | |
| elseif class == "potion" then | |
| return want_potion(it) | |
| elseif class == "scroll" then | |
| return want_scroll(it) | |
| elseif class == "wand" then | |
| return want_wand(it) | |
| elseif class == "missile" then | |
| return want_missile(it) | |
| else | |
| return false | |
| end | |
| end | |
| clear_autopickup_funcs() | |
| add_autopickup_func(autopickup) | |
| ------------------------------ | |
| -- some tables with hardcoded data about branches/portals/monsters: | |
| -- branch data: interlevel travel code, where name | |
| local branch_data = { | |
| {"T", "Temple"}, | |
| {"O", "Orc"}, | |
| --{"E", "Elf"}, | |
| {"L", "Lair"}, | |
| {"S", "Swamp"}, | |
| {"A", "Shoals"}, | |
| {"P", "Snake"}, | |
| {"N", "Spider"}, | |
| {"M", "Slime"}, | |
| {"V", "Vaults"}, | |
| {"C", "Crypt"}, | |
| {"W", "Tomb"}, | |
| {"D", "D:"}, | |
| {"U", "Depths"}, | |
| {"H", "Hell"}, | |
| {"I", "Dis"}, | |
| {"G", "Geh"}, | |
| {"X", "Coc"}, | |
| {"Y", "Tar"}, | |
| {"Z", "Zot"}, | |
| } -- hack | |
| -- portal data: where name, full name, feature name | |
| local portal_data = { | |
| --{"Bailey", "a flagged portal", "bailey"}, | |
| {"Bazaar", "gateway to a bazaar", "bazaar"}, | |
| --{"IceCv", "a frozen archway", "ice_cave"}, | |
| {"Ossuary", "covered staircase", "ossuary"}, | |
| {"Sewer", "a glowing drain", "sewer"}, | |
| --{"Volcano", "a dark tunnel", "volcano"}, | |
| --{"WizLab", "a magical portal", "wizlab"}, | |
| --{"Desolation", "ruined gateway", "desolation"}, | |
| --{"Lab", "labyrinth entrance", "labyrinth"}, | |
| } -- hack | |
| -- functions for use in the monster lists below | |
| function in_desc(lev,str) | |
| return function (m) | |
| return (you.xl() < lev and m:desc():find(str)) | |
| end | |
| end | |
| function pan_lord(lev) | |
| return function (m) | |
| return (you.xl() < lev and m:type() == ENUM_MONS_PANDEMONIUM_LORD) | |
| end | |
| end | |
| local res_func_table = { | |
| rF=you.res_fire, | |
| rC=you.res_cold, | |
| rPois=you.res_poison, | |
| rElec=you.res_shock, | |
| rN=you.res_draining, | |
| rCorr=(function() return you.res_corr() and 1 or 0 end), -- returns a boolean | |
| --MR=you.res_magic, -- (doesn't exist yet...) | |
| } --hack | |
| function check_resist(lev,resist,value) | |
| return function (m) | |
| return (you.xl() < lev and res_func_table[resist]() < value) | |
| end | |
| end | |
| function slow_berserk(lev) | |
| return function (m) | |
| return (you.xl() < lev and count_nearby(0,0,1) > 0) | |
| end | |
| end | |
| function hydra_weapon_status(weap) | |
| if not weap then | |
| return 0 | |
| end | |
| local sk = weap.weap_skill | |
| if sk == "Maces & Flails" or sk == "Short Blades" | |
| or sk == "Polearms" and weap.hands == 1 then | |
| return 0 | |
| elseif weap.ego() == "flaming" then | |
| return 1 | |
| else | |
| return -1 | |
| end | |
| end | |
| function hydra_check_flaming(lev) | |
| return function (m) | |
| return (you.xl() < lev and m:desc():find("hydra") | |
| and hydra_weapon_status(items.equipped_at("Weapon")) ~= 1) | |
| end | |
| end | |
| -- The format in monster lists below is that a num is equivalent to checking | |
| -- XL < num, otherwise we want a function. ["*"] should be a table of | |
| -- functions to check for every monster. | |
| -- berserk these | |
| local scary_monsters = { | |
| ["*"] = { | |
| in_desc(15,"hydra"), | |
| hydra_check_flaming(20), | |
| in_desc(100,"berserk[^e]"), | |
| in_desc(100,"statue"), | |
| in_desc(100,"'s ghost"), | |
| in_desc(100,"' ghost"), | |
| in_desc(100,"'s illusion"), | |
| in_desc(100,"' illusion"), | |
| pan_lord(100), | |
| }, | |
| ["Terence"] = 3, | |
| ["worm"] = slow_berserk(4), | |
| ["gnoll"] = 5, | |
| ["ice beast"] = 5, | |
| ["iguana"] = 5, | |
| ["Natasha"] = 5, | |
| ["Robin"] = 5, | |
| ["Ijyb"] = 7, | |
| ["ice beast"] = check_resist(7,"rC",1), | |
| ["orc wizard"] = 7, | |
| ["Grinder"] = 7, | |
| ["Dowan"] = 7, | |
| ["Duvessa"] = 7, | |
| ["Menkaure"] = 7, | |
| ["Edmund"] = 7, | |
| ["Blork the orc"] = 7, | |
| ["Eustachio"] = 7, | |
| ["gnoll sergeant"] = 7, | |
| ["Prince Ribbit"] = 10, | |
| ["Pikel"] = 10, | |
| ["Crazy Yiuf"] = 10, | |
| ["Sigmund"] = 10, | |
| ["ogre"] = 10, | |
| ["decayed bog body"] = 10, | |
| ["two-headed ogre"] = 12, | |
| ["orc priest"] = 12, | |
| ["orc warrior"] = 12, | |
| ["troll"] = 12, | |
| ["cyclops"] = 12, | |
| ["spiny frog"] = 12, | |
| ["black mamba"] = 12, | |
| ["Snorg"] = 12, | |
| ["Harold"] = 12, | |
| ["komodo dragon"] = 12, | |
| ["Gastronok"] = 12, | |
| ["snapping turtle"] = 12, | |
| ["Urug"] = 12, | |
| ["Grum"] = 12, | |
| ["electric eel"] = 12, | |
| ["Nergalle"] = 12, | |
| ["jelly"] = 12, | |
| ["manticore"] = 12, | |
| ["guardian mummy"] = 12, | |
| ["Psyche"] = 12, | |
| ["oklob sapling"] = 12, | |
| ["blink frog"] = 13, | |
| ["death yak"] = 15, | |
| ["Erica"] = 15, | |
| ["catoblepas"] = 15, | |
| ["orc knight"] = 15, | |
| ["swamp worm"] = 15, | |
| ["fire dragon"] = 17, | |
| ["ice dragon"] = 17, | |
| ["storm dragon"] = 17, | |
| ["ogre mage"] = 17, | |
| ["orc sorcerer"] = 17, | |
| ["orc high priest"] = 17, | |
| ["orc warlord"] = 17, | |
| ["dire elephant"] = 17, | |
| ["very large slime creature"] = 17, | |
| ["skeletal warrior"] = 17, | |
| ["Arachne"] = 17, | |
| ["deep troll"] = 17, | |
| ["thorn hunter"] = 17, | |
| ["sun demon"] = 17, | |
| ["white ugly thing"] = check_resist(17,"rC",1), | |
| ["white very ugly thing"] = check_resist(17,"rC",1), | |
| ["nagaraja"] = 20, | |
| ["naga sharpshooter"] = 20, | |
| ["emperor scorpion"] = 20, | |
| ["Donald"] = 20, | |
| ["Rupert"] = 20, | |
| ["Aizul"] = 20, | |
| ["Azrael"] = 20, | |
| ["Frances"] = 20, | |
| ["Saint Roka"] = 20, | |
| ["Agnes"] = 20, | |
| ["Jory"] = 20, | |
| ["Nikola"] = 20, | |
| ["stone giant"] = 20, | |
| ["fire giant"] = 20, | |
| ["frost giant"] = 20, | |
| ["acid blob"] = 20, | |
| ["azure jelly"] = 20, | |
| ["Asterion"] = 20, | |
| ["spriggan defender"] = 20, | |
| ["spriggan air mage"] = 20, | |
| ["spriggan druid"] = 20, | |
| ["deep troll shaman"] = 20, | |
| ["Xtahua"] = 20, | |
| ["tengu reaver"] = 20, | |
| ["ettin"] = 20, | |
| ["Polyphemus"] = 20, | |
| ["Bai Suzhen"] = 20, | |
| ["storm dragon"] = check_resist(24,"rElec",1), | |
| ["fire giant"] = check_resist(24,"rF",1), | |
| ["frost giant"] = check_resist(24,"rC",1), | |
| ["azure jelly"] = check_resist(24,"rC",1), | |
| ["Xtahua"] = check_resist(100,"rF",1), | |
| ["deep elf annihilator"] = 100, | |
| ["deep elf sorcerer"] = 100, | |
| ["the Enchantress"] = 100, | |
| ["Vashnia"] = 100, | |
| ["Sojobo"] = 100, | |
| ["Roxanne"] = 100, | |
| ["Erolcha"] = 100, | |
| ["Nessos"] = 100, | |
| ["Sonja"] = 100, | |
| ["Louise"] = 100, | |
| ["Mennas"] = 100, | |
| ["Margery"] = check_resist(100,"rF",2), | |
| ["Frederick"] = 100, | |
| ["Boris"] = 100, | |
| ["Mara"] = 100, | |
| ["boggart"] = 100, | |
| ["lich"] = 100, | |
| ["ancient lich"] = 100, | |
| ["oklob plant"] = 100, | |
| ["hellion"] = 100, | |
| ["tormentor"] = 100, | |
| ["Hell Sentinel"] = 100, | |
| ["Ice Fiend"] = 100, | |
| ["Tzitzimitl"] = 100, | |
| ["Brimstone Fiend"] = 100, | |
| ["curse toe"] = 100, | |
| ["curse skull"] = 100, | |
| ["Tiamat"] = 100, | |
| ["titanic slime creature"] = 100, | |
| ["enormous slime creature"] = 100, | |
| ["titan"] = 100, | |
| ["orb of fire"] = 100, | |
| ["caustic shrike"] = 100, | |
| ["seraph"] = 100, | |
| ["juggernaut"] = 100, | |
| ["Royal Jelly"] = 100, | |
| ["iron giant"] = 100, | |
| ["Cerebov"] = 100, | |
| ["Gloorx Vloq"] = 100, | |
| ["Lom Lobon"] = 100, | |
| ["Mnoleg"] = 100, | |
| ["Dispater"] = 100, | |
| ["Asmodeus"] = 100, | |
| ["Antaeus"] = 100, | |
| ["Ereshkigal"] = 100, | |
| ["greater mummy"] = 100, | |
| ["Khufu"] = 100, | |
| } -- hack | |
| -- BiA these even at low piety | |
| local bia_necessary_monsters = { | |
| ["*"] = { | |
| hydra_check_flaming(15), | |
| in_desc(100,"statue"), | |
| }, | |
| ["orb spider"] = 20, | |
| } -- hack | |
| -- BiA these | |
| local bia_monsters = { | |
| ["*"] = { | |
| hydra_check_flaming(17), | |
| in_desc(100,"statue"), | |
| in_desc(100,"'s ghost"), | |
| in_desc(100,"' ghost"), | |
| pan_lord(100), | |
| }, | |
| ["Rupert"] = 15, | |
| ["Azrael"] = 15, | |
| ["fire dragon"] = 15, | |
| ["ice dragon"] = 15, | |
| ["Snorg"] = 15, | |
| ["death yak"] = 15, | |
| ["red devil"] = 15, | |
| ["Nikola"] = 17, | |
| ["orc warlord"] = 20, | |
| ["Aizul"] = 20, | |
| ["Frances"] = 20, | |
| ["Saint Roka"] = 20, | |
| ["Agnes"] = 20, | |
| ["Jory"] = 20, | |
| ["Arachne"] = 20, | |
| ["Nikola"] = check_resist(20,"rElec",1), | |
| ["Vashnia"] = 20, | |
| ["Asterion"] = 20, | |
| ["orb spider"] = 20, | |
| ["thorn hunter"] = 20, | |
| ["sun demon"] = check_resist(20,"rF",1), | |
| ["Polyphemus"] = 20, | |
| ["Ilsuiw"] = 20, | |
| ["Bai Suzhen"] = 20, | |
| ["merfolk avatar"] = 20, | |
| ["deep troll shaman"] = 100, | |
| ["spriggan air mage"] = check_resist(100,"rElec",1), | |
| ["the Enchantress"] = 100, | |
| ["Sojobo"] = 100, | |
| ["Roxanne"] = 100, | |
| ["Erolcha"] = 100, | |
| ["Nessos"] = 100, | |
| ["Sonja"] = 100, | |
| ["Louise"] = 100, | |
| ["Mennas"] = 100, | |
| ["Margery"] = 100, | |
| ["Frederick"] = 100, | |
| ["Boris"] = 100, | |
| ["Mara"] = 100, | |
| ["boggart"] = 100, | |
| ["lich"] = 100, | |
| ["ancient lich"] = 100, | |
| ["oklob plant"] = 100, | |
| ["Hell Sentinel"] = 100, | |
| ["Ice Fiend"] = 100, | |
| ["Brimstone Fiend"] = 100, | |
| ["Tzitzimitl"] = 100, | |
| ["Tiamat"] = 100, | |
| ["orb of fire"] = 100, | |
| ["caustic shrike"] = 100, | |
| ["seraph"] = 100, | |
| ["Royal Jelly"] = 100, | |
| ["spark wasp"] = check_resist(100,"rElec",1), | |
| ["juggernaut"] = 100, | |
| ["iron giant"] = 100, | |
| ["entropy weaver"] = check_resist(100,"rCorr",1), | |
| ["Cerebov"] = 100, | |
| ["Gloorx Vloq"] = 100, | |
| ["Lom Lobon"] = 100, | |
| ["Mnoleg"] = 100, | |
| ["Dispater"] = 100, | |
| ["Asmodeus"] = 100, | |
| ["Antaeus"] = 100, | |
| ["Ereshkigal"] = 100, | |
| ["greater mummy"] = 100, | |
| ["Khufu"] = 100, | |
| } -- hack | |
| -- Use haste/might on these few | |
| local ridiculous_uniques = { | |
| ["*"] = {}, | |
| ["Antaeus"] = 100, | |
| ["Asmodeus"] = 100, | |
| ["Lom Lobon"] = 100, | |
| } -- hack | |
| -- Trog's Hand these | |
| local hand_monsters = { | |
| ["*"] = {}, | |
| ["Grinder"] = 10, | |
| ["orc sorcerer"] = 17, | |
| ["wizard"] = 17, | |
| ["ogre mage"] = 100, | |
| ["Rupert"] = 100, | |
| ["Xtahua"] = 100, | |
| ["Aizul"] = 100, | |
| ["Erolcha"] = 100, | |
| ["Louise"] = 100, | |
| ["lich"] = 100, | |
| ["ancient lich"] = 100, | |
| ["Kirke"] = 100, | |
| ["golden eye"] = 100, | |
| ["deep elf sorcerer"] = 100, | |
| ["deep elf demonologist"] = 100, | |
| ["sphinx"] = 100, | |
| ["great orb of eyes"] = 100, | |
| ["vault sentinel"] = 100, | |
| ["the Enchantress"] = 100, | |
| ["satyr"] = 100, | |
| ["vampire knight"] = 100, | |
| ["siren"] = 100, | |
| ["merfolk avatar"] = 100, | |
| } -- hack | |
| -- potion of resistance these | |
| local fire_resistance_monsters = { | |
| ["*"] = {}, | |
| ["Margery"] = check_resist(100,"rF",2), | |
| ["orb of fire"] = 100, | |
| ["hellephant"] = check_resist(100,"rF",2), | |
| ["Xtahua"] = check_resist(100,"rF",2), | |
| ["Cerebov"] = 100, | |
| ["Asmodeus"] = check_resist(100,"rF",2), | |
| } -- hack | |
| local cold_resistance_monsters = { | |
| ["*"] = {}, | |
| ["Ice Fiend"] = 100, | |
| ["Antaeus"] = 100, | |
| } -- hack | |
| local elec_resistance_monsters = { | |
| ["*"] = { | |
| in_desc(20,"black draconian"), | |
| }, | |
| ["storm dragon"] = 20, | |
| ["electric golem"] = 100, | |
| ["spark wasp"] = 100, | |
| ["Antaeus"] = 100, | |
| } -- hack | |
| local pois_resistance_monsters = { | |
| ["*"] = {}, | |
| ["swamp drake"] = 100, | |
| } -- hack | |
| local acid_resistance_monsters = { | |
| ["*"] = {}, | |
| ["acid blob"] = 100, | |
| } -- hack | |
| ----------------------------------------- | |
| -- player functions | |
| -- "intrinsics" that shouldn't change over the course of the game: | |
| function intrinsic_rpois() | |
| local sp = you.race() | |
| if sp == "Gargoyle" or sp == "Naga" or sp == "Ghoul" or sp == "Mummy" then | |
| return true | |
| end | |
| return false | |
| end | |
| function intrinsic_relec() | |
| local sp = you.race() | |
| if sp == "Gargoyle" then | |
| return true | |
| end | |
| return false | |
| end | |
| function intrinsic_sinv() | |
| local sp = you.race() | |
| if sp == "Naga" or sp == "Felid" or sp == "Formicid" or sp == "Vampire" then | |
| return true | |
| end | |
| -- we assume TSO piety won't drop below 2* and that we won't change gods | |
| -- away from TSO | |
| if you.god() == "the Shining One" and you.piety_rank() >= 2 then | |
| return true | |
| end | |
| return false | |
| end | |
| function intrinsic_flight() -- or swimming | |
| local sp = you.race() | |
| if sp == "Gargoyle" or sp == "Tengu" or sp == "Black Draconian" or | |
| sp == "Merfolk" or sp == "Octopode" or sp == "Barachi" then | |
| return true | |
| end | |
| return false | |
| end | |
| function intrinsic_fumble() | |
| local sp = you.race() | |
| if sp == "Merfolk" or sp == "Octopode" or sp == "Grey Draconian" or | |
| sp == "Centaur" or sp == "Naga" or sp == "Troll" or sp == "Ogre" | |
| or sp == "Barachi" then | |
| return false | |
| end | |
| return true | |
| end | |
| function intrinsic_evil() | |
| local sp = you.race() | |
| if sp == "Demonspawn" or sp == "Mummy" or sp == "Ghoul" or | |
| sp == "Vampire" then | |
| return true | |
| end | |
| return false | |
| end | |
| -- not exactly gourmand, but close enough | |
| function intrinsic_gourmand() | |
| return (you.race() == "Kobold" or you.race() == "Troll" | |
| or you.race() == "Felid") | |
| end | |
| function intrinsic_undead() | |
| return (you.race() == "Ghoul" or you.race() == "Mummy" | |
| or you.race() == "Vampire") | |
| end | |
| -- We group all species into four categories: | |
| -- heavy: species that can use arbitrary armour and aren't particularly great | |
| -- at dodging | |
| -- dodgy: species that can use arbitrary armour but are very good at dodging | |
| -- large: species with armour restrictions that want heavy dragon scales | |
| -- light: species with no body armour or who don't want anything heavier than | |
| -- 7 encumbrance | |
| function armour_plan() | |
| local sp = you.race() | |
| if sp == "Ogre" or sp == "Troll" then | |
| return "large" | |
| elseif sp == "Deep Elf" or sp == "Kobold" | |
| or sp == "Merfolk" then | |
| return "dodgy" | |
| elseif sp:find("Draconian") or sp == "Felid" or sp == "Octopode" | |
| or sp == "Spriggan" then | |
| return "light" | |
| else | |
| return "heavy" | |
| end | |
| end | |
| function expected_armour_multiplier() | |
| local ap = armour_plan() | |
| if ap == "heavy" then | |
| return 2 | |
| elseif ap == "large" or ap == "dodgy" then | |
| return 1.5 | |
| else | |
| return 1.25 | |
| end | |
| end | |
| function unfitting_armour() | |
| local sp = you.race() | |
| return (armour_plan() == "large" or sp == "Centaur" or sp == "Naga") | |
| end | |
| function want_buckler() | |
| if you.race() == "Felid" then | |
| return false | |
| end | |
| if SHIELD_CRAZY then | |
| return true | |
| end | |
| if wskill() == "Short Blades" or wskill() == "Unarmed Combat" then | |
| return true | |
| end | |
| if you.race() == "Formicid" or you.race() == "Halfling" | |
| or you.race() == "Kobold" then | |
| return true | |
| end | |
| return false | |
| end | |
| function want_shield() | |
| if not want_buckler() then | |
| return false | |
| end | |
| if SHIELD_CRAZY then | |
| return true | |
| end | |
| return (you.race() == "Troll" or you.race() == "Formicid") | |
| end | |
| -- used for backgrounds who don't get to choose a weapon | |
| function weapon_choice() | |
| sp = you.race() | |
| if sp == "Felid" or sp == "Troll" or sp == "Ghoul" then | |
| return "Unarmed Combat" | |
| elseif sp == "Ogre" or sp == "Kobold" then | |
| return "Maces & Flails" | |
| elseif sp == "Merfolk" then | |
| return "Polearms" | |
| elseif sp == "Spriggan" then | |
| return "Short Blades" | |
| else | |
| return "Axes" | |
| end | |
| end | |
| function wskill() | |
| -- cache in case you unwield a weapon somehow | |
| if c_persist.cached_wskill then | |
| return c_persist.cached_wskill | |
| end | |
| weap = items.equipped_at("Weapon") | |
| if weap and weap.class(true) == "weapon" | |
| and weap.weap_skill ~= "Short Blades" | |
| and you.class() ~= "Wanderer" then | |
| c_persist.cached_wskill = weap.weap_skill | |
| else | |
| c_persist.cached_wskill = weapon_choice() | |
| end | |
| return c_persist.cached_wskill | |
| end | |
| function max_rings() | |
| if you.race() == "Octopode" then | |
| return 8 | |
| else | |
| return 2 | |
| end | |
| end | |
| -- other player functions | |
| function hp_is_low(percentage) | |
| local hp, mhp = you.hp() | |
| return (100*hp <= percentage*mhp) | |
| end | |
| function chp() | |
| local hp, mhp = you.hp() | |
| return hp | |
| end | |
| function cmp() | |
| local mp, mmp = you.mp() | |
| return mp | |
| end | |
| function meph_immune() | |
| -- should also check clarity and unbreathing | |
| return (you.res_poison() >= 1) | |
| end | |
| function miasma_immune() | |
| -- this isn't all the cases, I know | |
| return (you.race() == "Gargoyle" or you.race() == "Vine Stalker" | |
| or you.race() == "Ghoul" or you.race() == "Mummy") | |
| end | |
| function is_waypointable(loc) | |
| return (not is_portal_location(loc) and not loc:find("Abyss") and loc ~= "Pan" and not loc:find("Zig")) | |
| end | |
| function is_portal_location(loc) | |
| for _, value in ipairs(portal_data) do | |
| if value[1] == loc then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function in_portal() | |
| return is_portal_location(where) | |
| end | |
| function get_feat_name(where_name) | |
| for _, value in ipairs(portal_data) do | |
| if where_name == value[1] then | |
| return value[3] | |
| end | |
| end | |
| end | |
| function cur_branch() | |
| for _, value in ipairs(branch_data) do | |
| if where:find(value[2]) then | |
| return value[1] | |
| end | |
| end | |
| end | |
| function found_branch(br) | |
| if br == "D" then | |
| return true | |
| end | |
| for _, value in ipairs(branch_data) do | |
| if value[1] == br then | |
| if travel.find_deepest_explored(value[2]) > 0 then | |
| return true | |
| else | |
| return false | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function in_branch(br) | |
| for _, value in ipairs(branch_data) do | |
| if value[1] == br then | |
| if where:find(value[2]) then | |
| return true | |
| else | |
| return false | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function is_traversable(x,y) | |
| local feat = view.feature_at(x,y) | |
| return feat ~= "unseen" and travel.feature_traversable(feat) | |
| end | |
| function is_cornerish(x,y) | |
| if is_traversable(x+1,y+1) or is_traversable(x+1,y-1) | |
| or is_traversable(x-1,y+1) or is_traversable(x-1,y-1) then | |
| return false | |
| end | |
| return ((is_traversable(x+1,y) or is_traversable(x-1,y)) | |
| and (is_traversable(x,y+1) or is_traversable(x,y-1))) | |
| end | |
| function is_solid(x,y) | |
| local feat = view.feature_at(x,y) | |
| return feat == "unseen" or travel.feature_solid(feat) | |
| end | |
| function dangerous_to_rest() | |
| if danger then | |
| return true | |
| end | |
| for x = -1,1 do | |
| for y = -1,1 do | |
| if view.feature_at(x,y) == "slimy_wall" then | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function starving() | |
| return (you.hunger_name() == "starving" or you.hunger_name() == "fainting") | |
| end | |
| function transformed() | |
| return (you.transform() ~= "") | |
| end | |
| function can_read() | |
| if you.berserk() or you.confused() or you.silenced() | |
| or you.status("engulfed (cannot breathe)") then | |
| return false | |
| end | |
| return true | |
| end | |
| function can_drink() | |
| if you.berserk() or you.race() == "Mummy" or you.transform() == "bat" | |
| or you.transform() == "lich" or you.status("no potions") then | |
| return false | |
| end | |
| return true | |
| end | |
| function can_zap() | |
| if you.berserk() or you.confused() or transformed() then | |
| return false | |
| end | |
| if you.mutation("inability to use devices") > 0 then | |
| return false | |
| end | |
| local x = you.mutation("MP-powered wands") | |
| if x > 0 then | |
| if cmp() < 3*x then | |
| return false | |
| end | |
| end | |
| return true | |
| end | |
| function can_berserk() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| you.status("berserk cooldown") or you.mesmerised() or | |
| too_hungry_to_berserk() or | |
| you.piety_rank() < 1 or | |
| you.god() ~= "Trog" or | |
| you.transform() == "tree" or | |
| you.transform() == "wisp" or | |
| you.transform() == "lich" or | |
| you.status("afraid") or | |
| you.race() == "Mummy" or you.race() == "Ghoul" | |
| or you.race() == "Formicid")) | |
| end | |
| function can_heroism() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or | |
| you.god() ~= "Okawaru" or | |
| you.piety_rank() < 1 or | |
| you.status("heroism") or | |
| cmp() < 2 or | |
| you.under_penance())) | |
| end | |
| function can_recall() | |
| if (you.god() ~= "Yredelemnul" or you.piety_rank() < 2) | |
| and (you.god() ~= "Beogh" or you.piety_rank() < 4) then | |
| return false | |
| end | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or | |
| you.status("recalling") or | |
| cmp() < 2)) | |
| end | |
| function can_recall_ancestor() | |
| if you.god() ~= "Hepliaklqana" then | |
| return false | |
| end | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or | |
| cmp() < 2)) | |
| end | |
| function can_finesse() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or | |
| you.god() ~= "Okawaru" or | |
| you.piety_rank() < 5 or | |
| you.status("finesse") or | |
| cmp() < 5 or | |
| you.under_penance())) | |
| end | |
| function can_slouch() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or | |
| you.god() ~= "Cheibriados" or | |
| you.piety_rank() < 4 or | |
| cmp() < 5)) | |
| end | |
| function can_drain_life() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or | |
| you.god() ~= "Yredelemnul" or | |
| you.piety_rank() < 4 or | |
| cmp() < 6)) | |
| end | |
| function can_hand() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| you.regenerating() or | |
| starving() or you.piety_rank() < 2 or | |
| you.god() ~= "Trog")) | |
| end | |
| function dd_heal_ability() | |
| if you.race() == "Deep Dwarf" and you.base_mp() > 0 | |
| and not (you.berserk() or you.confused() or starving()) then | |
| use_ability("Heal Wounds") | |
| return true | |
| end | |
| return false | |
| end | |
| function can_ely_healing() | |
| return (not (you.berserk() or you.silenced() or starving() | |
| or you.piety_rank() < 4 or you.god() ~= "Elyvilon" | |
| or cmp() < 2)) | |
| end | |
| function can_purification() | |
| return (not (you.berserk() or you.silenced() or starving() | |
| or you.piety_rank() < 3 or you.god() ~= "Elyvilon" | |
| or cmp() < 3)) | |
| end | |
| function can_recite() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or you.status("reciting") or | |
| you.piety_rank() < 1 or you.god() ~= "Zin")) | |
| end | |
| function can_ru_healing() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or you.exhausted() or you.piety_rank() < 3 or | |
| you.god() ~= "Ru")) | |
| end | |
| function can_apocalypse() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or you.exhausted() or you.piety_rank() < 5 or | |
| cmp() < 8 or you.god() ~= "Ru")) | |
| end | |
| function can_grand_finale() | |
| -- using berserk hunger check as a hack for now | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| too_hungry_to_berserk() or you.piety_rank() < 5 or | |
| cmp() < 8 or you.god() ~= "Uskayaw")) | |
| end | |
| function can_bia() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or you.piety_rank() < 4 or | |
| you.god() ~= "Trog")) | |
| end | |
| function can_sgd() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or you.piety_rank() < 5 or | |
| chp() <= 10 or | |
| you.god() ~= "Makhleb")) | |
| end | |
| function can_divine_warrior() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or you.piety_rank() < 5 or | |
| cmp() < 8 or you.god() ~= "the Shining One")) | |
| end | |
| function can_destruction() | |
| return (not (you.berserk() or you.confused() or you.silenced() or | |
| starving() or you.piety_rank() < 4 or | |
| chp() <= 6 or | |
| you.god() ~= "Makhleb")) | |
| end | |
| function can_teleport() | |
| return (not you.berserk() and not you.teleporting() and not you.anchored() | |
| and not you.confused() | |
| and you.transform() ~= "tree" | |
| and you.race() ~= "Formicid") | |
| end | |
| function too_hungry_to_berserk() | |
| if you.race() == "Vampire" then | |
| return (you.hunger_name() ~= "almost alive" | |
| and you.hunger_name() ~= "very full" | |
| and you.hunger_name() ~= "full") | |
| end | |
| return (starving() or | |
| you.hunger_name() == "near starving" or | |
| you.hunger_name() == "very hungry") | |
| end | |
| function player_speed_num() | |
| local num = 3 | |
| if you.god() == "Cheibriados" then | |
| num = 1 | |
| elseif you.race() == "Spriggan" or you.race() == "Centaur" then | |
| num = 4 | |
| elseif you.race() == "Naga" then | |
| num = 2 | |
| end | |
| if you.hasted() or you.berserk() then | |
| num = num + 1 | |
| end | |
| if you.slowed() then | |
| num = num - 1 | |
| end | |
| return num | |
| end | |
| ----------------------------------------- | |
| -- monster functions | |
| function mon_speed_num(m) | |
| local sdesc = m:speed_description() | |
| local num | |
| if sdesc == "extremely fast" then | |
| num = 6 | |
| elseif sdesc == "very fast" then | |
| num = 5 | |
| elseif sdesc == "fast" then | |
| num = 4 | |
| elseif sdesc == "normal" then | |
| num = 3 | |
| elseif sdesc == "slow" then | |
| num = 2 | |
| elseif sdesc == "very slow" then | |
| num = 1 | |
| end | |
| if m:status("fast") then | |
| num = num + 1 | |
| end | |
| if m:status("slow") then | |
| num = num - 1 | |
| end | |
| if m:name():find("spriggan") or m:name() == "the Enchantress" then | |
| num = num + 1 | |
| elseif m:name():find("naga") or m:name() == "Vashnia" then | |
| num = num - 1 | |
| end | |
| return num | |
| end | |
| function is_fast(m) | |
| return (mon_speed_num(m) > player_speed_num()) | |
| end | |
| function is_ranged(m) | |
| local name = m:name() | |
| if name:find("kraken") then | |
| return false | |
| end | |
| if m:has_known_ranged_attack() then | |
| return true | |
| end | |
| if name == "Maurice" or name == "Ijyb" or name == "crimson imp" | |
| or name == "lost soul" then | |
| return true | |
| end | |
| return false | |
| end | |
| function sense_immediate_danger() | |
| local e | |
| for _,e in ipairs(enemy_list) do | |
| if supdist(e.x,e.y) <= 2 then | |
| return true | |
| elseif supdist(e.x,e.y) <= 3 and e.m:reach_range() >= 2 then | |
| return true | |
| elseif is_ranged(e.m) then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function sense_danger(r, no_ignored) | |
| local e | |
| for _,e in ipairs(enemy_list) do | |
| if supdist(e.x,e.y) <= r then | |
| if (not no_ignored) or is_candidate_for_attack(e.x,e.y,no_ignored) then | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function sense_sigmund() | |
| local e | |
| for _,e in ipairs(enemy_list) do | |
| if e.m:name() == "Sigmund" then | |
| sigmund_dx = e.x | |
| sigmund_dy = e.y | |
| return | |
| end | |
| end | |
| end | |
| function initialize_monster_array() | |
| monster_array = {} | |
| local x | |
| for x = -LOS,LOS do | |
| monster_array[x] = {} | |
| end | |
| end | |
| function update_monster_array() | |
| local x,y | |
| enemy_list = {} | |
| --c_persist.mlist = {} | |
| for x = -LOS,LOS do | |
| for y = -LOS,LOS do | |
| monster_array[x][y] = monster.get_monster_at(x, y) | |
| if is_candidate_for_attack(x, y) then | |
| entry = {} | |
| entry.x = x | |
| entry.y = y | |
| entry.m = monster_array[x][y] | |
| table.insert(enemy_list, entry) | |
| --table.insert(c_persist.mlist, entry.m:name()) | |
| end | |
| end | |
| end | |
| end | |
| function check_monsters(r, mlist) | |
| local e | |
| local xl = you.xl() | |
| for _,e in ipairs(enemy_list) do | |
| if supdist(e.x,e.y) <= r then | |
| if not contains_string_in(e.m:name(), {"skeleton", "zombie", | |
| "simulacrum", "spectral"}) then | |
| local name = e.m:name() | |
| local entry = mlist[name] | |
| if type(entry) == "number" then | |
| if xl < entry then | |
| return true | |
| end | |
| elseif type(entry) == "function" then | |
| if entry(e.m) then | |
| return true | |
| end | |
| end | |
| for _, entry in ipairs(mlist["*"]) do | |
| if entry(e.m) then | |
| return true | |
| end | |
| end | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function count_bia(r) | |
| if you.god() ~= "Trog" then | |
| return 0 | |
| end | |
| local x, y | |
| local i = 0 | |
| for x = -r,r do | |
| for y = -r,r do | |
| m = monster_array[x][y] | |
| if m and m:is_safe() and m:is("berserk") | |
| and contains_string_in(m:name(), {"ogre","giant","bear","troll"}) then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_elliptic(r) | |
| if you.god() ~= "Hepliaklqana" then | |
| return 0 | |
| end | |
| local x, y | |
| local i = 0 | |
| for x = -r,r do | |
| for y = -r,r do | |
| m = monster_array[x][y] | |
| if m and m:is_safe() | |
| and contains_string_in(m:name(), {"elliptic"}) then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_sgd(r) | |
| if you.god() ~= "Makhleb" then | |
| return 0 | |
| end | |
| local x, y | |
| local i = 0 | |
| for x = -r,r do | |
| for y = -r,r do | |
| m = monster_array[x][y] | |
| if m and m:is_safe() | |
| and contains_string_in(m:name(), {"Executioner","green death","blizzard demon","balrug","cacodemon"}) then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_divine_warrior(r) | |
| if you.god() ~= "the Shining One" then | |
| return 0 | |
| end | |
| local x, y | |
| local i = 0 | |
| for x = -r,r do | |
| for y = -r,r do | |
| m = monster_array[x][y] | |
| if m and m:is_safe() | |
| and contains_string_in(m:name(), {"angel","daeva"}) then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_hostile_sgd(r) | |
| if you.god() ~= "Makhleb" then | |
| return 0 | |
| end | |
| local x, y | |
| local i = 0 | |
| for x = -r,r do | |
| for y = -r,r do | |
| m = monster_array[x][y] | |
| if m and not m:is_safe() and m:is("summoned") | |
| and contains_string_in(m:name(), {"Executioner","green death","blizzard demon","balrug","cacodemon"}) then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_big_slimes(r) | |
| local x, y | |
| local i = 0 | |
| for x = -r,r do | |
| for y = -r,r do | |
| m = monster_array[x][y] | |
| if m and not m:is_safe() | |
| and contains_string_in(m:name(), {"enormous slime creature","titanic slime creature"}) then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_hellions(r) | |
| local x, y | |
| local i = 0 | |
| for x = -r,r do | |
| for y = -r,r do | |
| m = monster_array[x][y] | |
| if m and not m:is_safe() | |
| and m:name() == "hellion" then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_daevas(r) | |
| local x, y | |
| local i = 0 | |
| for x = -r,r do | |
| for y = -r,r do | |
| m = monster_array[x][y] | |
| if m and not m:is_safe() | |
| and m:name() == "daeva" then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_pan_lords(r) | |
| local e | |
| local i = 0 | |
| for _,e in ipairs(enemy_list) do | |
| if e.m:type() == ENUM_MONS_PANDEMONIUM_LORD then | |
| i = i+1 | |
| end | |
| end | |
| return i | |
| end | |
| -- should only be called for adjacent squares | |
| function monster_in_way(dx,dy) | |
| m = monster_array[dx][dy] | |
| return (m and (m:attitude() <= ATT_NEUTRAL and not lair_step_mode or | |
| m:attitude() > ATT_NEUTRAL and | |
| (m:is_constricted() or m:is_caught() | |
| or m:status("petrified") | |
| or m:status("paralysed") | |
| or m:desc():find("sleeping") | |
| or view.feature_at(0,0) == "deep_water" | |
| or view.feature_at(0,0) == "lava" | |
| or view.feature_at(0,0) == "trap_zot"))) | |
| end | |
| function tabbable_square(x,y) | |
| if view.feature_at(x,y) ~= "unseen" and view.is_safe_square(x,y) then | |
| local m = monster_array[x][y] | |
| if not m or not m:is_firewood() then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function mons_tabbable_square(x,y) | |
| local feat = view.feature_at(x,y) | |
| return (feat ~= "deep_water" and feat ~= "lava" and not is_solid(x,y)) | |
| end | |
| function try_move(dx, dy) | |
| if view.is_safe_square(dx, dy) and not view.withheld(dx, dy) and | |
| not monster_in_way(dx,dy) then | |
| return delta_to_vi(dx, dy) | |
| else | |
| return nil | |
| end | |
| end | |
| function will_tab(cx, cy, ex, ey, square_func) | |
| local dx = ex - cx | |
| local dy = ey - cy | |
| if abs(dx) <= 1 and abs(dy) <= 1 then | |
| return true | |
| end | |
| local function attempt_move(fx, fy) | |
| if fx == 0 and fy == 0 then return end | |
| if supdist(cx+fx,cy+fy) > LOS then return end | |
| if square_func(cx+fx, cy+fy) then | |
| return will_tab(cx+fx, cy+fy, ex, ey, square_func) | |
| end | |
| end | |
| local move = nil | |
| if abs(dx) > abs(dy) then | |
| if abs(dy) == 1 then move = attempt_move(sign(dx), 0) end | |
| if move == nil then move = attempt_move(sign(dx), sign(dy)) end | |
| if move == nil then move = attempt_move(sign(dx), 0) end | |
| if move == nil and abs(dx) > abs(dy)+1 then | |
| move = attempt_move(sign(dx), 1) end | |
| if move == nil and abs(dx) > abs(dy)+1 then | |
| move = attempt_move(sign(dx), -1) end | |
| if move == nil then move = attempt_move(0, sign(dy)) end | |
| elseif abs(dx) == abs(dy) then | |
| move = attempt_move(sign(dx), sign(dy)) | |
| if move == nil then move = attempt_move(sign(dx), 0) end | |
| if move == nil then move = attempt_move(0, sign(dy)) end | |
| else | |
| if abs(dx) == 1 then move = attempt_move(0, sign(dy)) end | |
| if move == nil then move = attempt_move(sign(dx), sign(dy)) end | |
| if move == nil then move = attempt_move(0, sign(dy)) end | |
| if move == nil and abs(dy) > abs(dx)+1 then | |
| move = attempt_move(1, sign(dy)) end | |
| if move == nil and abs(dy) > abs(dx)+1 then | |
| move = attempt_move(-1, sign(dy)) end | |
| if move == nil then move = attempt_move(sign(dx), 0) end | |
| end | |
| if move == nil then return false end | |
| return move | |
| end | |
| function get_monster_info(dx, dy) | |
| m = monster_array[dx][dy] | |
| if not m then return nil end | |
| name = m:name() | |
| info = {} | |
| info.distance = (abs(dx) > abs(dy)) and -abs(dx) or -abs(dy) | |
| if not have_reaching() then | |
| info.attack_type = (-info.distance < 2) and 2 or 0 | |
| else | |
| if -info.distance > 2 then info.attack_type = 0 | |
| elseif -info.distance < 2 then info.attack_type = 2 | |
| elseif you.caught() or you.confused() then info.attack_type = 0 | |
| else info.attack_type = view.can_reach(dx, dy) and 1 or 0 end | |
| end | |
| info.can_attack = (info.attack_type > 0) and 1 or 0 | |
| info.safe = m:is_safe() and -1 or 0 | |
| info.constricting_you = m:is_constricting_you() and 1 or 0 | |
| info.very_stabbable = (m:stabbability() >= 1) and 1 or 0 | |
| -- info.stabbable = m:is(0) and 1 or 0 | |
| info.injury = m:damage_level() | |
| info.threat = m:threat() | |
| info.orc_priest_wizard = (name == "orc priest" or | |
| name == "orc wizard") and 1 or 0 | |
| return info | |
| end | |
| function compare_monster_info(m1, m2) | |
| flag_order = {"can_attack", "safe", "distance", "constricting_you", | |
| "very_stabbable", "injury", "threat", "orc_priest_wizard"} | |
| for i, flag in ipairs(flag_order) do | |
| if m1[flag] > m2[flag] then return true end | |
| if m1[flag] < m2[flag] then return false end | |
| end | |
| return false | |
| end | |
| function is_candidate_for_attack(x, y, no_untabbable) | |
| if supdist(x,y) > LOS then | |
| return false | |
| end | |
| m = monster_array[x][y] | |
| if not m or m:attitude() > ATT_NEUTRAL then | |
| return false | |
| end | |
| if m:name() == "butterfly" | |
| or m:name() == "orb of destruction" then | |
| return false | |
| end | |
| if m:is_firewood() then | |
| if not string.find(m:name(), "ballistomycete") then | |
| return false | |
| end | |
| end | |
| if no_untabbable then | |
| if will_tab(0,0,x,y,tabbable_square) then | |
| remove_ignore(x,y) | |
| else | |
| add_ignore(x,y) | |
| return false | |
| end | |
| end | |
| return true | |
| end | |
| function count_ranged(cx, cy, r) | |
| local e | |
| local i = 0 | |
| for _,e in ipairs(enemy_list) do | |
| local dist = supdist(cx-e.x,cy-e.y) | |
| if dist > 1 and dist <= r then | |
| if dist == 2 and is_fast(e.m) | |
| or (is_ranged(e.m) or dist == 2 and e.m:reach_range() >= 2) | |
| and view.cell_see_cell(cx,cy,e.x,e.y) then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_longranged(cx, cy, r) | |
| local e | |
| local i = 0 | |
| for _,e in ipairs(enemy_list) do | |
| local dist = supdist(cx-e.x,cy-e.y) | |
| if dist >= 4 and dist <= r then | |
| if is_ranged(e.m) and view.cell_see_cell(cx,cy,e.x,e.y) and not | |
| (e.m:desc():find("wandering") or e.m:desc():find("sleeping") or e.m:desc():find("dormant") or e.m:name() == "electric eel" or e.m:name() == "lava snake" or e.m:is_stationary() or e.m:desc():find("stupefied")) then | |
| if will_tab(e.x,e.y,0,0,mons_tabbable_square) then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_shortranged(cx, cy, r) | |
| local e | |
| local i = 0 | |
| for _,e in ipairs(enemy_list) do | |
| local dist = supdist(cx-e.x,cy-e.y) | |
| if dist <= r and is_ranged(e.m) then | |
| i = i+1 | |
| end | |
| end | |
| return i | |
| end | |
| function count_unalert(cx, cy, r) | |
| local e | |
| local i = 0 | |
| for _,e in ipairs(enemy_list) do | |
| local dist = supdist(cx-e.x,cy-e.y) | |
| if dist <= r and dist > 1 and view.cell_see_cell(cx,cy,e.x,e.y) then | |
| if e.m:desc():find("wandering") and not e.m:desc():find("mushroom") | |
| or e.m:desc():find("sleeping") or e.m:desc():find("dormant") then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function estimate_slouch_damage() | |
| local e | |
| local count = 0 | |
| local s,v | |
| for _,e in ipairs(enemy_list) do | |
| if you.see_cell_no_trans(e.x,e.y) then | |
| s = mon_speed_num(e.m) | |
| v = 0 | |
| if s >= 6 then | |
| v = 3 | |
| elseif s == 5 then | |
| v = 2.5 | |
| elseif s == 4 then | |
| v = 1.5 | |
| elseif s == 3 then | |
| v = 1 | |
| end | |
| if e.m:name() == "orb of fire" then | |
| v = v + 1 | |
| elseif v > 0 and e.m:threat() <= 1 then | |
| v = 0.5 | |
| end | |
| count = count + v | |
| end | |
| end | |
| return count | |
| end | |
| function count_drainable() | |
| local e | |
| local count = 0 | |
| for _,e in ipairs(enemy_list) do | |
| if you.see_cell_no_trans(e.x,e.y) and e.m:res_draining() == 0 then | |
| count = count + 1 | |
| end | |
| end | |
| return count | |
| end | |
| function count_nearby(cx, cy, r) | |
| local x, y | |
| local i = 0 | |
| for x = -r,r do | |
| for y = -r,r do | |
| if supdist(x,y) > 0 and is_candidate_for_attack(cx+x, cy+y) then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function count_slow_nearby(cx, cy, r) | |
| local x, y | |
| local i = 0 | |
| for x = -r,r do | |
| for y = -r,r do | |
| if supdist(x,y) > 0 and is_candidate_for_attack(cx+x, cy+y) | |
| then | |
| m = monster_array[cx+x][cy+y] | |
| if mon_speed_num(m) < player_speed_num() | |
| and not is_ranged(m) and m:reach_range() < 2 then | |
| i = i+1 | |
| end | |
| end | |
| end | |
| end | |
| return i | |
| end | |
| function distance_to_enemy(cx, cy) | |
| local dist = 10 | |
| local e | |
| for _,e in ipairs(enemy_list) do | |
| if supdist(cx-e.x,cy-e.y) < dist then | |
| dist = supdist(cx-e.x,cy-e.y) | |
| end | |
| end | |
| return dist | |
| end | |
| function distance_to_tabbable_enemy(cx, cy) | |
| local dist = 10 | |
| local e | |
| for _,e in ipairs(enemy_list) do | |
| if supdist(cx-e.x,cy-e.y) < dist then | |
| if will_tab(e.x,e.y,0,0,mons_tabbable_square) then | |
| dist = supdist(cx-e.x,cy-e.y) | |
| end | |
| end | |
| end | |
| return dist | |
| end | |
| ----------------------------------------- | |
| -- item functions | |
| function inventory() | |
| return iter.invent_iterator:new(items.inventory()) | |
| end | |
| function at_feet() | |
| return iter.invent_iterator:new(you.floor_items()) | |
| end | |
| function free_inventory_slots() | |
| local slots = 52 | |
| for _ in inventory() do | |
| slots = slots - 1 | |
| end | |
| return slots | |
| end | |
| function slot(x) | |
| if type(x) == "userdata" then | |
| return x.slot | |
| elseif type(x) == "string" then | |
| return items.letter_to_index(x) | |
| else | |
| return x | |
| end | |
| end | |
| function letter(x) | |
| if type(x) == "userdata" then | |
| return items.index_to_letter(x.slot) | |
| elseif type(x) == "number" then | |
| return items.index_to_letter(x) | |
| else | |
| return x | |
| end | |
| end | |
| function item(x) | |
| if type(x) == "number" then | |
| return items.inslot(x) | |
| elseif type(x) == "string" then | |
| return items.inslot(items.letter_to_index(x)) | |
| else | |
| return x | |
| end | |
| end | |
| function ring_list() | |
| rings = {} | |
| if you.race() ~= "Octopode" then | |
| if items.equipped_at("Left Ring") then | |
| table.insert(rings, items.equipped_at("Left Ring")) | |
| end | |
| if items.equipped_at("Right Ring") then | |
| table.insert(rings, items.equipped_at("Right Ring")) | |
| end | |
| return rings | |
| end | |
| for it in inventory() do | |
| if it.equipped and equip_slot(it) == "Ring" then | |
| table.insert(rings, it) | |
| end | |
| end | |
| return rings | |
| end | |
| function empty_ring_slots() | |
| return max_rings() - table.getn(ring_list()) | |
| end | |
| function have_two_hander() | |
| for it in inventory() do | |
| if it.class(true) == "weapon" and it.weap_skill == wskill() | |
| and it.hands == 2 then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function find_item(cls,name) | |
| if cls == "wand" then return find_wand(name) end | |
| for it in inventory() do | |
| if it.class(true) == cls and it.name():find(name) then | |
| return items.index_to_letter(it.slot) | |
| end | |
| end | |
| end | |
| function have_item(cls,name) | |
| for it in inventory() do | |
| if it.class(true) == cls and it.name():find(name) then | |
| return true | |
| end | |
| end | |
| end | |
| function find_wand(name) | |
| for it in inventory() do | |
| if it.class(true) == "wand" and it.name():find(name) and | |
| not it.name():find("empty") and (it.plus == nil or it.plus > 0) then | |
| return items.index_to_letter(it.slot) | |
| end | |
| end | |
| end | |
| function count_item(cls,name) | |
| f = find_item(cls, name) | |
| if f then | |
| return item(f).quantity | |
| end | |
| return 0 | |
| end | |
| function have_reaching() | |
| local wp = items.equipped_at("weapon") | |
| return wp and wp.reach_range == 2 and not wp.is_melded | |
| end | |
| function body_size() | |
| if you.race() == "Kobold" or you.race() == "Halfling" then | |
| return -1 | |
| elseif you.race() == "Spriggan" or you.race() == "Felid" then | |
| return -2 | |
| elseif you.race() == "Troll" or you.race() == "Ogre" | |
| or you.race() == "Naga" or you.race() == "Centaur" then | |
| return 1 | |
| else | |
| return 0 | |
| end | |
| end | |
| function target_shield_skill() | |
| local shield = items.equipped_at("Shield") | |
| if not shield then | |
| return 0 | |
| end | |
| if shield.encumbrance == 0 and not want_buckler() | |
| or shield.encumbrance ~= 0 and not want_shield() then | |
| return 0 | |
| end | |
| enc = shield.encumbrance > 0 and shield.encumbrance or 0.8 | |
| local size_factor = 5 - 2 * body_size() | |
| if you.race() == "Formicid" then | |
| size_factor = size_factor - 2 | |
| end | |
| return enc * size_factor | |
| end | |
| function at_target_shield_skill() | |
| return (you.base_skill("Shields") >= min(27, target_shield_skill() + (you.god() == "Ru" and 1 or 0))) | |
| end | |
| function min_delay_skill() | |
| weap = items.equipped_at("Weapon") | |
| if not weap then | |
| return 27 | |
| end | |
| if weap.weap_skill ~= wskill() then | |
| return last_min_delay_skill | |
| end | |
| if weap.weap_skill == "Short Blades" and weap.delay == 12 then | |
| last_min_delay_skill = 14 | |
| return 14 | |
| end | |
| local mindelay = math.floor(weap.delay / 2) | |
| if mindelay > 7 then | |
| mindelay = 7 | |
| end | |
| last_min_delay_skill = 2 * (weap.delay - mindelay) | |
| return last_min_delay_skill | |
| end | |
| function at_min_delay() | |
| return (you.base_skill(wskill()) >= min(27, min_delay_skill() + (you.god() == "Ru" and 1 or 0))) | |
| end | |
| function cleaving() | |
| weap = items.equipped_at("Weapon") | |
| if weap and weap.weap_skill == "Axes" then | |
| return true | |
| end | |
| return false | |
| end | |
| function armour_ac() | |
| arm = items.equipped_at("Armour") | |
| if arm then | |
| return arm.ac | |
| else | |
| return 0 | |
| end | |
| end | |
| function base_ac() | |
| local total = 0 | |
| for _,slotname in pairs(good_slots) do | |
| if slotname ~= "Shield" then | |
| it = items.equipped_at(slotname) | |
| if it then | |
| total = total + it.ac | |
| end | |
| end | |
| end | |
| return total | |
| end | |
| function armour_evp() | |
| arm = items.equipped_at("Armour") | |
| if arm then | |
| return arm.encumbrance | |
| else | |
| return 0 | |
| end | |
| end | |
| function on_corpses() | |
| for it in at_feet() do | |
| if string.find(it.name(), "corpse") then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function on_bottleable_corpses() | |
| for it in at_feet() do | |
| if string.find(it.name(), "corpse") | |
| and food.bottleable(it) then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function on_dangerous_corpse() | |
| for it in at_feet() do | |
| if string.find(it.name(), "corpse") then | |
| return food.dangerous(it) | |
| end | |
| end | |
| return false | |
| end | |
| function on_edible_corpse() | |
| for it in at_feet() do | |
| if it.name():find("corpse") and | |
| (not it.name():find("noeat")) and (you.god() ~= "Beogh" or not it.name():find("orc ")) then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function bad_food(it) | |
| return food.dangerous(it) | |
| end | |
| function can_swap(equip_slot) | |
| local it = items.equipped_at(equip_slot) | |
| if it and it.cursed then | |
| return false | |
| end | |
| if it and it.ego() == "flying" and | |
| (view.feature_at(0,0) == "deep_water" or | |
| view.feature_at(0,0) == "lava") then | |
| return false | |
| end | |
| return true | |
| end | |
| -- plural form, e.g. "Scrolls" | |
| -- or invoke with item_class="name_callback" and provide callback for name | |
| function see_item(item_class, r, name_callback) | |
| for x = -r,r do | |
| for y = -r,r do | |
| -- crawl.mpr("(" .. x .. ", " .. y .. "): " .. view.feature_at(x,y) .."\r") | |
| local is = items.get_items_at(x, y) | |
| if (is ~= nil) and (#is > 0) and (you.see_cell(x,y)) then | |
| for ind,i in pairs(is) do | |
| local iname = i.name() | |
| if (i:class(true) == item_class) or ((item_class == "name_callback") and name_callback(iname)) then | |
| return true | |
| end | |
| end | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| -- Matches all unindentified books | |
| function is_spellbook(book_name) | |
| return (book_name ~= nil) and (book_name:find("book") ~= nil) | |
| end | |
| function see_spellbooks_to_burn() | |
| return see_item("name_callback", LOS, is_spellbook) | |
| and not see_item("name_callback", 0, is_spellbook) | |
| end | |
| ----------------------------------------- | |
| -- "plans" - functions that take actions, and logic to determine which actions | |
| -- to take. | |
| -- Every function that might take an action should return as follows: | |
| -- true if tried to do something | |
| -- false if didn't do anything | |
| -- nil if should be rerun (currently only used by cascades, be careful | |
| -- of loops... this is poorly tested) | |
| function get_target() | |
| local e, bestx, besty, best_info, new_info | |
| bestx = 0 | |
| besty = 0 | |
| best_info = nil | |
| for _,e in ipairs(enemy_list) do | |
| if not util.contains(failed_move, 20*e.x+e.y) then | |
| if is_candidate_for_attack(e.x, e.y, true) then | |
| new_info = get_monster_info(e.x, e.y) | |
| if (not best_info) or | |
| compare_monster_info(new_info, best_info) then | |
| bestx = e.x | |
| besty = e.y | |
| best_info = new_info | |
| end | |
| end | |
| end | |
| end | |
| return bestx, besty, best_info | |
| end | |
| function should_rest() | |
| if you.confused() or you.berserk() or transformed() then | |
| return true | |
| end | |
| if dangerous_to_rest() then | |
| return false | |
| end | |
| if you.turns() < hiding_turn_count + 10 then | |
| if SPAM then | |
| say("Waiting for ranged monster.") | |
| end | |
| return true | |
| end | |
| return (reason_to_rest(99.9) or you.status("barbed spikes") | |
| or you.god() == "Makhleb" and you.turns() <= sgd_timer + 100 | |
| or should_ally_rest()) | |
| end | |
| -- check statuses to see whether there is something to rest off, | |
| -- does not include some things in should_rest() because they are | |
| -- not clearly good to wait out with monsters around | |
| function reason_to_rest(percentage) | |
| if not no_spells and starting_spell() then | |
| local mp,mmp = you.mp() | |
| if mp < mmp and (you.race() ~= "Deep Dwarf" | |
| or player_resist("Spirit") <= 0) then | |
| return true | |
| end | |
| end | |
| if you.god() == "Elyvilon" and you.piety_rank() >= 4 then | |
| local mp,mmp = you.mp() | |
| if mp < mmp and mp < 10 and (you.race() ~= "Deep Dwarf" | |
| or player_resist("Spirit") <= 0) then | |
| return true | |
| end | |
| end | |
| return (you.confused() or transformed() or | |
| hp_is_low(percentage) and | |
| (you.race() ~= "Deep Dwarf" and you.hunger_name() ~= "bloodless" | |
| or you.regenerating()) | |
| and (you.god() ~= "the Shining One" or hp_is_low(75) or | |
| count_divine_warrior(2) == 0) | |
| or you.slowed() or you.exhausted() or you.teleporting() | |
| or you.status("berserk cooldown") or you.status("marked") | |
| or you.silencing() or you.corrosion() > 0) | |
| end | |
| -- are we significantly stronger than usual thanks to a buff that we used? | |
| function buffed() | |
| if hp_is_low(50) or transformed() or you.corrosion() >= 2 then | |
| return false | |
| end | |
| if you.god() == "Okawaru" and | |
| (you.status("heroism") or you.status("finesse")) then | |
| return true | |
| end | |
| if you.extra_resistant() then | |
| return true | |
| end | |
| return false | |
| end | |
| function should_ally_rest() | |
| if you.god() ~= "Yredelemnul" and you.god() ~= "Beogh" then | |
| return false | |
| end | |
| if dangerous_to_rest() then | |
| return false | |
| end | |
| local x,y | |
| for x = -3,3 do | |
| for y = -3,3 do | |
| m = monster_array[x][y] | |
| if m and m:attitude() == ATT_FRIENDLY and m:damage_level() > 0 then | |
| if SPAM then | |
| say("Waiting for " .. m:name() .. " to heal.") | |
| end | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function want_permafood() | |
| if you.race() == "Mummy" or you.transform() == "lich" then | |
| return false | |
| end | |
| if you.race() == "Vampire" then | |
| return (you.hunger_name() ~= "almost alive" | |
| and you.hunger_name() ~= "very full" | |
| and you.hunger_name() ~= "full") | |
| end | |
| if you.race() == "Spriggan" then | |
| return (you.hunger_name() ~= "completely stuffed" | |
| and you.hunger_name() ~= "very full") | |
| end | |
| return (you.hunger_name() == "near starving" | |
| or you.hunger_name() == "very hungry" | |
| and (where == "Snake:4" or where == "Spider:4" | |
| or where == "Swamp:4" or where == "Shoals:4" | |
| or you.xl() >= 18 or intrinsic_gourmand()) | |
| or starving()) | |
| end | |
| function want_chunk() | |
| if you.race() == "Spriggan" or you.race() == "Mummy" | |
| or you.race() == "Vampire" or you.transform() == "lich" then | |
| return false | |
| end | |
| if you.race() == "Ghoul" and you.rot() > 0 then | |
| return true | |
| end | |
| if intrinsic_gourmand() and (you.hunger_name() == "not hungry" | |
| or you.hunger_name() == "full" | |
| or you.hunger_name() == "very full") then | |
| return true | |
| end | |
| return you.hunger_name() == "hungry" or | |
| you.hunger_name() == "very hungry" or want_permafood() | |
| end | |
| function rest() | |
| magic("s") | |
| next_delay = 5 | |
| end | |
| function easy_rest() | |
| magic("5") | |
| end | |
| function attack() | |
| local success = false | |
| failed_move = { } | |
| while not success do | |
| bestx, besty, best_info = get_target() | |
| if best_info == nil then | |
| return false | |
| end | |
| success = make_attack(bestx, besty, best_info) | |
| end | |
| return true | |
| end | |
| function chop() | |
| magic("cYq") | |
| end | |
| function berserk() | |
| use_ability("Berserk") | |
| end | |
| function heroism() | |
| use_ability("Heroism") | |
| end | |
| function recall() | |
| if you.god() == "Yredelemnul" then | |
| use_ability("Recall Undead Slaves", "", true) | |
| else | |
| use_ability("Recall Orcish Followers", "", true) | |
| end | |
| end | |
| function recall_ancestor() | |
| use_ability("Recall Ancestor", "", true) | |
| end | |
| function finesse() | |
| use_ability("Finesse") | |
| end | |
| function slouch() | |
| use_ability("Slouch") | |
| end | |
| function drain_life() | |
| use_ability("Drain Life") | |
| end | |
| function hand() | |
| use_ability("Trog's Hand") | |
| end | |
| function ru_healing() | |
| use_ability("Draw Out Power") | |
| end | |
| function ely_healing() | |
| use_ability("Greater Healing") | |
| end | |
| function purification() | |
| use_ability("Purification") | |
| end | |
| function recite() | |
| use_ability("Recite", "", true) | |
| end | |
| function bia() | |
| use_ability("Brothers in Arms") | |
| end | |
| function sgd() | |
| use_ability("Greater Servant of Makhleb") | |
| end | |
| function divine_warrior() | |
| use_ability("Summon Divine Warrior") | |
| end | |
| function apocalypse() | |
| use_ability("Apocalypse") | |
| end | |
| -- Will fail if the book is in a non-fire cloud already. | |
| function burn_spellbooks() | |
| use_ability("Burn Spellbooks") | |
| end | |
| function plan_bia() | |
| if can_bia() and want_to_bia() then | |
| bia() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_sgd() | |
| if can_sgd() and want_to_sgd() then | |
| sgd() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_divine_warrior() | |
| if can_divine_warrior() and want_to_divine_warrior() then | |
| divine_warrior() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_orbrun_divine_warrior() | |
| if can_divine_warrior() and want_to_orbrun_divine_warrior() then | |
| divine_warrior() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_recite() | |
| if can_recite() and danger and not (immediate_danger and hp_is_low(33)) then | |
| recite() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_apocalypse() | |
| if can_apocalypse() and want_to_apocalypse() then | |
| apocalypse() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_grand_finale() | |
| if not danger or not can_grand_finale() then | |
| return false | |
| end | |
| local invo = you.skill("Invocations") | |
| if invo < 10 or you.piety_rank() < 6 and invo < 15 then | |
| -- fail rate potentially too high, need to add ability failure rate lua | |
| return false | |
| end | |
| local e, bestx, besty, best_info, new_info | |
| best_info = nil | |
| for _,e in ipairs(enemy_list) do | |
| if is_traversable(e.x,e.y) and you.see_cell_no_trans(e.x,e.y) | |
| and not cloud_is_dangerous(view.cloud_at(e.x,e.y)) then | |
| new_info = get_monster_info(e.x, e.y) | |
| if new_info.safe == 0 and | |
| ((not best_info) or new_info.threat > best_info.threat | |
| or new_info.threat == best_info.threat and | |
| (new_info.injury < best_info.injury | |
| or new_info.injury == best_info.injury and | |
| new_info.distance < best_info.distance)) then | |
| best_info = new_info | |
| bestx = e.x | |
| besty = e.y | |
| end | |
| end | |
| end | |
| if best_info then | |
| use_ability("Grand Finale", "r" .. vector_move(bestx,besty) .. "\rY") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_hydra_destruction() | |
| if not can_destruction() then | |
| return false | |
| end | |
| if you.skill("Invocations") < 8 then | |
| return false | |
| end | |
| if count_sgd(4) > 0 or (hydra_weapon_status(items.equipped_at("Weapon")) > -1) or you.xl() >= 20 then | |
| return false | |
| end | |
| local x,y | |
| for x = -5,5 do | |
| for y = -5,5 do | |
| m = monster_array[x][y] | |
| if m and string.find(m:desc(), "hydra") then | |
| say("INVOKING MAJOR DESTRUCTION") | |
| for letter, abil in pairs(you.ability_table()) do | |
| if abil == "Major Destruction" then | |
| magic("a" .. letter .. "r" .. vector_move(x, y) .. "\r") | |
| return true | |
| end | |
| end | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_resistance() | |
| if not you.extra_resistant() and not you.teleporting() | |
| and want_resistance() then | |
| return drink_by_name("resistance") | |
| end | |
| return false | |
| end | |
| function plan_hand() | |
| if can_hand() and want_to_hand() and not you.teleporting() then | |
| hand() | |
| return true | |
| end | |
| return false | |
| end | |
| function dd_prefer_hand() | |
| if you.god() ~= "Trog" then | |
| return false | |
| end | |
| return (you.piety_rank() == 6 or you.base_mp() < 3) | |
| end | |
| function dd_prefer_ru_healing() | |
| if you.god() ~= "Ru" or you.piety_rank() < 3 then | |
| return false | |
| end | |
| if you.status("very heavily drained") then | |
| return false | |
| end | |
| if you.base_mp() < 3 then | |
| return true | |
| end | |
| if you.status("heavily drained") then | |
| return false | |
| end | |
| if not you.status("drained") then | |
| return true | |
| end | |
| local hp,mhp = you.hp() | |
| return (mhp - hp >= 45) | |
| end | |
| function prefer_ru_healing() | |
| if you.race() == "Deep Dwarf" then | |
| return dd_prefer_ru_healing() | |
| end | |
| return (not (you.status("drained") or you.status("heavily drained") or you.status("very heavily drained"))) | |
| end | |
| function prefer_ely_healing() | |
| if you.god() ~= "Elyvilon" or you.piety_rank() < 4 then | |
| return false | |
| end | |
| if you.hunger_name() == "near starving" and | |
| not (you.race() == "Deep Dwarf" and you.base_mp() < 10) then | |
| return false | |
| end | |
| return true | |
| end | |
| function plan_dd_hand_for_healing() | |
| if you.race() ~= "Deep Dwarf" then | |
| return false | |
| end | |
| local hp,mhp = you.hp() | |
| if mhp - hp >= 30 and can_hand() and | |
| dd_prefer_hand() then | |
| hand() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_abyss_hand() | |
| local hp,mhp = you.hp() | |
| if mhp - hp >= 30 and can_hand() then | |
| hand() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_orbrun_hand() | |
| local hp,mhp = you.hp() | |
| if mhp - hp >= 30 and can_hand() then | |
| hand() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_cure_bad_poison() | |
| if not (danger or you.race() == "Deep Dwarf") then | |
| return false | |
| end | |
| if you.poison_survival() <= chp() - 60 then | |
| if drink_by_name("curing") then | |
| say("(to cure bad poison)") | |
| return true | |
| end | |
| if can_purification() then | |
| purification() | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_cancellation() | |
| if not danger or not can_drink() or you.teleporting() then | |
| return false | |
| end | |
| if you.petrifying() or you.corrosion() >= 4 | |
| or you.corrosion() >= 3 and hp_is_low(70) then | |
| if drink_by_name("cancellation") then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_blinking() | |
| if not where:find("Zig") or not danger or not can_read() | |
| or you.mutation("blurry vision") > 0 then | |
| return false | |
| end | |
| local para_danger = false | |
| for _,e in ipairs(enemy_list) do | |
| if e.m:name() == "floating eye" or e.m:name() == "starcursed mass" then | |
| para_danger = true | |
| end | |
| end | |
| if not para_danger then | |
| return false | |
| end | |
| if count_item("scroll", "of blinking") == 0 then | |
| return false | |
| end | |
| local x,y,dx,dy,m | |
| local cur_count = 0 | |
| local best_count = 0 | |
| local count | |
| local best_x,best_y | |
| for x = -1,1 do | |
| for y = -1,1 do | |
| m = monster_array[x][y] | |
| if m and m:name() == "floating eye" then | |
| cur_count = cur_count + 3 | |
| elseif m and m:name() == "starcursed mass" then | |
| cur_count = cur_count + 1 | |
| end | |
| end | |
| end | |
| if cur_count >= 2 then | |
| return false | |
| end | |
| for x = -LOS,LOS do | |
| for y = -LOS,LOS do | |
| if is_traversable(x,y) and not is_solid(x,y) | |
| and monster_array[x][y] == nil and view.is_safe_square(x,y) | |
| and not view.withheld(x,y) and you.see_cell_no_trans(x,y) then | |
| count = 0 | |
| for dx = -1,1 do | |
| for dy = -1,1 do | |
| if abs(x+dx) <= LOS and abs(y+dy) <= LOS then | |
| m = monster_array[x+dx][y+dy] | |
| if m and m:name() == "floating eye" then | |
| count = count + 3 | |
| elseif m and m:name() == "starcursed mass" then | |
| count = count + 1 | |
| end | |
| end | |
| end | |
| end | |
| if count > best_count then | |
| best_count = count | |
| best_x = x | |
| best_y = y | |
| end | |
| end | |
| end | |
| end | |
| if best_count >= cur_count + 2 then | |
| local c = find_item("scroll", "blinking") | |
| return read2(letter(c), vector_move(best_x,best_y) .. ".") | |
| end | |
| return false | |
| end | |
| function heal_general() | |
| if can_ru_healing() and prefer_ru_healing() then | |
| ru_healing() | |
| return true | |
| end | |
| if can_ely_healing() and prefer_ely_healing() then | |
| ely_healing() | |
| return true | |
| end | |
| if heal_wounds() then | |
| return true | |
| end | |
| if can_ru_healing() and you.race() ~= "Deep Dwarf" then | |
| ru_healing() | |
| return true | |
| end | |
| if can_ely_healing() then | |
| ely_healing() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_heal_wounds() | |
| if want_to_heal_wounds() then | |
| return heal_general() | |
| end | |
| return false | |
| end | |
| function plan_orbrun_heal_wounds() | |
| if want_to_orbrun_heal_wounds() then | |
| return heal_general() | |
| end | |
| return false | |
| end | |
| function plan_orbrun_haste() | |
| if want_to_orbrun_buff() and not you.status("finesse") then | |
| return haste() | |
| end | |
| return false | |
| end | |
| function plan_orbrun_might() | |
| if want_to_orbrun_buff() then | |
| return might() | |
| end | |
| return false | |
| end | |
| function plan_haste() | |
| if want_to_serious_buff() then | |
| return haste() | |
| end | |
| return false | |
| end | |
| function plan_might() | |
| if want_to_serious_buff() then | |
| return might() | |
| end | |
| return false | |
| end | |
| function plan_orbrun_heroism() | |
| if can_heroism() and want_to_orbrun_buff() then | |
| return heroism() | |
| end | |
| return false | |
| end | |
| function plan_orbrun_finesse() | |
| if can_finesse() and want_to_orbrun_buff() then | |
| return finesse() | |
| end | |
| return false | |
| end | |
| function heal_wounds() | |
| if you.race() == "Deep Dwarf" and you.xl() < 15 | |
| and not (immediate_danger and (hp_is_low(25) or chp() <= 20)) | |
| and dd_heal_ability() then | |
| dd_hw_meter = dd_hw_meter + 100 | |
| return true | |
| end | |
| if you.mutation("no potion heal") < 3 | |
| and drink_by_name("heal wounds") then | |
| dd_hw_meter = dd_hw_meter + 100 | |
| return true | |
| end | |
| if you.race() == "Deep Dwarf" and dd_heal_ability() then | |
| dd_hw_meter = dd_hw_meter + 100 | |
| return true | |
| end | |
| return false | |
| end | |
| function haste() | |
| if you.hasted() or you.race() == "Formicid" or you.god() == "Cheibriados" then | |
| return false | |
| end | |
| return drink_by_name("haste") | |
| end | |
| function might() | |
| if you.mighty() then | |
| return false | |
| end | |
| return drink_by_name("might") | |
| end | |
| function cloud_is_dangerous(cloud) | |
| if cloud == "flame" or cloud == "fire" then | |
| return (you.res_fire() < 1) | |
| elseif cloud == "noxious fumes" then | |
| return (not meph_immune()) | |
| elseif cloud == "freezing vapour" then | |
| return (you.res_cold() < 1) | |
| elseif cloud == "poison gas" then | |
| return (you.res_poison() < 1) | |
| elseif cloud == "calcifying dust" then | |
| return (you.race() ~= "Gargoyle") | |
| elseif cloud == "foul pestilence" then | |
| return (not miasma_immune()) | |
| elseif cloud == "seething chaos" or cloud == "mutagenic fog" then | |
| return true | |
| end | |
| return false | |
| end | |
| function assess_square(x,y) | |
| a = {} | |
| -- distance to current square | |
| a.supdist = supdist(x,y) | |
| -- is current square near a BiA/SGD? | |
| if a.supdist == 0 then | |
| a.near_ally = (count_bia(3) + count_sgd(3) + count_divine_warrior(3) > 0) | |
| end | |
| -- can we move there? | |
| a.can_move = (a.supdist == 0) or not view.withheld(x,y) | |
| and not monster_in_way(x,y) | |
| and is_traversable(x,y) | |
| and not is_solid(x,y) | |
| if not a.can_move then | |
| return a | |
| end | |
| cloud = view.cloud_at(x,y) | |
| -- nonadjacent monsters who might be able to attack you (ranged/reaching/fast) | |
| a.ranged = count_ranged(x,y,LOS) | |
| -- alert ranged monsters at distance >= 4 | |
| a.longranged = count_longranged(x,y,LOS) | |
| -- adjacent monsters | |
| a.adjacent = count_nearby(x,y,1) | |
| -- wandering or sleeping monsters | |
| a.unalert = count_unalert(x,y,LOS) | |
| -- ranged monsters at distance <= 5, not necessarily visible | |
| a.shortranged = count_shortranged(x,y,5) | |
| -- corners - avoid these if possible | |
| a.cornerish = is_cornerish(x,y) | |
| if have_reaching() then | |
| -- slow adjacent nonranged monsters, for kiting | |
| a.slow_adjacent = count_slow_nearby(x,y,1) | |
| end | |
| -- distance to nearest enemy | |
| a.enemy_distance = distance_to_enemy(x,y) | |
| -- will we fumble if we try to attack from this square? | |
| a.fumble = (not you.flying() and view.feature_at(x,y) == "shallow_water" | |
| and intrinsic_fumble() and not (you.god() == "Beogh" and you.piety_rank() >= 5)) | |
| -- will we be slow if we move into this square? | |
| a.slow = (not you.flying() and view.feature_at(x,y) == "shallow_water" | |
| and you.race() ~= "Merfolk" and you.race() ~= "Octopode" | |
| and you.race() ~= "Barachi" | |
| and not (you.god() == "Beogh" and you.piety_rank() >= 5)) | |
| -- is the square safe to step in? (checks traps+clouds) | |
| a.safe = view.is_safe_square(x,y) | |
| cloud = view.cloud_at(x,y) | |
| -- would we want to move out of a cloud? note that we don't worry about | |
| -- weak clouds if monsters are around | |
| a.cloud_safe = (cloud == nil) or a.safe | |
| or danger and not cloud_is_dangerous(cloud) | |
| -- equal to 10000 if the move is not closer to any stair in good_stair_list, | |
| -- otherwise equal to the (min) dist to such a stair | |
| a.stair_closer = stair_improvement(x,y) | |
| return a | |
| end | |
| -- returns a string explaining why moving a1->a2 is preferable to not moving | |
| -- possibilities are: | |
| -- cloud - stepping out of harmful cloud | |
| -- water - stepping out of shallow water when it would cause fumbling | |
| -- reaching - kiting slower monsters with reaching | |
| -- hiding - moving out of sight of alert ranged enemies at distance >= 4 | |
| -- stealth - moving out of sight of sleeping or wandering monsters | |
| -- outnumbered - stepping away from a square adjacent to multiple monsters | |
| -- (when not cleaving) | |
| -- fleeing - moving towards stairs | |
| function step_reason(a1,a2) | |
| if not (a2.can_move and a2.safe and a2.supdist > 0) then | |
| return false | |
| elseif (a2.fumble or a2.slow) and a1.cloud_safe then | |
| return false | |
| elseif (not a1.near_ally) | |
| and a2.stair_closer < 10000 and a1.stair_closer > 0 | |
| and a1.enemy_distance < 10 | |
| and reason_to_rest(90) and not buffed() | |
| and (no_spells or starting_spell() ~= "Summon Small Mammal") then | |
| return "fleeing" | |
| elseif (not a1.near_ally) and a2.ranged == 0 and a2.adjacent == 0 and a1.longranged > 0 then | |
| return "hiding" | |
| elseif (not a1.near_ally) and a2.ranged == 0 and a2.adjacent == 0 and a2.unalert < a1.unalert then | |
| return "stealth" | |
| elseif not a1.cloud_safe then | |
| return "cloud" | |
| elseif a1.fumble then | |
| if a2.ranged > a1.ranged and a2.enemy_distance > a1.enemy_distance then | |
| return false | |
| else | |
| return "water" | |
| end | |
| elseif have_reaching() and a1.slow_adjacent > 0 and a2.adjacent == 0 | |
| and a2.ranged == 0 then | |
| return "reaching" | |
| elseif cleaving() then | |
| return false | |
| elseif a1.adjacent == 1 then | |
| return false | |
| elseif a2.adjacent + a2.ranged <= a1.adjacent + a1.ranged - 2 then | |
| return "outnumbered" | |
| else | |
| return false | |
| end | |
| end | |
| -- determines whether moving a0->a2 is an improvement over a0->a1 | |
| -- assumes that these two moves have already been determined to be better | |
| -- than not moving, with given reasons | |
| function step_improvement(bestreason,reason,a1,a2) | |
| if reason == "fleeing" and bestreason ~= "fleeing" then | |
| return true | |
| elseif bestreason == "fleeing" and reason ~= "fleeing" then | |
| return false | |
| elseif reason == "water" and bestreason == "water" | |
| and a2.enemy_distance < a1.enemy_distance then | |
| return true | |
| elseif reason == "water" and bestreason == "water" | |
| and a2.enemy_distance > a1.enemy_distance then | |
| return false | |
| elseif a2.adjacent+a2.ranged < a1.adjacent+a1.ranged then | |
| return true | |
| elseif a2.adjacent+a2.ranged > a1.adjacent+a1.ranged then | |
| return false | |
| elseif cleaving() and a2.ranged < a1.ranged then | |
| return true | |
| elseif cleaving() and a2.ranged > a1.ranged then | |
| return false | |
| elseif a2.adjacent+a2.ranged == 0 and a2.unalert < a1.unalert then | |
| return true | |
| elseif a2.adjacent+a2.ranged == 0 and a2.unalert > a1.unalert then | |
| return false | |
| elseif reason == "fleeing" and a2.stair_closer < a1.stair_closer then | |
| return true | |
| elseif reason == "fleeing" and a2.stair_closer > a1.stair_closer then | |
| return false | |
| elseif a2.enemy_distance < a1.enemy_distance then | |
| return true | |
| elseif a2.enemy_distance > a1.enemy_distance then | |
| return false | |
| elseif a2.stair_closer < a1.stair_closer then | |
| return true | |
| elseif a2.stair_closer > a2.stair_closer then | |
| return false | |
| elseif a1.cornerish and not a2.cornerish then | |
| return true | |
| else | |
| return false | |
| end | |
| end | |
| function choose_tactical_step() | |
| tactical_step = nil | |
| tactical_reason = "none" | |
| if you.confused() or you.berserk() or you.constricted() | |
| or you.transform() == "tree" or you.transform() == "fungus" | |
| or where:find("Slime") or you.status("barbed spikes") then | |
| return | |
| end | |
| local a0 = assess_square(0,0) | |
| if a0.cloud_safe and not (a0.fumble and sense_danger(3)) | |
| and (not have_reaching() or a0.slow_adjacent == 0) | |
| and (a0.adjacent <= 1 or cleaving()) | |
| and (a0.near_ally or a0.enemy_distance == 10) then | |
| return | |
| end | |
| local bestx,besty,bestreason | |
| local besta = nil | |
| local x,y | |
| local a | |
| local reason | |
| for x = -1,1 do | |
| for y = -1,1 do | |
| if supdist(x,y) > 0 then | |
| a = assess_square(x,y) | |
| reason = step_reason(a0,a) | |
| if reason then | |
| if besta == nil or step_improvement(bestreason,reason,besta,a) then | |
| bestx = x | |
| besty = y | |
| besta = a | |
| bestreason = reason | |
| end | |
| end | |
| end | |
| end | |
| end | |
| if besta then | |
| tactical_step = delta_to_vi(bestx,besty) | |
| tactical_reason = bestreason | |
| end | |
| end | |
| function plan_cloud_step() | |
| if tactical_reason == "cloud" then | |
| say("Stepping ~*~*~tactically~*~*~ (" .. tactical_reason .. ").") | |
| magic(tactical_step .. "Y") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_water_step() | |
| if tactical_reason == "water" then | |
| say("Stepping ~*~*~tactically~*~*~ (" .. tactical_reason .. ").") | |
| magic(tactical_step .. "Y") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_coward_step() | |
| if tactical_reason == "hiding" or tactical_reason == "stealth" then | |
| if tactical_reason == "hiding" then | |
| hiding_turn_count = you.turns() | |
| end | |
| if SPAM then | |
| say("Stepping ~*~*~tactically~*~*~ (" .. tactical_reason .. ").") | |
| end | |
| magic(tactical_step .. "Y") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_flee_step() | |
| if tactical_reason == "fleeing" then | |
| if SPAM then | |
| say("FLEEEEING.") | |
| end | |
| set_stair_target(tactical_step) | |
| last_flee_turn = you.turns() | |
| magic(tactical_step .. "Y") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_other_step() | |
| if tactical_reason ~= "none" then | |
| say("Stepping ~*~*~tactically~*~*~ (" .. tactical_reason .. ").") | |
| magic(tactical_step .. "Y") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_berserk() | |
| if can_berserk() and want_to_berserk() then | |
| berserk() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_heroism() | |
| if can_heroism() and want_to_heroism() then | |
| heroism() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_recall() | |
| if can_recall() and want_to_recall() then | |
| recall() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_recall_ancestor() | |
| if can_recall_ancestor() and want_to_recall_ancestor() then | |
| recall_ancestor() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_zig_fog() | |
| if not where:find("Zig") | |
| or you.berserk() or you.teleporting() or you.confused() | |
| or not danger or not hp_is_low(70) | |
| or count_nearby(0,0,LOS) - count_nearby(0,0,2) < 15 | |
| or view.cloud_at(0,0) ~= nil then | |
| return false | |
| end | |
| return read_by_name("fog") | |
| end | |
| function plan_finesse() | |
| if can_finesse() and want_to_finesse() then | |
| finesse() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_slouch() | |
| if can_slouch() and want_to_slouch() then | |
| slouch() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_drain_life() | |
| if can_drain_life() and want_to_drain_life() then | |
| drain_life() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_burn_spellbooks() | |
| if want_to_burn_spellbooks() then | |
| burn_spellbooks() | |
| return true | |
| end | |
| return false | |
| end | |
| function want_to_bia() | |
| if not danger then | |
| return false | |
| end | |
| if check_monsters(LOS, bia_necessary_monsters) or you.piety_rank() > 4 and | |
| ((want_to_berserk() and not can_berserk()) or | |
| check_monsters(LOS, bia_monsters)) then | |
| if count_bia(4) == 0 and not you.teleporting() then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function want_to_finesse() | |
| if danger and where:find("Zig") and hp_is_low(80) and count_nearby(0,0,LOS) >= 5 then | |
| return true | |
| end | |
| if danger and check_monsters(LOS, bia_monsters) and not you.teleporting() then | |
| return true | |
| end | |
| return false | |
| end | |
| function want_to_slouch() | |
| if danger and you.piety_rank() == 6 and not you.teleporting() and estimate_slouch_damage() >= 6 then | |
| return true | |
| end | |
| return false | |
| end | |
| function want_to_drain_life() | |
| if not danger then | |
| return false | |
| end | |
| local cd = count_drainable() | |
| return (cd >= 8 or cd >= 3 and you.race() == "Deep Dwarf" and hp_is_low(80)) | |
| end | |
| function want_to_sgd() | |
| if you.skill("Invocations") >= 12 and | |
| (check_monsters(LOS, bia_monsters) or hp_is_low(50) and immediate_danger) then | |
| if count_sgd(4) == 0 and not you.teleporting() then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function want_to_divine_warrior() | |
| if you.skill("Invocations") >= 8 and | |
| (check_monsters(LOS, bia_monsters) or hp_is_low(50) and immediate_danger) then | |
| if count_divine_warrior(4) == 0 and not you.teleporting() then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function want_to_orbrun_divine_warrior() | |
| return (danger and count_pan_lords(LOS) > 0 and count_divine_warrior(4) == 0 and not you.teleporting()) | |
| end | |
| function want_to_apocalypse() | |
| if you.race() ~= "Deep Dwarf" | |
| and not (you.status("drained") or you.status("heavily drained") or you.status("very heavily drained")) | |
| and (check_monsters(LOS, bia_monsters) or hp_is_low(25) and immediate_danger) then | |
| return true | |
| end | |
| return false | |
| end | |
| function dd_want_to_teleport() | |
| --say("HW: " .. dd_hw_meter) | |
| return ((immediate_danger and dd_hw_meter > 300 | |
| or immediate_danger and bad_corrosion() | |
| or you.god() ~= "Trog" and immediate_danger and hp_is_low(25) | |
| or you.god() == "Trog" and (you.slowed() or too_hungry_to_berserk()) | |
| and want_to_berserk() | |
| and not can_berserk()) | |
| and count_bia(4) == 0) | |
| end | |
| function bad_corrosion() | |
| if you.corrosion() == 0 then | |
| return false | |
| elseif where:find("Slime") then | |
| return (you.corrosion() >= 6 and hp_is_low(70)) | |
| else | |
| return (you.corrosion() >= 3 and hp_is_low(50) or | |
| you.corrosion() >= 4 and hp_is_low(70)) | |
| end | |
| end | |
| function want_to_teleport() | |
| if where:find("Zig") then | |
| return false | |
| end | |
| if count_hostile_sgd(LOS) > 0 and you.xl() < 21 then | |
| sgd_timer = you.turns() | |
| return true | |
| end | |
| if where == "Pan" and (count_hellions(LOS) >= 3 or count_daevas(LOS) >= 3) then | |
| dislike_pan_level = true | |
| return true | |
| end | |
| if you.xl() <= 17 and not can_berserk() and count_big_slimes(LOS) > 0 then | |
| return true | |
| end | |
| if you.race() == "Deep Dwarf" then | |
| return dd_want_to_teleport() | |
| end | |
| return ((immediate_danger and bad_corrosion() | |
| or you.god() ~= "Trog" and immediate_danger and hp_is_low(25) | |
| or you.god() == "Trog" and (you.slowed() or too_hungry_to_berserk()) | |
| and want_to_berserk() | |
| and not can_berserk()) | |
| and count_bia(4) == 0) | |
| end | |
| function want_to_orbrun_teleport() | |
| return (hp_is_low(33) and sense_danger(2)) | |
| end | |
| function need_to_wait_for_ely_healing() | |
| local mp,mmp = you.mp() | |
| return (you.god() == "Elyvilon" and you.piety_rank() >= 4 and mmp >= 2 | |
| and mp < 2) | |
| end | |
| function dd_want_to_heal_wounds() | |
| if (not danger) and (you.regenerating() or dd_prefer_hand() or (dd_prefer_ru_healing() and you.exhausted()) or need_to_wait_for_ely_healing()) then | |
| return false | |
| end | |
| local hp,mhp = you.hp() | |
| if immediate_danger then | |
| return (hp_is_low(25) or (hp_is_low(50) and mhp - hp >= 30) or | |
| (mhp - hp >= 20 and hp <= 20)) | |
| else | |
| if (dd_prefer_hand() or (dd_prefer_ru_healing() and you.exhausted()) or need_to_wait_for_ely_healing()) and (not you.teleporting()) and not hp_is_low(66) then | |
| return false | |
| end | |
| if (you.god() == "Makhleb" and you.piety_rank() >= 1 | |
| or you.god() == "Yredelemnul" and you.piety_rank() >= 4) | |
| and not hp_is_low(66) then | |
| return false | |
| end | |
| return (hp_is_low(25) or | |
| (mhp - hp >= 30) or | |
| (mhp - hp >= 20 and hp <= 20)) | |
| end | |
| end | |
| function want_to_heal_wounds() | |
| if you.race() == "Deep Dwarf" then | |
| return dd_want_to_heal_wounds() | |
| end | |
| if danger and can_ely_healing() and hp_is_low(50) and you.piety_rank() >= 5 | |
| and you.hunger_name() ~= "near starving" then | |
| return true | |
| end | |
| return (danger and hp_is_low(25)) | |
| end | |
| function want_to_orbrun_heal_wounds() | |
| if danger then | |
| return (hp_is_low(25) or hp_is_low(50) and you.teleporting()) | |
| elseif you.race() == "Deep Dwarf" and you.god() ~= "Trog" and you.god() ~= "Makhleb" then | |
| local hp,mhp = you.hp() | |
| return (mhp - hp >= 30) | |
| else | |
| return (hp_is_low(50)) | |
| end | |
| end | |
| function want_to_orbrun_buff() | |
| return ((count_pan_lords(LOS) > 0) or check_monsters(LOS, scary_monsters)) | |
| end | |
| function want_to_serious_buff() | |
| if danger and where:find("Zig") and hp_is_low(50) and count_nearby(0,0,LOS) >= 5 then | |
| return true | |
| end | |
| if you.god() == "Okawaru" or you.god() == "Trog" then | |
| return false -- these gods have their own buffs | |
| end | |
| if you.num_runes() < 3 then | |
| return false -- none of these uniques exist early | |
| end | |
| if you.teleporting() then | |
| return false -- don't waste a potion if we are already leaving | |
| end | |
| return (check_monsters(LOS, ridiculous_uniques)) | |
| end | |
| function want_resistance() | |
| return (check_monsters(LOS, fire_resistance_monsters) and you.res_fire() < 3 | |
| or check_monsters(LOS, cold_resistance_monsters) and you.res_cold() < 3 | |
| or check_monsters(LOS, elec_resistance_monsters) and you.res_shock() < 1 | |
| or check_monsters(LOS, pois_resistance_monsters) and | |
| you.res_poison() < 1 | |
| or where:find("Zig") and check_monsters(LOS, acid_resistance_monsters) | |
| and not you.res_corr()) | |
| end | |
| function want_to_hand() | |
| return check_monsters(LOS, hand_monsters) | |
| end | |
| function want_to_berserk() | |
| return (hp_is_low(50) and sense_danger(2) and | |
| (you.race() ~= "Deep Dwarf" or dd_want_to_heal_wounds()) or | |
| check_monsters(2, scary_monsters) or | |
| (invisi_sigmund and not options.autopick_on)) | |
| end | |
| function want_to_heroism() | |
| return (danger and (hp_is_low(70) or check_monsters(LOS, scary_monsters) or | |
| count_nearby(0,0,LOS) >= 4)) | |
| end | |
| function want_to_recall() | |
| if immediate_danger and hp_is_low(66) then | |
| return false | |
| end | |
| local mp,mmp = you.mp() | |
| return (mp == mmp) | |
| end | |
| function want_to_recall_ancestor() | |
| return (count_elliptic(LOS) == 0) | |
| end | |
| function want_to_stay_in_abyss() | |
| return (game_status == "abyss" and not you.have_rune("abyssal") | |
| and not hp_is_low(50)) | |
| end | |
| function have_pan_runes() | |
| return (you.have_rune("demonic") and you.have_rune("fiery") | |
| and you.have_rune("dark") and you.have_rune("magical") | |
| and you.have_rune("glowing")) | |
| end | |
| function have_hell_runes() | |
| return (you.have_rune("iron") and you.have_rune("obsidian") | |
| and you.have_rune("icy") and you.have_rune("bone")) | |
| end | |
| function want_to_be_in_pan() | |
| return (game_status == "pan" and not have_pan_runes()) | |
| end | |
| function plan_wait_for_melee() | |
| is_waiting = false | |
| if sense_danger(1) or (have_reaching() and sense_danger(2)) | |
| or (not options.autopick_on) or | |
| you.berserk() or you.have_orb() or count_bia(LOS) > 0 or count_sgd(LOS) > 0 or count_divine_warrior(LOS) > 0 or | |
| not view.is_safe_square(0,0) or | |
| view.feature_at(0,0) == "shallow_water" and not you.flying() or | |
| where:find("Abyss") then | |
| wait_count = 0 | |
| return false | |
| end | |
| if you.turns() >= last_wait + 10 then | |
| wait_count = 0 | |
| end | |
| if (not danger) or wait_count >= 10 then | |
| return false | |
| end | |
| -- hack to make us wait when we enter v5 so we don't move off stairs | |
| if v5_entry_turn and you.turns() <= v5_entry_turn + 2 then | |
| is_waiting = true | |
| return false | |
| end | |
| count = 0 | |
| sleeping_count = 0 | |
| local e | |
| for _,e in ipairs(enemy_list) do | |
| if is_ranged(e.m) then | |
| wait_count = 0 | |
| return false | |
| end | |
| if e.m:reach_range() >= 2 and supdist(e.x,e.y) <= 2 then | |
| wait_count = 0 | |
| return false | |
| end | |
| if will_tab(e.x,e.y,0,0,mons_tabbable_square) and not | |
| (e.m:name() == "wandering mushroom" or | |
| e.m:name():find("ballistomycete") or | |
| e.m:name():find("vortex") or | |
| e.m:desc():find("fleeing") or | |
| e.m:status("paralysed") or | |
| e.m:status("confused") or | |
| e.m:status("petrified")) then | |
| count = count + 1 | |
| if e.m:desc():find("sleeping") or e.m:desc():find("dormant") then | |
| sleeping_count = sleeping_count + 1 | |
| end | |
| end | |
| end | |
| if count == 0 then | |
| return false | |
| end | |
| -- say "Waiting for monsters to approach." | |
| if sleeping_count == 0 then | |
| wait_count = wait_count + 1 | |
| end | |
| last_wait = you.turns() | |
| if plan_cure_poison() then | |
| return true | |
| end | |
| -- don't actually wait yet, because we might use a ranged attack instead | |
| is_waiting = true | |
| return false | |
| end | |
| function plan_wait_spit() | |
| if not is_waiting then | |
| return false | |
| end | |
| if you.mutation("spit poison") < 1 then | |
| return false | |
| end | |
| if you.berserk() or you.confused() or you.breath_timeout() then | |
| return false | |
| end | |
| if you.xl() > 11 or starving() or you.hunger_name() == "near starving" | |
| or you.hunger_name() == "very hungry" or you.hunger_name() == "hungry" then | |
| return false | |
| end | |
| dist = 10 | |
| cur_e = none | |
| for _,e in ipairs(enemy_list) do | |
| if supdist(e.x,e.y) < dist and e.m:res_poison() < 1 then | |
| dist = supdist(e.x,e.y) | |
| cur_e = e | |
| end | |
| end | |
| ab_range = 6 | |
| ab_name = "Spit Poison" | |
| if you.mutation("spit poison") > 2 then | |
| ab_range = 7 | |
| ab_name = "Breathe Poison Gas" | |
| end | |
| if dist <= ab_range then | |
| if use_ability(ab_name, "r" .. vector_move(cur_e.x, cur_e.y) .. "\r") then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function starting_spell() | |
| if you.god() == "Trog" or you.xl() > 9 then | |
| no_spells = true | |
| return | |
| end | |
| -- Missing Infusion for now. | |
| local spell_list = {"Shock", "Magic Dart", "Sandblast", "Flame Tongue", "Freeze", "Pain", "Summon Small Mammal", "Beastly Appendage", "Sting"} | |
| for _,sp in ipairs(spell_list) do | |
| if spells.memorised(sp) and spells.fail(sp) <= 25 then | |
| return sp | |
| end | |
| end | |
| no_spells = true | |
| end | |
| function spell_range(sp) | |
| if sp == "Summon Small Mammal" then | |
| return LOS | |
| elseif sp == "Beastly Appendage" then | |
| return 4 | |
| elseif sp == "Sandblast" then | |
| return 3 | |
| else | |
| return spells.range(sp) | |
| end | |
| end | |
| function spell_castable(sp) | |
| if sp == "Beastly Appendage" then | |
| if transformed() then | |
| return false | |
| end | |
| elseif sp == "Summon Small Mammal" then | |
| local x,y | |
| local count = 0 | |
| for x = -LOS,LOS do | |
| for y = -LOS,LOS do | |
| m = monster_array[x][y] | |
| if m and m:attitude() == ATT_FRIENDLY then | |
| count = count + 1 | |
| end | |
| end | |
| end | |
| if count >= 4 then | |
| return false | |
| end | |
| elseif sp == "Sandblast" then | |
| if not have_item("missile", "stone") then | |
| return false | |
| end | |
| end | |
| return true | |
| end | |
| function plan_starting_spell() | |
| if no_spells then | |
| return false | |
| end | |
| if you.silenced() or you.confused() or you.berserk() or starving() then | |
| return false | |
| end | |
| local sp = starting_spell() | |
| if not sp then | |
| return false | |
| end | |
| if cmp() < spells.mana_cost(sp) then | |
| return false | |
| end | |
| if you.xl() > 4 and not is_waiting then | |
| return false | |
| end | |
| local dist = distance_to_tabbable_enemy(0,0) | |
| if dist < 2 and wskill() ~= "Unarmed Combat" then | |
| local weap = items.equipped_at("Weapon") | |
| if weap and weap.weap_skill == wskill() then | |
| return false | |
| end | |
| end | |
| if dist > spell_range(sp) then | |
| return false | |
| end | |
| if not spell_castable(sp) then | |
| return false | |
| end | |
| say("CASTING " .. sp) | |
| if spells.range(sp) > 0 then | |
| magic("z" .. spells.letter(sp) .. "f") | |
| else | |
| magic("z" .. spells.letter(sp)) | |
| end | |
| return true | |
| end | |
| function plan_wait_throw() | |
| if not is_waiting then | |
| return false | |
| end | |
| if distance_to_enemy(0,0) < 3 then | |
| return false | |
| end | |
| if items.fired_item() then | |
| magic("Q-ff") -- reset quiver before throwing | |
| return true | |
| else | |
| return false | |
| end | |
| end | |
| function plan_wait_wait() | |
| if not is_waiting then | |
| return false | |
| end | |
| magic("s") | |
| return true | |
| end | |
| function plan_attack() | |
| if danger and attack() then | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_eat_chunk() | |
| if want_chunk() then | |
| for it in inventory() do | |
| if string.find(it.name(), "chunk") and | |
| not bad_food(it) then | |
| magic("ee") | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_eat_permafood() | |
| if where == "Zot:5" or where:find("Zot") and count_permafood() >= 10 then | |
| return plan_orbrun_eat_permafood() | |
| end | |
| if want_permafood() then | |
| if eat_permafood() then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_orbrun_eat_permafood() | |
| if you.hunger_name() ~= "completely stuffed" and | |
| you.hunger_name() ~= "very full" and | |
| you.hunger_name() ~= "almost alive" and | |
| (you.race() ~= "Ghoul" or you.hunger_name() ~= "not hungry") and | |
| you.transform() ~= "lich" then | |
| if eat_permafood() then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_eat_anyway() | |
| if you.hunger_name() == "very hungry" then | |
| if eat_permafood() then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function drink_blood() | |
| if drink_by_name("coagulated blood") then | |
| return true | |
| end | |
| return drink_by_name("blood") | |
| end | |
| function eat_permafood(prefer_ration) | |
| if you.race() == "Vampire" then | |
| return drink_blood() | |
| end | |
| local l | |
| local max_prefer = 0 | |
| local food_name | |
| for it in inventory() do | |
| if it.class(true) == "food" and not bad_food(it) then | |
| local name = it.name() | |
| local prefer | |
| if name:find("ration") then | |
| if prefer_ration then | |
| prefer = 5 | |
| else | |
| prefer = 1 | |
| end | |
| elseif name:find("chunk") then | |
| prefer = 0 | |
| else | |
| prefer = 3 | |
| end | |
| if prefer > max_prefer then | |
| l = items.index_to_letter(it.slot) | |
| max_prefer = prefer | |
| food_name = it.name() | |
| end | |
| end | |
| end | |
| if max_prefer > 0 then | |
| items.swap_slots(items.letter_to_index(l), items.letter_to_index('e'), | |
| false) | |
| say("EATING " .. food_name .. ".") | |
| magic("ee") | |
| return true | |
| end | |
| return false | |
| end | |
| function count_permafood() | |
| local s = 0 | |
| for it in inventory() do | |
| if it.class(true) == "food" and not bad_food(it) and not food.ischunk(it) then | |
| s = s + it.quantity | |
| end | |
| end | |
| return s | |
| end | |
| function plan_easy_rest() | |
| if should_rest() then | |
| easy_rest() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_rest() | |
| if should_rest() then | |
| rest() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_orbrun_rest() | |
| if you.confused() or you.slowed() or | |
| you.berserk() or you.teleporting() or you.silencing() or | |
| transformed() then | |
| rest() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_abyss_rest() | |
| local hp,mhp = you.hp() | |
| if you.confused() or you.slowed() or | |
| you.berserk() or you.teleporting() or you.silencing() or | |
| transformed() or (hp < mhp) and you.regenerating() then | |
| rest() | |
| return true | |
| end | |
| return false | |
| end | |
| function want_to_burn_spellbooks() | |
| return BURN_BOOKS and (you.god() == "Trog") and can_read() and see_spellbooks_to_burn() | |
| end | |
| function want_blood() | |
| return (count_item("potion","of blood") < 10) | |
| end | |
| -- this function could probably be simplified | |
| function vp_handle_corpses() | |
| if want_blood() and on_bottleable_corpses() then | |
| chop() | |
| elseif want_permafood() and on_edible_corpse() and not on_dangerous_corpse() then | |
| magic("eY") | |
| elseif on_bottleable_corpses() then | |
| chop() | |
| else | |
| return false | |
| end | |
| return true | |
| end | |
| function plan_handle_corpses() | |
| if not on_corpses() then | |
| return false | |
| end | |
| if you.race() == "Vampire" then | |
| return vp_handle_corpses() | |
| end | |
| if on_edible_corpse() then | |
| chop() | |
| return true | |
| end | |
| return false | |
| end | |
| function magicfind(target) | |
| -- this will be turned on again in ready() | |
| offlevel_travel = false | |
| magic(control('f') .. target .. "\ra\r") | |
| end | |
| function god_options() | |
| return c_persist.cur_god_list or GOD_LIST | |
| end | |
| function plan_find_altar() | |
| if not want_altar() then | |
| return false | |
| end | |
| str = "@altar&&<<of " .. table.concat(god_options(), "||of ") | |
| if FADED_ALTAR then | |
| str = str .. "||of an unknown god" | |
| end | |
| str = str .. ">>" | |
| magicfind(str) | |
| return true | |
| end | |
| local tried_find_altar_turn = -1 | |
| function plan_find_conversion_altar() | |
| if unshafting() then | |
| return false | |
| end | |
| if game_status == "tso" and you.god() ~= "the Shining One" then | |
| str = "altar of the Shining One" | |
| elseif LUGONU_CONVERSION and you.xl() == 12 and you.god() == "Lugonu" then | |
| str = "altar of Makhleb" | |
| else | |
| return false | |
| end | |
| expect_new_location = true | |
| if tried_find_altar_turn ~= you.turns() then | |
| tried_find_altar_turn = you.turns() | |
| magic(control('f') .. str .. "\ra\r") | |
| return nil | |
| end | |
| magic(control('f') .. str .. "\rb\r") | |
| return true | |
| end | |
| function plan_abandon_god() | |
| if you.god() ~= "No God" and you.num_runes() == 0 | |
| and not util.contains(god_options(), you.god()) then | |
| magic("aXYY") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_unwield_weapon() | |
| if wskill() ~= "Unarmed Combat" then | |
| return false | |
| end | |
| if not items.equipped_at("Weapon") then | |
| return false | |
| end | |
| magic("w-") | |
| return true | |
| end | |
| function plan_join_beogh() | |
| if you.race() ~= "Hill Orc" or not want_altar() or you.confused() then | |
| return false | |
| end | |
| for _,god in ipairs(god_options()) do | |
| if god == "Beogh" and use_ability("Convert to Beogh", "YY") then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_convert() | |
| if (game_status ~= "tso" or you.god() == "the Shining One" | |
| or view.feature_at(0,0) ~= "altar_the_shining_one") and | |
| ((not LUGONU_CONVERSION) or you.god() ~= "Lugonu" | |
| or view.feature_at(0,0) ~= "altar_makhleb") then | |
| return false | |
| end | |
| if you.silenced() then | |
| rest() | |
| else | |
| if view.feature_at(0,0) == "altar_makhleb" then | |
| for i,br in ipairs(c_persist.branches_entered) do | |
| if br == "L" then | |
| table.remove(c_persist.branches_entered,i) | |
| end | |
| end | |
| end | |
| magic("<YYY") | |
| end | |
| return true | |
| end | |
| function plan_join_god() | |
| if not want_altar() then | |
| return false | |
| end | |
| feat = view.feature_at(0,0) | |
| for _,god in ipairs(god_options()) do | |
| if feat == ("altar_" .. string.gsub(string.lower(god), " ", "_")) then | |
| if you.silenced() then | |
| rest() | |
| else | |
| magic("<YY") | |
| end | |
| return true | |
| end | |
| end | |
| if FADED_ALTAR and feat == "altar_ecumenical" then | |
| if you.silenced() then | |
| rest() | |
| else | |
| magic("<YY") | |
| end | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_find_corpses() | |
| magicfind("@/corpse$&&!!rott&&!!skel&&!!noeat") | |
| return true | |
| end | |
| function plan_autoexplore() | |
| if free_inventory_slots() == 0 then | |
| return false | |
| end | |
| magic("o") | |
| return true | |
| end | |
| function plan_drop_other_items() | |
| upgrade_phase = false | |
| for it in inventory() do | |
| if it.class(true) == "missile" and not want_missile(it) or | |
| it.class(true) == "wand" and not want_wand(it) or | |
| it.class(true) == "potion" and not want_potion(it) or | |
| it.class(true) == "scroll" and not want_scroll(it) then | |
| say("DROPPING " .. it.name() .. ".") | |
| magic("d" .. letter(it) .. "\r") | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_quaff_id() | |
| for it in inventory() do | |
| if it.class(true) == "potion" and it.quantity > 1 and | |
| not it.fully_identified then | |
| return drink(it) | |
| end | |
| end | |
| return false | |
| end | |
| function plan_read_id() | |
| if not can_read() then | |
| return false | |
| end | |
| for it in inventory() do | |
| if it.class(true) == "scroll" and | |
| not it.fully_identified then | |
| items.swap_slots(it.slot, items.letter_to_index('Y'), false) | |
| weap = items.equipped_at("Weapon") | |
| scroll_letter = 'Y' | |
| if weap and not weap.artefact and not brand_is_great(weap.ego()) then | |
| scroll_letter = items.index_to_letter(weap.slot) | |
| items.swap_slots(weap.slot, items.letter_to_index('Y'), false) | |
| end | |
| if you.race() ~= "Felid" then | |
| return read2(scroll_letter, ".Y" .. string.char(27) .. "YB") | |
| else | |
| return read2(scroll_letter, ".Y" .. string.char(27) .. "YC") | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_use_id_scrolls() | |
| if not can_read() then | |
| return false | |
| end | |
| local id_scroll | |
| for it in inventory() do | |
| if it.class(true) == "scroll" and it.name():find("identify") then | |
| id_scroll = it | |
| end | |
| end | |
| if not id_scroll then | |
| return false | |
| end | |
| local oldslots = { } | |
| local newslots = {[0] = 'B', [1] = 'N', [2] = 'Y'} -- harmless keys | |
| local count = 0 | |
| for it in inventory() do | |
| if it.class(true) == "wand" and not it.fully_identified and | |
| (it.name():find("empty") or | |
| it.name():find("digging") | |
| and (where:find("Depths") or where:find("Zot"))) then | |
| oldname = it.name() | |
| if read2(id_scroll, letter(it)) then | |
| say("IDENTIFYING " .. oldname) | |
| return true | |
| end | |
| end | |
| end | |
| for it in inventory() do | |
| if it.class(true) == "jewellery" and not it.fully_identified then | |
| oldname = it.name() | |
| if read2(id_scroll, letter(it)) then | |
| say("IDENTIFYING " .. oldname) | |
| return true | |
| end | |
| end | |
| end | |
| if id_scroll.quantity > 1 then | |
| for it in inventory() do | |
| if it.class(true) == "potion" and not it.fully_identified then | |
| oldname = it.name() | |
| if read2(id_scroll, letter(it)) then | |
| say("IDENTIFYING " .. oldname) | |
| return true | |
| end | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function body_armour_is_great(arm) | |
| local name = arm.name() | |
| local ap = armour_plan() | |
| if ap == "heavy" then | |
| return (name:find("gold dragon") or name:find("crystal plate") | |
| or name:find("plate armour of fire") | |
| or name:find("pearl dragon")) | |
| elseif ap == "large" then | |
| return name:find("dragon scales") | |
| elseif ap == "dodgy" then | |
| return (arm.encumbrance <= 11 and name:find("dragon scales")) | |
| else | |
| return (name:find("dragon scales") or name:find("robe of resistance")) | |
| end | |
| end | |
| function body_armour_is_good(arm) | |
| if in_branch("Z") then | |
| return true | |
| end | |
| local name = arm.name() | |
| local ap = armour_plan() | |
| if ap == "heavy" then | |
| return (name:find("plate") or name:find("dragon scales")) | |
| elseif ap == "large" then | |
| return false | |
| elseif ap == "dodgy" then | |
| return (name:find("ring mail") or name:find("robe of resistance")) | |
| else | |
| return name:find("robe of fire resistance") | |
| end | |
| end | |
| -- do we want to keep this brand? | |
| function brand_is_great(brand) | |
| if brand == "speed" then | |
| return true | |
| elseif brand == "vampirism" then | |
| return (you.race() == "Deep Dwarf" or not you.have_orb()) | |
| elseif brand == "electrocution" then | |
| return (where ~= "Zot:5") | |
| elseif brand == "holy wrath" then | |
| return (where == "Zot:5" or you.have_orb() or PAN_RUNE or HELL_RUNE or GOLDEN_RUNE) | |
| else | |
| return false | |
| end | |
| end | |
| function plan_use_good_consumables() | |
| for it in inventory() do | |
| if it.class(true) == "scroll" and can_read() then | |
| if it.name():find("acquirement") then | |
| if view.feature_at(0,0) ~= "deep_water" | |
| and view.feature_at(0,0) ~= "lava" then | |
| if you.race() == "Felid" then | |
| return read2(it, " c") | |
| else | |
| return read2(it, " b") | |
| end | |
| end | |
| elseif it.name():find("enchant weapon") then | |
| weapon = items.equipped_at("weapon") | |
| if weapon and weapon.class(true) == "weapon" and not weapon.artefact and | |
| weapon.plus < 9 then | |
| oldname = weapon.name() | |
| if read2(it, letter(weapon)) then | |
| say("ENCHANTING " .. oldname .. ".") | |
| return true | |
| end | |
| end | |
| elseif it.name():find("brand weapon") then | |
| weapon = items.equipped_at("weapon") | |
| if weapon and weapon.class(true) == "weapon" and not weapon.artefact and not brand_is_great(weapon.ego()) then | |
| oldname = weapon.name() | |
| if read2(it, letter(weapon)) then | |
| say("BRANDING " .. oldname .. ".") | |
| return true | |
| end | |
| end | |
| elseif it.name():find("enchant armour") then | |
| body = items.equipped_at("Armour") | |
| ac = armour_ac() | |
| if body and not body.artefact and body.plus < ac | |
| and body_armour_is_great(body) | |
| and not body.name():find("quicksilver dragon scales") then | |
| oldname = body.name() | |
| if read2(it, letter(body)) then | |
| say("ENCHANTING " .. oldname .. ".") | |
| return true | |
| end | |
| end | |
| for _,slotname in pairs(good_slots) do | |
| if slotname ~= "Armour" and slotname ~= "Shield" then | |
| it2 = items.equipped_at(slotname) | |
| if it2 and not it2.artefact and it2.plus < 2 and it2.plus >= 0 | |
| and not it2.name():find("scarf") | |
| then | |
| oldname = it2.name() | |
| if read2(it, letter(it2)) then | |
| say("ENCHANTING " .. oldname .. ".") | |
| return true | |
| end | |
| end | |
| if slotname == "Boots" and it2 and it2.name():find("barding") | |
| and not it2.artefact and it2.plus < 4 and it2.plus >= 0 then | |
| oldname = it2.name() | |
| if read2(it, letter(it2)) then | |
| say("ENCHANTING " .. oldname .. ".") | |
| return true | |
| end | |
| end | |
| end | |
| end | |
| if body and not body.artefact and body.plus < ac | |
| and body_armour_is_good(body) | |
| and not body.name():find("quicksilver dragon scales") then | |
| oldname = body.name() | |
| if read2(it, letter(body)) then | |
| say("ENCHANTING " .. oldname .. ".") | |
| return true | |
| end | |
| end | |
| elseif it.name():find("recharging") then | |
| for it2 in inventory() do | |
| if it2.class(true) == "wand" and | |
| it2.name():find("digging") | |
| and (it2.name():find("empty") or | |
| it2.plus and it2.plus < 10) then | |
| oldname = it2.name() | |
| if read2(it, letter(it2)) then | |
| say("RECHARGING " .. oldname .. ".") | |
| return true | |
| end | |
| end | |
| end | |
| elseif it.name():find("remove curse") then | |
| for it2 in inventory() do | |
| if it2.cursed and it2.equipped then | |
| return read(it) | |
| end | |
| end | |
| end | |
| elseif it.class(true) == "potion" then | |
| if it.name():find("experience") then | |
| return drink(it) | |
| end | |
| if it.name():find("mutation") then | |
| if base_mutation("inhibited regeneration") > 0 | |
| and you.race() ~= "Ghoul" or | |
| base_mutation("teleportitis") > 0 or | |
| base_mutation("deformed body") > 0 and you.race() ~= "Naga" | |
| and you.race() ~= "Centaur" | |
| and (armour_plan() == "heavy" or armour_plan() == "large") or | |
| base_mutation("berserk") > 0 or | |
| base_mutation("deterioration") > 1 or | |
| base_mutation("blurry vision") > 0 or | |
| base_mutation("frail") > 0 or | |
| base_mutation("forlorn") > 0 or | |
| base_mutation("no potion heal") > 0 and you.race() ~= "Vine Stalker" | |
| then | |
| if you.god() ~= "Zin" then | |
| return drink(it) | |
| elseif you.piety_rank() >= 6 and not you.one_time_ability_used() and | |
| use_ability("Cure All Mutations", "Y") then | |
| return true | |
| end | |
| end | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function base_mutation(str) | |
| return you.mutation(str) - you.temp_mutation(str) | |
| end | |
| function is_melee_weapon(it) | |
| return (it and it.class(true) == "weapon" and it.weap_skill ~= "Bows" | |
| and it.weap_skill ~= "Crossbows" and it.weap_skill ~= "Slings") | |
| end | |
| function plan_wield_weapon() | |
| local weap = items.equipped_at("Weapon") | |
| if is_melee_weapon(weap) or you.berserk() or transformed() then | |
| return false | |
| end | |
| if wskill() == "Unarmed Combat" then | |
| return false | |
| end | |
| for it in inventory() do | |
| if it and it.class(true) == "weapon" then | |
| if should_equip(it) and (not it.name():find("vamp") or intrinsic_undead()) then | |
| l = items.index_to_letter(it.slot) | |
| say("Wielding weapon " .. it.name() .. ".") | |
| magic("w" .. l .. "YY") | |
| -- this might have a 0-turn fail because of unIDed vamp/holy | |
| return nil | |
| end | |
| end | |
| end | |
| if weap and not is_melee_weapon(weap) then | |
| magic("w-") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_swap_weapon() | |
| if you.race() == "Troll" or you.berserk() or transformed() or not items.equipped_at("Weapon") then | |
| return false | |
| end | |
| local sit | |
| local x,y | |
| if you.xl() < 18 then | |
| for x = -3,3 do | |
| for y = -3,3 do | |
| m = monster_array[x][y] | |
| if m and string.find(m:desc(), "hydra") and will_tab(0,0,x,y,tabbable_square) then | |
| sit = "hydra" | |
| end | |
| end | |
| end | |
| end | |
| if in_extended() then | |
| sit = "extended" | |
| end | |
| twohands = true | |
| if items.equipped_at("Shield") and you.race() ~= "Formicid" then | |
| twohands = false | |
| end | |
| it_old = items.equipped_at("Weapon") | |
| swappable = can_swap("Weapon") | |
| if not swappable or (it_old.ego() == "vampirism" and not intrinsic_undead()) then | |
| return false | |
| end | |
| cur_val = weapon_value(it_old,true,it_old,sit) | |
| max_val = cur_val | |
| max_it = nil | |
| for it in inventory() do | |
| if it and it.class(true) == "weapon" and not it.equipped then | |
| if (twohands or it.hands < 2) and not (it.name():find("vamp") and not intrinsic_undead()) then | |
| val2 = weapon_value(it,true,it_old,sit) | |
| if val2 > max_val then | |
| max_val = val2 | |
| max_it = it | |
| end | |
| end | |
| end | |
| end | |
| if max_it then | |
| l = items.index_to_letter(max_it.slot) | |
| say("SWAPPING to " .. max_it.name() .. ".") | |
| magic("w" .. l .. "YY") | |
| -- this might have a 0-turn fail because of unIDed vamp/holy | |
| return nil | |
| end | |
| return false | |
| end | |
| function plan_bless_weapon() | |
| if you.god() ~= "the Shining One" or you.one_time_ability_used() | |
| or you.piety_rank() < 6 or you.silenced() then | |
| return false | |
| end | |
| local bestv = -1 | |
| local minv, maxv, bestletter | |
| for it in inventory() do | |
| if equip_slot(it) == "Weapon" then | |
| minv,maxv = equip_value(it, true, nil, "bless") | |
| if minv > bestv then | |
| bestv = minv | |
| bestletter = letter(it) | |
| end | |
| end | |
| end | |
| if bestv > 0 then | |
| use_ability("Brand Weapon With Holy Wrath", bestletter .. "Y") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_upgrade_weapon() | |
| if you.race() == "Troll" then | |
| return false | |
| end | |
| local sit | |
| if in_extended() then | |
| sit = "extended" | |
| end | |
| twohands = true | |
| if items.equipped_at("Shield") and you.race() ~= "Formicid" then | |
| twohands = false | |
| end | |
| it_old = items.equipped_at("Weapon") | |
| swappable = can_swap("Weapon") | |
| for it in inventory() do | |
| if it and it.class(true) == "weapon" and not it.equipped then | |
| local equip = false | |
| local drop = false | |
| if should_upgrade(it,it_old,sit) then | |
| equip = true | |
| elseif should_drop(it) then | |
| drop = true | |
| end | |
| if equip and swappable and (twohands or it.hands < 2) then | |
| if it.name():find("vamp") and not intrinsic_undead() and not | |
| (you.hunger_name() == "full" or you.hunger_name() == "very full" or | |
| you.hunger_name() == "completely stuffed") then | |
| say("Eating in order to wield vampiric weapon.") | |
| if eat_permafood() then | |
| return true | |
| end | |
| else | |
| l = items.index_to_letter(it.slot) | |
| say("UPGRADING to " .. it.name() .. ".") | |
| magic("w" .. l .. "YY") | |
| -- this might have a 0-turn fail because of unIDed vamp/holy | |
| return nil | |
| end | |
| end | |
| if drop then | |
| l = items.index_to_letter(it.slot) | |
| say("DROPPING " .. it.name() .. ".") | |
| magic("d" .. l .. "\r") | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_remove_terrible_jewellery() | |
| if you.berserk() or transformed() then | |
| return false | |
| end | |
| for it in inventory() do | |
| if it and it.equipped and it.class(true) == "jewellery" | |
| and not it.cursed | |
| and should_remove(it) then | |
| say("REMOVING " .. it.name() .. ".") | |
| magic("P" .. letter(it) .. "YY") | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_upgrade_amulet() | |
| it_old = items.equipped_at("Amulet") | |
| swappable = can_swap("Amulet") | |
| for it in inventory() do | |
| if it and equip_slot(it) == "Amulet" | |
| and (it.fully_identified or count_item("scroll", "remove curse") > 1) | |
| and not it.equipped then | |
| local equip = false | |
| local drop = false | |
| if should_upgrade(it,it_old) then | |
| equip = true | |
| elseif should_drop(it) then | |
| drop = true | |
| end | |
| if equip and swappable then | |
| l = items.index_to_letter(it.slot) | |
| say("UPGRADING to " .. it.name() .. ".") | |
| magic("P" .. l .. "YY") | |
| return true | |
| end | |
| if drop then | |
| l = items.index_to_letter(it.slot) | |
| say("DROPPING " .. it.name() .. ".") | |
| magic("d" .. l .. "\r") | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_upgrade_rings() | |
| local it_rings = ring_list() | |
| local empty = (empty_ring_slots() > 0) | |
| for it in inventory() do | |
| if it and equip_slot(it) == "Ring" | |
| and (it.fully_identified or count_item("scroll", "remove curse") > 1) | |
| and not it.equipped then | |
| local equip = false | |
| local drop = false | |
| local swap = nil | |
| if empty then | |
| if should_equip(it) then | |
| equip = true | |
| end | |
| else | |
| for _, it_old in ipairs(it_rings) do | |
| if not equip and not it_old.cursed and should_upgrade(it, it_old) then | |
| equip = true | |
| swap = it_old.slot | |
| end | |
| end | |
| end | |
| if not equip and should_drop(it) then | |
| drop = true | |
| end | |
| if equip then | |
| l = items.index_to_letter(it.slot) | |
| say("UPGRADING to " .. it.name() .. ".") | |
| if swap then | |
| items.swap_slots(swap, items.letter_to_index('Y'), false) | |
| if l == 'Y' then | |
| l = items.index_to_letter(swap) | |
| end | |
| end | |
| magic("P" .. l .. "YY") | |
| return true | |
| end | |
| if drop then | |
| l = items.index_to_letter(it.slot) | |
| say("DROPPING " .. it.name() .. ".") | |
| magic("d" .. l .. "\r") | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_maybe_upgrade_armour() | |
| if not upgrade_phase then | |
| return false | |
| end | |
| return plan_upgrade_armour() | |
| end | |
| function plan_upgrade_armour() | |
| if cloudy or you.mesmerised() then | |
| return false | |
| end | |
| for it in inventory() do | |
| if it and it.class(true) == "armour" and not it.equipped then | |
| local st, _ = it.subtype() | |
| local equip = false | |
| local drop = false | |
| local swappable | |
| it_old = items.equipped_at(good_slots[st]) | |
| swappable = can_swap(good_slots[st]) | |
| if should_upgrade(it,it_old) then | |
| equip = true | |
| elseif should_drop(it) then | |
| drop = true | |
| end | |
| if good_slots[st] == "Helmet" and it.ac == 1 and (you.mutation("horns") > 0 | |
| or you.mutation("beak") > 0 or you.mutation("antennae") > 0) then | |
| equip = false | |
| drop = true | |
| end | |
| if good_slots[st] == "Helmet" and | |
| (you.mutation("horns") >= 3 or you.mutation("antennae") >= 3) then | |
| equip = false | |
| drop = true | |
| end | |
| if it.name():find("boots") and | |
| (you.mutation("talons") >= 3 or you.mutation("hooves") >= 3) then | |
| equip = false | |
| drop = true | |
| end | |
| if it.name():find("boots") and you.race() == "Merfolk" | |
| and (view.feature_at(0,0) == "shallow_water" or | |
| view.feature_at(0,0) == "deep_water") then | |
| equip = false | |
| drop = false | |
| end | |
| if good_slots[st] == "Gloves" and you.mutation("claws") >= 3 then | |
| equip = false | |
| drop = true | |
| end | |
| if equip and swappable then | |
| l = items.index_to_letter(it.slot) | |
| say("UPGRADING to " .. it.name() .. ".") | |
| magic("W" .. l .. "YN") | |
| upgrade_phase = true | |
| return true | |
| end | |
| if drop then | |
| l = items.index_to_letter(it.slot) | |
| say("DROPPING " .. it.name() .. ".") | |
| magic("d" .. l .. "\r") | |
| return true | |
| end | |
| end | |
| end | |
| for it in inventory() do | |
| if it and it.equipped and it.class(true) == "armour" and (not it.cursed) | |
| and should_remove(it) then | |
| l = items.index_to_letter(it.slot) | |
| say("REMOVING " .. it.name() .. ".") | |
| magic("T" .. l .. "YN") | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_go_up() | |
| local feat = view.feature_at(0,0) | |
| if feat:find("stone_stairs_up") or feat == "escape_hatch_up" | |
| or feat == "exit_zot" or feat == "exit_dungeon" | |
| or feat == "exit_depths" then | |
| if you.mesmerised() then | |
| return false | |
| end | |
| expect_new_location = true | |
| magic("<") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_go_down() | |
| local feat = view.feature_at(0,0) | |
| if feat:find("stone_stairs_down") then | |
| expect_new_location = true | |
| magic(">") | |
| return true | |
| end | |
| return false | |
| end | |
| function ready_for_lair() | |
| if you.god() == "Trog" or you.god() == "Cheibriados" or you.god() == "Okawaru" or you.god() == "Qazlal" or you.god() == "the Shining One" or you.god() == "Lugonu" or you.god() == "Uskayaw" or you.god() == "Xom" or you.god() == "Zin" or | |
| (you.god() == "Beogh" or you.god() == "Makhleb" or you.god() == "Yredelemnul") and you.piety_rank() >= 4 or | |
| (you.god() == "Ru" or you.god() == "Elyvilon") and you.piety_rank() >= 3 or | |
| you.god() == "Hepliaklqana" and you.piety_rank() >= 2 then | |
| return true | |
| end | |
| return false | |
| end | |
| function feat_is_upstairs(feat) | |
| return (feat:find("stone_stairs_up") or | |
| feat:find("exit_") and (feat == "exit_hell" or feat == "exit_vaults" | |
| or feat == "exit_zot" or feat == "exit_slime_pits" | |
| or feat == "exit_orcish_mines" or feat == "exit_lair" | |
| or feat == "exit_crypt" or feat == "exit_snake_pit" | |
| or feat == "exit_elven_halls" or feat == "exit_tomb" | |
| or feat == "exit_swamp" or feat == "exit_shoals" | |
| or feat == "exit_spider_nest" or feat == "exit_depths")) | |
| end | |
| function want_to_stairdance_up() | |
| local feat = view.feature_at(0,0) | |
| if not feat_is_upstairs(feat) then | |
| return false | |
| end | |
| local n = stairdance_count[where] or 0 | |
| if n >= 20 then | |
| return false | |
| end | |
| if you.caught() or you.mesmerised() or you.constricted() or you.rooted() | |
| or you.transform() == "tree" or you.transform() == "fungus" | |
| or count_bia(3) > 0 or count_sgd(3) > 0 or count_divine_warrior(3) > 0 then | |
| return false | |
| end | |
| local only_when_safe = (you.berserk() or hp_is_low(33)) | |
| local follow_count = 0 | |
| local other_count = 0 | |
| local e | |
| for _,e in ipairs(enemy_list) do | |
| local dist = supdist(e.x,e.y) | |
| if dist == 1 and e.m:stabbability() == 0 and can_use_stairs(e.m:name()) then | |
| follow_count = follow_count + 1 | |
| else | |
| other_count = other_count + 1 | |
| end | |
| end | |
| if only_when_safe and follow_count > 0 then | |
| return false | |
| end | |
| if follow_count == 0 and (reason_to_rest(90) or you.status("barbed spikes")) | |
| and not buffed() | |
| or other_count > 0 and follow_count > 0 then | |
| stairdance_count[where] = n + 1 | |
| return true | |
| end | |
| return false | |
| end | |
| -- adding some clua for this would be better | |
| function can_use_stairs(mname) | |
| if mname:find("zombie") or mname:find("skeleton") or mname:find("spectral") | |
| or mname:find("simulacrum") or mname:find("oklob") | |
| or mname:find("statue") or mname:find("ballistomycete") | |
| or mname == "burning bush" or mname == "lightning spire" | |
| or mname:find("tentacle") or mname == "silent spectre" | |
| or mname == "Geryon" or mname == "Royal Jelly" | |
| or mname:find("'s ghost") or mname:find("' ghost") | |
| or mname == "swamp worm" or mname == "electric eel" | |
| or mname == "kraken" or mname == "lava snake" | |
| or mname == "bat" or mname == "unseen horror" | |
| or mname == "fire vortex" then | |
| return false | |
| else | |
| return true | |
| end | |
| end | |
| function plan_stairdance_up() | |
| if want_to_stairdance_up() then | |
| expect_new_location = true | |
| -- set travel_destination to the current location in case we | |
| -- leave the branch while stairdancing | |
| if not travel_destination then | |
| travel_destination = cur_branch() | |
| end | |
| say("STAIRDANCE") | |
| magic("<") | |
| return true | |
| end | |
| return false | |
| end | |
| function want_to_buy(it) | |
| local class = it.class(true) | |
| if class == "food" or class == "missile" then | |
| return false | |
| elseif class == "scroll" then | |
| local sub = it.subtype() | |
| if (sub == "identify" or sub == "remove curse") and count_item("scroll",sub) > 9 then | |
| return false | |
| elseif sub == "recharging" and count_item("scroll", sub) > 0 then | |
| return false | |
| end | |
| end | |
| return autopickup(it, it.name()) | |
| end | |
| function plan_shop() | |
| if view.feature_at(0,0) ~= "enter_shop" or free_inventory_slots() == 0 then | |
| return false | |
| end | |
| if you.berserk() or you.caught() or you.mesmerised() then | |
| return false | |
| end | |
| local it, price, on_list | |
| for n,e in ipairs(items.shop_inventory()) do | |
| it = e[1] | |
| price = e[2] | |
| on_list = e[3] | |
| if want_to_buy(it) then | |
| -- We want the item. Can we afford buying it now? | |
| local wealth = you.gold() | |
| if price <= wealth then | |
| --say("BUYING " .. it.name() .. " (" .. price .. " gold).") | |
| magic("<" .. letter(n-1) .. "\ry") | |
| return | |
| -- Should in theory also work in Bazaar, but doesn't make much sense | |
| -- (since we won't really return or acquire money and travel back here) | |
| elseif not on_list | |
| and not you.where():find("Bazaar") and not zot_soon() then | |
| say("SHOPLISTING " .. it.name() .. " (" .. price .. " gold" | |
| .. ", have " .. wealth .. ").") | |
| magic("<" .. string.upper(letter(n-1))) | |
| return | |
| end | |
| elseif on_list then | |
| -- We no longer want the item. Remove it from shopping list. | |
| magic("<" .. string.upper(letter(n-1))) | |
| return | |
| end | |
| end | |
| return false | |
| end | |
| function plan_shopping_spree() | |
| if game_status ~= "shopping" then | |
| return false | |
| end | |
| which_item = can_afford_any_shoplist_item() | |
| if not which_item then | |
| -- Remove everything on shoplist | |
| clear_out_shopping_list() | |
| -- Set travel_destination if necessary so that we will return to D. | |
| if not in_branch("D") then | |
| travel_destination = "D" | |
| end | |
| -- record that we are done shopping this game | |
| c_persist.done_shopping = true | |
| update_game_status() | |
| return false | |
| end | |
| say("SHOPPING SPREE") | |
| magic("$" .. letter(which_item - 1)) | |
| expect_new_location = true | |
| return true | |
| end | |
| -- Usually, this function should return `1` or `false`. | |
| function can_afford_any_shoplist_item() | |
| local shoplist = items.shopping_list() | |
| if not shoplist then | |
| return false | |
| end | |
| local price | |
| for n, entry in ipairs(shoplist) do | |
| price = entry[2] | |
| -- Since the shopping list holds no reference to the item itself, | |
| -- we cannot check want_to_buy() until arriving at the shop. | |
| if price <= you.gold() then | |
| return n | |
| end | |
| end | |
| return false | |
| end | |
| -- Clear out shopping list if no affordable items are left before entering Zot | |
| function clear_out_shopping_list() | |
| local shoplist = items.shopping_list() | |
| if not shoplist then | |
| return false | |
| end | |
| say("CLEARING SHOPPING LIST") | |
| -- Press ! twice to toggle action to 'delete' | |
| local clear_shoplist_magic = "$!!" | |
| for n, it in ipairs(shoplist) do | |
| clear_shoplist_magic = clear_shoplist_magic .. "a" | |
| end | |
| magic(clear_shoplist_magic) | |
| return false | |
| end | |
| function plan_simple_go_down() | |
| if travel_destination or unshafting() then | |
| return false | |
| end | |
| if (found_branch("L") and ready_for_lair() or where == "D:11") and not | |
| util.contains(c_persist.branches_entered, "L") then | |
| return false | |
| end | |
| if where == "Vaults:4" and easy_runes() < 2 then | |
| return false | |
| end | |
| if where == "Tomb:1" or where == "Tomb:2" or where == "Tomb:3" then | |
| return false | |
| end | |
| expect_new_location = true | |
| magic("G>") | |
| return true | |
| end | |
| function want_altar() | |
| return (you.race() ~= "Demigod" and you.num_runes() == 0 | |
| and not util.contains(god_options(), you.god())) | |
| end | |
| function plan_go_to_temple() | |
| local c = c_persist.plan_fail_count["try_go_to_temple"] | |
| if c and c >= 100 then | |
| return false | |
| end | |
| if found_branch("T") and (want_altar() or TSO_CONVERSION | |
| or LUGONU_CONVERSION) and not | |
| util.contains(c_persist.branches_entered, "T") and in_branch("D") then | |
| expect_new_location = true | |
| magic("GTY") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_enter_branch() | |
| local br | |
| if found_branch("L") and ready_for_lair() and not | |
| util.contains(c_persist.branches_entered, "L") and in_branch("D") then | |
| br = "L" | |
| elseif found_branch("O") and not LATE_ORC and not | |
| util.contains(c_persist.branches_entered, "O") and in_branch("D") and | |
| util.contains(c_persist.branches_entered, "L") then | |
| br = "O" | |
| end | |
| if br then | |
| expect_new_location = true | |
| magic("G" .. br .. "\rY") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_go_to_portal_entrance() | |
| for _, por in ipairs(c_persist.portals_found) do | |
| for _, val in ipairs(portal_data) do | |
| if val[1] == por then | |
| magicfind("@" .. val[2]) | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_go_to_zig() | |
| if not where:find("Depths") or game_status ~= "zig" or c_persist.entered_zig then | |
| return false | |
| else | |
| expect_new_location = true | |
| magicfind("gateway to a ziggurat") | |
| return true | |
| end | |
| end | |
| function plan_go_to_zig_dig() | |
| if not where:find("Depths") or game_status ~= "zig" or c_persist.entered_zig | |
| or view.feature_at(0,0) == "enter_ziggurat" | |
| or view.feature_at(3,1) == "enter_ziggurat" | |
| or count_charges("digging") == 0 then | |
| return false | |
| else | |
| expect_new_location = true | |
| off_level_travel = false | |
| magic(control('f') .. "gateway to a ziggurat" .. "\rayby\r") | |
| return true | |
| end | |
| end | |
| function plan_zig_dig() | |
| if not where:find("Depths") or game_status ~= "zig" or c_persist.entered_zig | |
| or view.feature_at(3,1) ~= "enter_ziggurat" then | |
| return false | |
| else | |
| local c = find_item("wand", "digging") | |
| if c and can_zap() then | |
| say("ZAPPING " .. item(c).name() .. ".") | |
| magic("V" .. letter(c) .. "L") | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_go_to_portal_exit() | |
| if in_portal() then | |
| magic("X<\r") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_go_to_abyss_portal() | |
| if not where:find("Depths") or not want_to_stay_in_abyss() then | |
| return false | |
| else | |
| expect_new_location = true | |
| magicfind("one-way gate to the infinite horrors of the Abyss") | |
| return true | |
| end | |
| end | |
| function plan_go_to_pan_portal() | |
| if not where:find("Depths") or not want_to_be_in_pan() then | |
| return false | |
| else | |
| expect_new_location = true | |
| magicfind("halls of Pandemonium") | |
| return true | |
| end | |
| end | |
| function plan_go_to_abyss_downstairs() | |
| if where:find("Abyss") and want_to_stay_in_abyss() | |
| and where ~= "Abyss:3" and where ~= "Abyss:4" and where ~= "Abyss:5" then | |
| magic("X>\r") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_go_to_pan_downstairs() | |
| if where == "Pan" then | |
| magic("X>\r") | |
| return true | |
| end | |
| return false | |
| end | |
| local pan_failed_rune_count = -1 | |
| function want_to_dive_pan() | |
| return (where == "Pan" and you.num_runes() > pan_failed_rune_count | |
| and (you.have_rune("demonic") and not have_pan_runes() | |
| or dislike_pan_level)) | |
| end | |
| function plan_dive_go_to_pan_downstairs() | |
| if want_to_dive_pan() then | |
| magic("X>\r") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_open_runed_doors() | |
| if where ~= "Pan" and (where ~= "Depths:3" or not PAN_RUNE) then | |
| return false | |
| end | |
| for x = -1,1 do | |
| for y = -1,1 do | |
| if view.feature_at(x,y) == "runed_door" then | |
| magic(delta_to_vi(x,y) .. "Y") | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_go_to_abyss_exit() | |
| if want_to_stay_in_abyss() then | |
| return false | |
| end | |
| magic("X<\r") | |
| return true | |
| end | |
| function plan_go_to_pan_exit() | |
| if where == "Pan" and not want_to_be_in_pan() then | |
| magic("X<\r") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_dive() | |
| if (in_branch("M") and where ~= "Slime:5" or game_status == "hells" and | |
| (in_branch("I") and where ~= "Dis:7" | |
| or in_branch("G") and where ~= "Geh:7" | |
| or in_branch("X") and where ~= "Coc:7" | |
| or in_branch("Y") and where ~= "Tar:7")) | |
| and not travel_destination then | |
| expect_new_location = true | |
| magic("G>") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_early_new_travel() | |
| if game_status == "hells" and | |
| (where == "Dis:7" and you.have_rune("iron") | |
| or where == "Geh:7" and you.have_rune("obsidian") | |
| or where == "Coc:7" and you.have_rune("icy") | |
| or where == "Tar:7" and you.have_rune("bone")) | |
| and not travel_destination then | |
| travel_destination = "H" | |
| return plan_continue_travel() | |
| end | |
| return false | |
| end | |
| function plan_enter_zig() | |
| if not where:find("Depths") or game_status ~= "zig" or c_persist.entered_zig then | |
| return false | |
| end | |
| if view.feature_at(0,0) == "enter_ziggurat" then | |
| expect_new_location = true | |
| c_persist.entered_zig = true | |
| magic(">Y") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_enter_portal() | |
| for _, por in ipairs(c_persist.portals_found) do | |
| if string.find(view.feature_at(0,0), "enter_" .. get_feat_name(por)) then | |
| expect_portal = true | |
| expect_new_location = true | |
| magic(">") | |
| return true | |
| end | |
| return false | |
| end | |
| return false | |
| end | |
| function plan_exit_portal() | |
| if not in_portal() or you.mesmerised() then | |
| return false | |
| end | |
| if string.find(view.feature_at(0,0), "exit_" .. get_feat_name(where)) then | |
| expect_new_location = true | |
| magic("<") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_enter_abyss() | |
| if view.feature_at(0,0) == "enter_abyss" and want_to_stay_in_abyss() then | |
| expect_new_location = true | |
| magic(">Y") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_enter_pan() | |
| if view.feature_at(0,0) == "enter_pandemonium" and want_to_be_in_pan() then | |
| expect_new_location = true | |
| magic(">Y") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_go_down_abyss() | |
| if view.feature_at(0,0) == "abyssal_stair" and want_to_stay_in_abyss() | |
| and where ~= "Abyss:3" and where ~= "Abyss:4" and where ~= "Abyss:5" then | |
| expect_new_location = true | |
| magic(">") | |
| return true | |
| end | |
| return false | |
| end | |
| local pan_stair_turn = -100 | |
| function plan_go_down_pan() | |
| if view.feature_at(0,0) == "transit_pandemonium" | |
| or view.feature_at(0,0) == "exit_pandemonium" then | |
| if pan_stair_turn == you.turns() then | |
| magic("X" .. control('f')) | |
| return true | |
| end | |
| expect_new_location = true | |
| pan_stair_turn = you.turns() | |
| magic(">Y") | |
| return nil -- in case we are trying to leave a rune level | |
| end | |
| return false | |
| end | |
| function plan_dive_pan() | |
| if not want_to_dive_pan() then | |
| return false | |
| end | |
| if view.feature_at(0,0) == "transit_pandemonium" | |
| or view.feature_at(0,0) == "exit_pandemonium" then | |
| if pan_stair_turn == you.turns() then | |
| pan_failed_rune_count = you.num_runes() | |
| return false | |
| end | |
| expect_new_location = true | |
| pan_stair_turn = you.turns() | |
| dislike_pan_level = false | |
| magic(">Y") | |
| return nil -- in case we are trying to leave a rune level | |
| end | |
| return false | |
| end | |
| function plan_zig_leave_level() | |
| if not where:find("Zig") then | |
| return false | |
| end | |
| if where:find(tostring(ZIG_DIVE)) then | |
| if view.feature_at(0,0) == "exit_ziggurat" then | |
| magic("<Y") | |
| expect_new_location = true | |
| return true | |
| end | |
| elseif string.find(view.feature_at(0,0), "stone_stairs_down") then | |
| magic(">") | |
| expect_new_location = true | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_lugonu_exit_abyss() | |
| if you.god() ~= "Lugonu" then | |
| return false | |
| end | |
| if (you.berserk() or you.confused() or you.silenced() or starving() | |
| or you.piety_rank() < 1 or cmp() < 1) then | |
| return false | |
| end | |
| expect_new_location = true | |
| use_ability("Depart the Abyss") | |
| return true | |
| end | |
| function plan_exit_abyss() | |
| if view.feature_at(0,0) == "exit_abyss" and not want_to_stay_in_abyss() and not you.mesmerised() and you.transform() ~= "tree" then | |
| expect_new_location = true | |
| magic("<") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_exit_pan() | |
| if view.feature_at(0,0) == "exit_pandemonium" and not want_to_be_in_pan() and not you.mesmerised() and you.transform() ~= "tree" then | |
| expect_new_location = true | |
| magic("<") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_step_towards_lair() | |
| local x, y, feat | |
| if (stepped_on_lair or not found_branch("L")) | |
| and (where ~= "Crypt:3" or stepped_on_tomb or not found_branch("W")) then | |
| return false | |
| end | |
| for x = -LOS,LOS do | |
| for y = -LOS,LOS do | |
| feat = view.feature_at(x,y) | |
| if (feat == "enter_lair" or feat == "enter_tomb") | |
| and you.see_cell_no_trans(x,y) then | |
| if x == 0 and y == 0 then | |
| if where == "Crypt:3" then | |
| stepped_on_tomb = true | |
| else | |
| stepped_on_lair = true | |
| end | |
| return false | |
| else | |
| lair_step_mode = true | |
| local result = move_towards(x,y) | |
| lair_step_mode = false | |
| return result | |
| end | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_continue_travel() | |
| if travel_destination then | |
| if in_branch(travel_destination) or | |
| not found_branch(travel_destination) then | |
| travel_destination = nil | |
| return false | |
| end | |
| expect_new_location = true | |
| magic("G" .. travel_destination .. "\rY") | |
| return true | |
| end | |
| return false | |
| end | |
| function choose_lair_rune_branch() | |
| if RUNE_PREFERENCE == "smart" then | |
| if crawl.random2(2) == 0 then | |
| branch_options = { "N", "P", "S", "A" } | |
| else | |
| branch_options = { "N", "S", "P", "A" } | |
| end | |
| elseif RUNE_PREFERENCE == "dsmart" then | |
| if crawl.random2(2) == 0 then | |
| branch_options = { "N", "S", "P", "A" } | |
| else | |
| branch_options = { "S", "N", "P", "A" } | |
| end | |
| elseif RUNE_PREFERENCE == "nowater" then | |
| branch_options = { "P", "N", "S", "A" } | |
| else -- "random" | |
| if crawl.random2(2) == 0 then | |
| branch_options = { "P", "N", "S", "A" } | |
| else | |
| branch_options = { "S", "A", "P", "N" } | |
| end | |
| end | |
| for _, branch_code in ipairs(branch_options) do | |
| if found_branch(branch_code) and | |
| not util.contains(c_persist.branches_entered, branch_code) then | |
| return branch_code | |
| end | |
| end | |
| return nil | |
| end | |
| function choose_hell_rune_branch() | |
| local hell_branches = { "I", "G", "X", "Y" } | |
| local untried_branches = {} | |
| local count = 0 | |
| for _, branch_code in ipairs(hell_branches) do | |
| if not util.contains(c_persist.branches_entered, branch_code) then | |
| table.insert(untried_branches, branch_code) | |
| count = count + 1 | |
| end | |
| end | |
| if count == 0 then | |
| return "U" -- depths | |
| end | |
| return untried_branches[crawl.roll_dice(1,count)] | |
| end | |
| local depths_loop_count = 0 | |
| function plan_new_travel() | |
| if cloudy then | |
| return false | |
| end | |
| local back_to_D_places = { "Temple", "Orc:2", "Vaults:4"} | |
| if util.contains(back_to_D_places, where) then | |
| travel_destination = "D" | |
| end | |
| if where == "D:11" and not util.contains(c_persist.branches_entered, "L") then | |
| travel_destination = "L" | |
| end | |
| if where == "Lair:6" then | |
| if travel.find_deepest_explored("D") == 15 and easy_runes() < 2 then | |
| travel_destination = choose_lair_rune_branch() | |
| elseif game_status == "slime" then | |
| travel_destination = "M" | |
| else | |
| travel_destination = "D" | |
| end | |
| end | |
| if where == "Snake:4" and you.have_rune("serpentine") then | |
| travel_destination = "D" | |
| end | |
| if where == "Swamp:4" and you.have_rune("decaying") then | |
| travel_destination = "D" | |
| end | |
| if where == "Spider:4" and you.have_rune("gossamer") then | |
| travel_destination = "D" | |
| end | |
| if where == "Shoals:4" and you.have_rune("barnacled") then | |
| travel_destination = "D" | |
| end | |
| if where == "Vaults:5" and you.have_rune("silver") then | |
| if game_status == "tomb" then | |
| travel_destination = "C" | |
| else | |
| travel_destination = "D" | |
| end | |
| end | |
| if where == "Slime:5" and you.have_rune("slimy") then | |
| travel_destination = "D" | |
| end | |
| if where == "D:15" then | |
| if easy_runes() == 1 and not util.contains(c_persist.branches_entered, "V") | |
| or easy_runes() == 2 and util.contains(c_persist.branches_entered, "U") and not you.have_rune("silver") then | |
| travel_destination = "V" | |
| elseif easy_runes() >= 1 and not util.contains(c_persist.branches_entered, "U") and not (EARLY_SECOND_RUNE and easy_runes() == 1) then | |
| travel_destination = "U" | |
| elseif you.have_rune("silver") then | |
| if game_status == "slime" then | |
| travel_destination = "L" | |
| elseif game_status == "tomb" then | |
| travel_destination = "V" | |
| else | |
| travel_destination = "U" | |
| end | |
| elseif LATE_ORC and found_branch("O") and not | |
| util.contains(c_persist.branches_entered, "O") then | |
| travel_destination = "O" | |
| else | |
| travel_destination = "L" | |
| end | |
| end | |
| if where == "Depths:5" then | |
| if game_status == "zot" then | |
| travel_destination = "Z" | |
| elseif game_status == "hells" then | |
| travel_destination = "H" | |
| else | |
| if game_status == "shopping" then | |
| c_persist.done_shopping = true | |
| end | |
| if depths_loop_count < 20 then | |
| travel_destination = "D" | |
| depths_loop_count = depths_loop_count + 1 | |
| else | |
| game_status = "zot" | |
| travel_destination = "Z" | |
| end | |
| end | |
| end | |
| if where == "Hell" then | |
| travel_destination = choose_hell_rune_branch() | |
| end | |
| -- travel back from hell ends is handled in plan_early_new_travel() | |
| if where == "Crypt:3" then | |
| if game_status == "tomb" then | |
| travel_destination = "W" | |
| else | |
| travel_destination = "V" | |
| end | |
| end | |
| if where == "Tomb:3" and you.have_rune("golden") then | |
| travel_destination = "C" | |
| end | |
| return plan_continue_travel() | |
| end | |
| function plan_fly() | |
| if you.xl() >= 14 and intrinsic_flight() and not you.flying() then | |
| if cmp() >= 3 and not you.rooted() and not starving() then | |
| if use_ability("Fly") then | |
| say("FLYING.") | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| local did_ancestor_identity = false | |
| function plan_ancestor_identity() | |
| if you.god() ~= "Hepliaklqana" then | |
| return false | |
| end | |
| if not did_ancestor_identity then | |
| use_ability("Ancestor Identity","\b\b\b\b\b\b\b\b\b\b\b\b\b\b\belliptic\ra") | |
| did_ancestor_identity = true | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_ancestor_life() | |
| if you.god() ~= "Hepliaklqana" then | |
| return false | |
| end | |
| local ancestor_options = {"Knight", "Battlemage", "Hexer"} | |
| if use_ability("Ancestor Life: " .. ancestor_options[crawl.roll_dice(1,3)], "Y") then | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_sacrifice() | |
| if you.god() ~= "Ru" or starving() then | |
| return false | |
| end | |
| -- sacrifices that we won't do for now: words, drink, courage, durability, hand, resistance, purity, health, artifice (on DD) | |
| good_sacrifices = { | |
| "Sacrifice Artifice", -- 70 | |
| "Sacrifice Arcana", -- 25 | |
| "Sacrifice Love", -- 25 | |
| "Sacrifice Stealth", -- 15 | |
| "Sacrifice Essence", -- variable | |
| "Sacrifice Nimbleness", -- 30 | |
| "Sacrifice Skill", -- 35 | |
| "Sacrifice an Eye", -- 20 | |
| "Sacrifice Experience", -- 30 | |
| "Reject Sacrifices", | |
| } -- hack | |
| for _,sacrifice in ipairs(good_sacrifices) do | |
| if sacrifice == "Sacrifice Nimbleness" then | |
| for letter, abil in pairs(you.ability_table()) do | |
| if abil == sacrifice then | |
| you.train_skill("Fighting",1) | |
| say("INVOKING " .. sacrifice .. ".") | |
| magic("a" .. letter .. "YY") | |
| return true | |
| end | |
| end | |
| elseif use_ability(sacrifice, "YY") then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_find_upstairs() | |
| magic("X<\r") | |
| return true | |
| end | |
| function plan_gd1() | |
| expect_new_location = true | |
| magic("GD1\rY") | |
| return true | |
| end | |
| function plan_zig_go_to_stairs() | |
| if not where:find("Zig") then | |
| return false | |
| end | |
| if where:find(tostring(ZIG_DIVE)) then | |
| magic("X<\r") | |
| else | |
| magic("X>\r") | |
| end | |
| return true | |
| end | |
| function plan_find_downstairs() | |
| -- try to avoid branch entrances by going to a random > from them | |
| local feat = view.feature_at(0,0) | |
| if feat:find("enter_") or feat == "escape_hatch_down" then | |
| local i,j | |
| local c = "X" | |
| j = crawl.roll_dice(1,12) | |
| for i = 1,j do | |
| c = (c .. ">") | |
| end | |
| magic(c .. "\r") | |
| return true | |
| end | |
| magic("X>\r") | |
| return true | |
| end | |
| function set_waypoint() | |
| magic(control('w') .. waypoint_parity) | |
| did_waypoint = true | |
| return true | |
| end | |
| function clear_level_map(num) | |
| level_map[num] = {} | |
| for i = -100,100 do | |
| level_map[num][i] = {} | |
| end | |
| stair_dists[num] = {} | |
| end | |
| function update_level_map(num) | |
| local dx,dy = travel.waypoint_delta(num) | |
| local val,oldval | |
| local staircount = #stair_dists[num] | |
| local newcount = staircount | |
| local mapqueue = {} | |
| local distqueue = {} | |
| for j = 1,staircount do | |
| distqueue[j] = {} | |
| end | |
| for x = -LOS,LOS do | |
| for y = -LOS,LOS do | |
| table.insert(mapqueue, {x+dx,y+dy}) | |
| end | |
| end | |
| local first = 1 | |
| local last = #mapqueue | |
| local x,y | |
| local feat | |
| while first < last do | |
| if first % 1000 == 0 then | |
| coroutine.yield() | |
| end | |
| x = mapqueue[first][1] | |
| y = mapqueue[first][2] | |
| first = first + 1 | |
| feat = view.feature_at(x-dx,y-dy) | |
| if feat ~= "unseen" then | |
| if level_map[num][x][y] == nil then | |
| for ddx = -1,1 do | |
| for ddy = -1,1 do | |
| if ddx ~= 0 or ddy ~= 0 then | |
| last = last + 1 | |
| mapqueue[last] = {x+ddx,y+ddy} | |
| end | |
| end | |
| end | |
| end | |
| if travel.feature_traversable(feat) and not travel.feature_solid(feat) then | |
| if level_map[num][x][y] ~= "." then | |
| if feat_is_upstairs(feat) then | |
| newcount = #stair_dists[num]+1 | |
| stair_dists[num][newcount] = {} | |
| for i = -100,100 do | |
| stair_dists[num][newcount][i] = {} | |
| end | |
| stair_dists[num][newcount][x][y] = 0 | |
| distqueue[newcount] = {{x,y},} | |
| end | |
| for j = 1,staircount do | |
| oldval = stair_dists[num][j][x][y] | |
| for ddx = -1,1 do | |
| for ddy = -1,1 do | |
| if (ddx ~= 0 or ddy ~= 0) then | |
| val = stair_dists[num][j][x+ddx][y+ddy] | |
| if val ~= nil and (oldval == nil or oldval > val+1) then | |
| oldval = val+1 | |
| end | |
| end | |
| end | |
| end | |
| if stair_dists[num][j][x][y] ~= oldval then | |
| stair_dists[num][j][x][y] = oldval | |
| table.insert(distqueue[j],{x,y}) | |
| end | |
| end | |
| end | |
| level_map[num][x][y] = "." | |
| else | |
| level_map[num][x][y] = "#" | |
| end | |
| end | |
| end | |
| for j = 1,newcount do | |
| update_dist_map(stair_dists[num][j],distqueue[j]) | |
| end | |
| end | |
| function update_dist_map(dist_map,queue) | |
| local first = 1 | |
| local last = #queue | |
| local x,y,val | |
| while first <= last do | |
| if first % 300 == 0 then | |
| coroutine.yield() | |
| end | |
| x = queue[first][1] | |
| y = queue[first][2] | |
| first = first + 1 | |
| val = dist_map[x][y] + 1 | |
| for dx = -1,1 do | |
| for dy = -1,1 do | |
| if (dx ~= 0 or dy ~= 0) and level_map[waypoint_parity][x+dx][y+dy] == "." then | |
| oldval = dist_map[x+dx][y+dy] | |
| if oldval == nil or oldval > val then | |
| dist_map[x+dx][y+dy] = val | |
| last = last+1 | |
| queue[last] = {x+dx,y+dy} | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end | |
| function find_good_stairs() | |
| good_stair_list = { } | |
| if not is_waypointable(where) then | |
| return | |
| end | |
| local num = waypoint_parity | |
| local dx,dy = travel.waypoint_delta(num) | |
| local staircount = #(stair_dists[num]) | |
| local pdist,mdist,minmdist,speed_diff | |
| local pspeed = player_speed_num() | |
| for i = 1,staircount do | |
| pdist = stair_dists[num][i][dx][dy] | |
| if pdist == nil then | |
| pdist = 10000 | |
| end | |
| minmdist = 1000 | |
| for _,e in ipairs(enemy_list) do | |
| mdist = stair_dists[num][i][dx+e.x][dy+e.y] | |
| if mdist == nil then | |
| mdist = 10000 | |
| end | |
| speed_diff = mon_speed_num(e.m) - pspeed | |
| if speed_diff > 1 then | |
| mdist = mdist / 2 | |
| elseif speed_diff > 0 then | |
| mdist = mdist / 1.5 | |
| end | |
| if is_ranged(e.m) then | |
| mdist = mdist - 4 | |
| end | |
| if mdist < minmdist then | |
| minmdist = mdist | |
| end | |
| end | |
| if pdist < minmdist then | |
| table.insert(good_stair_list, i) | |
| end | |
| end | |
| end | |
| function stair_improvement(x,y) | |
| if not is_waypointable(where) then | |
| return 10000 | |
| end | |
| if x == 0 and y == 0 then | |
| if feat_is_upstairs(view.feature_at(0,0)) then | |
| return 0 | |
| else | |
| return 10000 | |
| end | |
| end | |
| local num = waypoint_parity | |
| local dx,dy = travel.waypoint_delta(num) | |
| local val | |
| local minval = 10000 | |
| for _,i in ipairs(good_stair_list) do | |
| val = stair_dists[num][i][dx+x][dy+y] | |
| if val < stair_dists[num][i][dx][dy] and val < minval then | |
| minval = val | |
| end | |
| end | |
| return minval | |
| end | |
| function set_stair_target(c) | |
| local x,y = vi_to_delta(c) | |
| local num = waypoint_parity | |
| local dx,dy = travel.waypoint_delta(num) | |
| local val | |
| local minval = 10000 | |
| local best_stair | |
| for _,i in ipairs(good_stair_list) do | |
| val = stair_dists[num][i][dx+x][dy+y] | |
| if val < stair_dists[num][i][dx][dy] and val < minval then | |
| minval = val | |
| best_stair = i | |
| end | |
| end | |
| target_stair = best_stair | |
| end | |
| function plan_continue_flee() | |
| if you.turns() >= last_flee_turn + 10 or not target_stair then | |
| return false | |
| end | |
| if danger or not reason_to_rest(90) or you.transform() == "tree" | |
| or count_bia(3) > 0 or count_sgd(3) > 0 or count_divine_warrior(3) > 0 | |
| or you.status("barbed spikes") or you.confused() or buffed() then | |
| return false | |
| end | |
| local num = waypoint_parity | |
| local dx,dy = travel.waypoint_delta(num) | |
| local val | |
| for x = -1,1 do | |
| for y = -1,1 do | |
| if is_traversable(x,y) and not is_solid(x,y) | |
| and not monster_in_way(x,y) and view.is_safe_square(x,y) | |
| and not view.withheld(x,y) then | |
| val = stair_dists[num][target_stair][dx+x][dy+y] | |
| if val and val < stair_dists[num][target_stair][dx][dy] then | |
| if SPAM then | |
| say("STILL FLEEEEING.") | |
| end | |
| magic(delta_to_vi(x,y) .. "YY") | |
| return true | |
| end | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_stuck() | |
| if starving() then | |
| return random_step("starving") | |
| end | |
| stuck_turns = stuck_turns + 1 | |
| if stuck_turns > QUIT_TURNS then | |
| magic(control('q') .. "yes\r") | |
| return true | |
| end | |
| return random_step("stuck") | |
| -- panic("Stuck!") | |
| end | |
| function plan_full_inventory_panic() | |
| if FULL_INVENTORY_PANIC and free_inventory_slots() == 0 then | |
| panic("Inventory is full!") | |
| else | |
| return false | |
| end | |
| end | |
| function unshafting() | |
| return (where_shafted_from and (where_shafted_from ~= you.where()) | |
| and not where:find("Slime")) | |
| end | |
| function plan_unshaft() | |
| if unshafting() and where ~= "Temple" then | |
| --say("Trying to unshaft to " .. where_shafted_from .. ".") | |
| expect_new_location = true | |
| magic("G<") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_not_coded() | |
| panic("Need to code this!") | |
| return true | |
| end | |
| function random_step(reason) | |
| if you.mesmerised() then | |
| say("Waiting to end mesmerise (" .. reason .. ").") | |
| magic("s") | |
| return true | |
| end | |
| local i,j | |
| local dx,dy | |
| local count = 0 | |
| for i = -1,1 do | |
| for j = -1,1 do | |
| if not (i == 0 and j == 0) and is_traversable(i,j) | |
| and not view.withheld(i,j) | |
| and not monster_in_way(i,j) then | |
| count = count + 1 | |
| if crawl.one_chance_in(count) then | |
| dx = i | |
| dy = j | |
| end | |
| end | |
| end | |
| end | |
| if count > 0 then | |
| say("Stepping randomly (" .. reason .. ").") | |
| magic(delta_to_vi(dx,dy) .. "YY") | |
| return true | |
| else | |
| say("Standing still (" .. reason .. ").") | |
| magic("s") | |
| return true | |
| end | |
| -- return false | |
| end | |
| function plan_disturbance_random_step() | |
| if crawl.messages(5):find("There is a strange disturbance nearby!") then | |
| return random_step("disturbance") | |
| end | |
| return false | |
| end | |
| function plan_wait() | |
| rest() | |
| return true | |
| end | |
| function plan_flail_at_invis() | |
| if options.autopick_on then | |
| invisi_count = 0 | |
| invisi_sigmund = false | |
| return false | |
| end | |
| if invisi_count > 100 then | |
| say("Invisible monster not found???") | |
| invisi_count = 0 | |
| invisi_sigmund = false | |
| magic(control('a')) | |
| return true | |
| end | |
| invisi_count = invisi_count + 1 | |
| local x,y | |
| for x = -1,1 do | |
| for y = -1,1 do | |
| if supdist(x,y) > 0 and view.invisible_monster(x,y) then | |
| magic(control(delta_to_vi(x,y))) | |
| return true | |
| end | |
| end | |
| end | |
| if invisi_sigmund and (sigmund_dx ~= 0 or sigmund_dy ~= 0) then | |
| x = sigmund_dx | |
| y = sigmund_dy | |
| if adjacent(x,y) and is_traversable(x,y) then | |
| magic(control(delta_to_vi(x,y))) | |
| return true | |
| elseif x == 0 and is_traversable(0,sign(y)) then | |
| magic(delta_to_vi(0,sign(y))) | |
| return true | |
| elseif y == 0 and is_traversable(sign(x),0) then | |
| magic(delta_to_vi(sign(x),0)) | |
| return true | |
| end | |
| end | |
| local success = false | |
| local tries = 0 | |
| while not success and tries < 100 do | |
| x = -1 + crawl.random2(3) | |
| y = -1 + crawl.random2(3) | |
| tries = tries + 1 | |
| if (x ~= 0 or y ~= 0) and is_traversable(x,y) | |
| and view.feature_at(x,y) ~= "closed_door" | |
| and view.feature_at(x,y) ~= "runed_door" then | |
| success = true | |
| end | |
| end | |
| if tries >= 100 then | |
| magic("s") | |
| else | |
| magic(control(delta_to_vi(x,y))) | |
| end | |
| return true | |
| end | |
| function plan_cure_confusion() | |
| if you.confused() and (danger or not options.autopick_on) then | |
| if view.cloud_at(0,0) == "noxious fumes" and not meph_immune() then | |
| if you.god() == "Beogh" then | |
| magic("s") -- avoid Beogh penance | |
| return true | |
| end | |
| return false | |
| end | |
| if drink_by_name("curing") then | |
| say("(to cure confusion)") | |
| return true | |
| end | |
| if can_purification() then | |
| purification() | |
| return true | |
| end | |
| if you.god() == "Beogh" then | |
| magic("s") -- avoid Beogh penance | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_cure_starving() | |
| if you.hunger_name() == "fainting" | |
| or you.hunger_name() == "starving" then | |
| return eat_permafood(true) | |
| end | |
| return false | |
| end | |
| -- curing poison/confusion with purification is handled elsewhere | |
| function plan_special_purification() | |
| if not can_purification() then | |
| return false | |
| end | |
| if you.slowed() or you.petrifying() then | |
| purification() | |
| return true | |
| end | |
| local str, mstr = you.strength() | |
| local int, mint = you.intelligence() | |
| local dex, mdex = you.dexterity() | |
| if str < mstr and (str < mstr-5 or str < 3) | |
| or int < mint and int < 3 | |
| or dex < mdex and (dex < mdex-8 or dex < 3) then | |
| purification() | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_teleport() | |
| if can_teleport() and want_to_teleport() then | |
| -- return false | |
| return teleport() | |
| end | |
| return false | |
| end | |
| function plan_orbrun_teleport() | |
| if can_teleport() and want_to_orbrun_teleport() then | |
| return teleport() | |
| end | |
| return false | |
| end | |
| function plan_tomb_use_hatch() | |
| if (where == "Tomb:2" and not you.have_rune("golden") | |
| or where == "Tomb:1") | |
| and view.feature_at(0,0) == "escape_hatch_down" then | |
| expect_new_location = true | |
| prev_hatch_dist = 1000 | |
| magic(">") | |
| return true | |
| end | |
| if (where == "Tomb:3" and you.have_rune("golden") | |
| or where == "Tomb:2") | |
| and view.feature_at(0,0) == "escape_hatch_up" then | |
| expect_new_location = true | |
| prev_hatch_dist = 1000 | |
| magic("<") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_tomb_go_to_final_hatch() | |
| if where == "Tomb:2" and not you.have_rune("golden") | |
| and view.feature_at(0,0) ~= "escape_hatch_down" then | |
| magic("X>\r") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_tomb_go_to_hatch() | |
| if where == "Tomb:3" then | |
| if you.have_rune("golden") | |
| and view.feature_at(0,0) ~= "escape_hatch_up" then | |
| magic("X<\r") | |
| return true | |
| end | |
| elseif where == "Tomb:2" then | |
| if not you.have_rune("golden") | |
| and view.feature_at(0,0) == "escape_hatch_down" then | |
| return false | |
| end | |
| if view.feature_at(0,0) == "escape_hatch_up" then | |
| local x,y = travel.waypoint_delta(waypoint_parity) | |
| local new_hatch_dist = supdist(x,y) | |
| if new_hatch_dist >= prev_hatch_dist | |
| and (x ~= prev_hatch_x or y ~= prev_hatch_y) then | |
| return false | |
| end | |
| prev_hatch_dist = new_hatch_dist | |
| prev_hatch_x = x | |
| prev_hatch_y = y | |
| end | |
| magic("X<\r") | |
| return true | |
| elseif where == "Tomb:1" then | |
| if view.feature_at(0,0) == "escape_hatch_down" then | |
| local x,y = travel.waypoint_delta(waypoint_parity) | |
| local new_hatch_dist = supdist(x,y) | |
| if new_hatch_dist >= prev_hatch_dist | |
| and (x ~= prev_hatch_x or y ~= prev_hatch_y) then | |
| return false | |
| end | |
| prev_hatch_dist = new_hatch_dist | |
| prev_hatch_x = x | |
| prev_hatch_y = y | |
| end | |
| magic("X>\r") | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_stuck_clear_exclusions() | |
| local n = clear_exclusion_count[where] or 0 | |
| if n > 20 then | |
| return false | |
| end | |
| clear_exclusion_count[where] = n+1 | |
| magic("X" .. control('e')) | |
| return true | |
| end | |
| function plan_swamp_clear_exclusions() | |
| if where ~= "Swamp:4" then | |
| return false | |
| end | |
| magic("X" .. control('e')) | |
| return true | |
| end | |
| function plan_swamp_go_to_rune() | |
| if where ~= "Swamp:4" or you.have_rune("decaying") then | |
| return false | |
| end | |
| if last_swamp_fail_count == c_persist.plan_fail_count.try_swamp_go_to_rune then | |
| swamp_rune_reachable = true | |
| end | |
| last_swamp_fail_count = c_persist.plan_fail_count.try_swamp_go_to_rune | |
| magicfind("@decaying rune") | |
| return true | |
| end | |
| function plan_swamp_clouds_hack() | |
| if where ~= "Swamp:4" then | |
| return false | |
| end | |
| if you.have_rune("decaying") and can_teleport() and teleport() then | |
| return true | |
| end | |
| if swamp_rune_reachable then | |
| say("Waiting for clouds to move.") | |
| magic("s") | |
| return true | |
| end | |
| local x,y | |
| local bestx,besty | |
| local dist | |
| local bestdist = 11 | |
| for x = -1,1 do | |
| for y = -1,1 do | |
| if supdist(x,y) > 0 and view.is_safe_square(x,y) | |
| and not view.withheld(x,y) and not monster_in_way(x,y) then | |
| dist = 11 | |
| for x2 = -LOS,LOS do | |
| for y2 = -LOS,LOS do | |
| if (view.cloud_at(x2,y2) == "freezing vapour" | |
| or view.cloud_at(x2,y2) == "foul pestilence") | |
| and you.see_cell_no_trans(x2,y2) | |
| and (you.god() ~= "Qazlal" or not view.is_safe_square(x2,y2)) then | |
| if supdist(x-x2,y-y2) < dist then | |
| dist = supdist(x-x2,y-y2) | |
| end | |
| end | |
| end | |
| end | |
| if dist < bestdist then | |
| bestx = x | |
| besty = y | |
| bestdist = dist | |
| end | |
| end | |
| end | |
| end | |
| if bestdist < 11 then | |
| magic(delta_to_vi(bestx,besty) .. "Y") | |
| return true | |
| end | |
| for x = -LOS,LOS do | |
| for y = -LOS,LOS do | |
| if (view.cloud_at(x,y) == "freezing vapour" | |
| or view.cloud_at(x,y) == "foul pestilence") | |
| and you.see_cell_no_trans(x,y) then | |
| return random_step("Swamp:4") | |
| end | |
| end | |
| end | |
| return plan_stuck_teleport() | |
| end | |
| function plan_dig_grate() | |
| local grate_mon_list | |
| local grate_count_needed = 3 | |
| if where:find("Zot") then | |
| grate_mon_list = {"draconian stormcaller", "draconian scorcher"} | |
| elseif where == "Depths:5" then | |
| grate_mon_list = {"draconian stormcaller", "draconian scorcher", | |
| "angel", "daeva", "lich", "eye"} | |
| elseif where:find("Depths") then | |
| grate_mon_list = {"angel", "daeva", "lich", "eye"} | |
| elseif where == "Pan" or where == "Geh:7" then | |
| grate_mon_list = {"smoke demon"} | |
| grate_count_needed = 1 | |
| elseif where:find("Zig") then | |
| grate_mon_list = {""} | |
| grate_count_needed = 1 | |
| else | |
| return false | |
| end | |
| local e | |
| for _,e in ipairs(enemy_list) do | |
| local name = e.m:name() | |
| if contains_string_in(name,grate_mon_list) | |
| and not will_tab(0,0,e.x,e.y,tabbable_square) then | |
| local grate_count = 0 | |
| local dx,dy | |
| local closest_grate = 20 | |
| local gx,gy,cgx,cgy | |
| for dx = -1,1 do | |
| for dy = -1,1 do | |
| gx = e.x + dx | |
| gy = e.y + dy | |
| if supdist(gx,gy) <= LOS and view.feature_at(gx,gy) == "iron_grate" then | |
| grate_count = grate_count + 1 | |
| if abs(gx) + abs(gy) < closest_grate and you.see_cell_solid_see(gx,gy) then | |
| cgx = gx | |
| cgy = gy | |
| closest_grate = abs(gx) + abs(gy) | |
| end | |
| end | |
| end | |
| end | |
| if grate_count >= grate_count_needed and closest_grate < 20 then | |
| local c = find_item("wand", "digging") | |
| if c and can_zap() then | |
| say("ZAPPING " .. item(c).name() .. ".") | |
| magic("V" .. letter(c) .. "r" .. vector_move(cgx,cgy) .. "\r") | |
| return true | |
| end | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| function plan_stuck_dig_grate() | |
| local dx,dy | |
| local closest_grate = 20 | |
| local cx,cy | |
| for dx = -LOS,LOS do | |
| for dy = -LOS,LOS do | |
| if view.feature_at(dx,dy) == "iron_grate" then | |
| if abs(dx) + abs(dy) < closest_grate and you.see_cell_solid_see(dx,dy) then | |
| cx = dx | |
| cy = dy | |
| closest_grate = abs(dx) + abs(dy) | |
| end | |
| end | |
| end | |
| end | |
| if closest_grate < 20 then | |
| local c = find_item("wand", "digging") | |
| if c and can_zap() then | |
| say("ZAPPING " .. item(c).name() .. ".") | |
| magic("V" .. letter(c) .. "r" .. vector_move(cx,cy) .. "\r") | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function plan_stuck_forget_map() | |
| if not cloudy and not danger | |
| and (where == "Slime:5" and not you.have_rune("slimy") | |
| or where == "Geh:7" and not you.have_rune("obsidian")) then | |
| magic("X" .. control('f')) | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_stuck_cloudy() | |
| if cloudy and not hp_is_low(50) and not you.mesmerised() then | |
| return random_step("cloudy") | |
| end | |
| return false | |
| end | |
| function plan_stuck_teleport() | |
| if can_teleport() then | |
| return teleport() | |
| end | |
| return false | |
| end | |
| function read(c) | |
| if not can_read() then | |
| return false | |
| end | |
| say("READING " .. item(c).name() .. ".") | |
| magic("r" .. letter(c)) | |
| return true | |
| end | |
| function read2(c,etc) | |
| if not can_read() then | |
| return false | |
| end | |
| local int, mint = you.intelligence() | |
| if int <= 0 then | |
| -- failing to read a scroll due to intzero can make qw unhappy | |
| return false | |
| end | |
| say("READING " .. item(c).name() .. ".") | |
| magic("r" .. letter(c) .. etc) | |
| return true | |
| end | |
| function drink(c) | |
| if not can_drink() then | |
| return false | |
| end | |
| say("DRINKING " .. item(c).name() .. ".") | |
| magic("q" .. letter(c)) | |
| return true | |
| end | |
| function selfzap(c) | |
| if not can_zap() then | |
| return false | |
| end | |
| say("ZAPPING " .. item(c).name() .. ".") | |
| magic("V" .. letter(c) .. ".") | |
| return true | |
| end | |
| function read_by_name(name) | |
| local c = find_item("scroll", name) | |
| if (c and read(c)) then | |
| return true | |
| end | |
| return false | |
| end | |
| function drink_by_name(name) | |
| local c = find_item("potion", name) | |
| if (c and drink(c)) then | |
| return true | |
| end | |
| return false | |
| end | |
| function selfzap_by_name(name) | |
| local c = find_item("wand", name) | |
| if (c and selfzap(c)) then | |
| return true | |
| end | |
| return false | |
| end | |
| function teleport() | |
| if read_by_name("teleportation") then | |
| dd_hw_meter = 0 | |
| return true | |
| end | |
| return false | |
| end | |
| function plan_cure_poison() | |
| if you.poison_survival() <= 1 and you.poisoned() | |
| or you.race() == "Deep Dwarf" and you.poison_survival() <= chp() - 10 then | |
| if drink_by_name("curing") then | |
| say("(to cure poison)") | |
| return true | |
| end | |
| end | |
| if you.poison_survival() <= 1 and you.poisoned() then | |
| if can_hand() then | |
| hand() | |
| return true | |
| end | |
| if can_purification() then | |
| purification() | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function move_towards(dx, dy) | |
| if you.transform() == "tree" or you.transform() == "fungus" | |
| or you.confused() and | |
| (count_bia(1) > 0 or count_sgd(1) > 0 or count_divine_warrior(1) > 0) then | |
| magic("s") | |
| return true | |
| end | |
| local move = nil | |
| if abs(dx) > abs(dy) then | |
| if abs(dy) == 1 then move = try_move(sign(dx), 0) end | |
| if move == nil then move = try_move(sign(dx), sign(dy)) end | |
| if move == nil then move = try_move(sign(dx), 0) end | |
| if move == nil and abs(dx) > abs(dy)+1 then | |
| move = try_move(sign(dx), 1) end | |
| if move == nil and abs(dx) > abs(dy)+1 then | |
| move = try_move(sign(dx), -1) end | |
| if move == nil then move = try_move(0, sign(dy)) end | |
| elseif abs(dx) == abs(dy) then | |
| move = try_move(sign(dx), sign(dy)) | |
| if move == nil then move = try_move(sign(dx), 0) end | |
| if move == nil then move = try_move(0, sign(dy)) end | |
| else | |
| if abs(dx) == 1 then move = try_move(0, sign(dy)) end | |
| if move == nil then move = try_move(sign(dx), sign(dy)) end | |
| if move == nil then move = try_move(0, sign(dy)) end | |
| if move == nil and abs(dy) > abs(dx)+1 then | |
| move = try_move(1, sign(dy)) end | |
| if move == nil and abs(dy) > abs(dx)+1 then | |
| move = try_move(-1, sign(dy)) end | |
| if move == nil then move = try_move(sign(dx), 0) end | |
| end | |
| if move == nil or move_count >= 10 then | |
| add_ignore(dx,dy) | |
| table.insert(failed_move, 20*dx+dy) | |
| return false | |
| else | |
| if (abs(dx) > 1 or abs(dy) > 1) and not lair_step_mode | |
| and view.feature_at(dx,dy) ~= "closed_door" then | |
| did_move = true | |
| if monster_array[dx][dy] or did_move_towards_monster > 0 then | |
| local move_x, move_y = vi_to_delta(move) | |
| target_memory_x = dx - move_x | |
| target_memory_y = dy - move_y | |
| did_move_towards_monster = 2 | |
| end | |
| end | |
| if lair_step_mode then | |
| local move_x, move_y = vi_to_delta(move) | |
| if view.feature_at(move_x, move_y) == "shallow_water" then | |
| return false | |
| end | |
| end | |
| magic(move .. "Y") | |
| return true | |
| end | |
| end | |
| function plan_continue_tab() | |
| if did_move_towards_monster == 0 then | |
| return false | |
| end | |
| if supdist(target_memory_x, target_memory_y) == 0 then | |
| return false | |
| end | |
| if not options.autopick_on then | |
| return false | |
| end | |
| return move_towards(target_memory_x, target_memory_y) | |
| end | |
| function add_ignore(dx,dy) | |
| m = monster_array[dx][dy] | |
| if not m then | |
| return | |
| end | |
| name = m:name() | |
| if not util.contains(ignore_list, name) then | |
| table.insert(ignore_list, name) | |
| crawl.setopt("runrest_ignore_monster ^= " .. name .. ":1") | |
| --say("Ignoring " .. name .. ".") | |
| end | |
| end | |
| function remove_ignore(dx,dy) | |
| m = monster_array[dx][dy] | |
| name = m:name() | |
| for i,mname in ipairs(ignore_list) do | |
| if mname == name then | |
| table.remove(ignore_list, i) | |
| crawl.setopt("runrest_ignore_monster -= " .. name .. ":1") | |
| --say("Unignoring " .. name .. ".") | |
| return | |
| end | |
| end | |
| end | |
| function clear_ignores() | |
| local size = #ignore_list | |
| local mname | |
| local i | |
| if size > 0 then | |
| for i = 1, size do | |
| mname = table.remove(ignore_list) | |
| crawl.setopt("runrest_ignore_monster -= " .. mname .. ":1") | |
| --say("Unignoring " .. mname .. ".") | |
| end | |
| end | |
| end | |
| -- this gets stuck if netted, confused, etc | |
| function attack_reach(x, y) | |
| magic('vr' .. vector_move(x, y) .. '.') | |
| end | |
| function attack_melee(x, y) | |
| if you.confused() then | |
| if count_bia(1) > 0 or count_sgd(1) > 0 or count_divine_warrior(1) > 0 then | |
| magic("s") | |
| return | |
| elseif you.transform() == "tree" then | |
| magic(control(delta_to_vi(x, y)) .. "Y") | |
| return | |
| end | |
| end | |
| if monster_array[x][y]:attitude() == ATT_NEUTRAL then | |
| if you.god() == "the Shining One" or you.god() == "Elyvilon" | |
| or you.god() == "Zin" then | |
| magic("s") | |
| else | |
| magic(control(delta_to_vi(x, y))) | |
| end | |
| end | |
| magic(delta_to_vi(x, y) .. "Y") | |
| end | |
| function make_attack(x, y, info) | |
| if info.attack_type == 2 then attack_melee(x, y) | |
| elseif info.attack_type == 1 then attack_reach(x, y) | |
| else | |
| return move_towards(x, y) | |
| end | |
| return true | |
| end | |
| function use_ability(name, extra, mute) | |
| for letter, abil in pairs(you.ability_table()) do | |
| if abil == name then | |
| if not mute or SPAM then | |
| say("INVOKING " .. name .. ".") | |
| end | |
| magic("a" .. letter .. (extra or "")) | |
| return true | |
| end | |
| end | |
| end | |
| function note(x) | |
| crawl.take_note(you.turns() .. " ||| " .. x) | |
| end | |
| function say(x) | |
| crawl.mpr(you.turns() .. " ||| " .. x) | |
| note(x) | |
| end | |
| -- these few functions are called directly from ready() | |
| function record_portal_found(por) | |
| if not util.contains(c_persist.portals_found, por) then | |
| say("Found " .. por .. ".") | |
| table.insert(c_persist.portals_found, por) | |
| end | |
| end | |
| function check_messages() | |
| local recent_messages = crawl.messages(20) | |
| local very_recent_messages = crawl.messages(5) | |
| if very_recent_messages:find("Sigmund flickers and vanishes") then | |
| invisi_sigmund = true | |
| end | |
| if very_recent_messages:find("Your surroundings suddenly seem different") then | |
| invisi_sigmund = false | |
| end | |
| str1 = "Your pager goes off" | |
| str2 = "qwqwqw" | |
| if recent_messages:find(str1) then | |
| a = recent_messages:reverse():find(str1:reverse()) | |
| b = recent_messages:reverse():find(str2:reverse()) | |
| if (not b) or a < b then | |
| have_message = true | |
| end | |
| end | |
| if in_portal() then | |
| return false | |
| end | |
| if recent_messages:find("Found") then | |
| for _, value in ipairs(portal_data) do | |
| if recent_messages:find(value[2]) then | |
| record_portal_found(value[1]) | |
| end | |
| end | |
| end | |
| end | |
| function plan_message() | |
| if read_message then | |
| crawl.setopt("clear_messages = false") | |
| magic("_") | |
| read_message = false | |
| else | |
| crawl.setopt("clear_messages = true") | |
| magic(":qwqwqw\r") | |
| read_message = true | |
| have_message = false | |
| crawl.delay(2500) | |
| end | |
| end | |
| ---------------------------------------- | |
| -- cascading plans: this is the bot's flowchart for using the above plans | |
| function cascade(plans) | |
| local plan_turns = {} | |
| local plan_result = {} | |
| return function () | |
| for i, plandata in ipairs(plans) do | |
| plan = plandata[1] | |
| if you.turns() ~= plan_turns[plan] or plan_result[plan] == nil then | |
| --say(plandata[2]) | |
| result = plan() | |
| if not automatic then | |
| return true | |
| end | |
| plan_turns[plan] = you.turns() | |
| plan_result[plan] = result | |
| if result == nil or result == true then | |
| if DELAYED and result == true then | |
| crawl.delay(next_delay) | |
| end | |
| next_delay = DELAY_TIME | |
| return nil | |
| end | |
| elseif plan_turns[plan] and plan_result[plan] == true then | |
| if not plandata[2]:find("^try") then | |
| panic(plandata[2] .. " failed despite returning true.") | |
| end | |
| if not c_persist.plan_fail_count[plandata[2]] then | |
| c_persist.plan_fail_count[plandata[2]] = 0 | |
| end | |
| c_persist.plan_fail_count[plandata[2]] = c_persist.plan_fail_count[plandata[2]] + 1 | |
| end | |
| end | |
| return false | |
| end | |
| end | |
| -- any plan that might not know whether or not it successfully took an action | |
| -- (e.g. autoexplore) should prepend "try_" to its text | |
| plan_pre_explore = cascade { | |
| {plan_fly, "fly"}, | |
| {plan_ancestor_life, "ancestor_life"}, | |
| {plan_sacrifice, "sacrifice"}, | |
| {plan_bless_weapon, "bless_weapon"}, | |
| {plan_upgrade_weapon, "upgrade_weapon"}, | |
| {plan_maybe_upgrade_armour, "maybe_upgrade_armour"}, | |
| {plan_use_good_consumables, "use_good_consumables"}, | |
| } -- hack | |
| plan_pre_explore2 = cascade { | |
| {plan_disturbance_random_step, "disturbance_random_step"}, | |
| {plan_upgrade_armour, "upgrade_armour"}, | |
| {plan_upgrade_amulet, "upgrade_amulet"}, | |
| {plan_upgrade_rings, "upgrade_rings"}, | |
| {plan_read_id, "try_read_id"}, | |
| {plan_quaff_id, "quaff_id"}, | |
| {plan_use_id_scrolls, "use_id_scrolls"}, | |
| {plan_drop_other_items, "drop_other_items"}, | |
| {plan_full_inventory_panic, "full_inventory_panic"}, | |
| } -- hack | |
| plan_emergency = cascade { | |
| {plan_cure_starving, "cure_starving"}, | |
| {plan_special_purification, "special_purification"}, | |
| {plan_cure_confusion, "cure_confusion"}, | |
| {plan_coward_step, "coward_step"}, | |
| {plan_flee_step, "flee_step"}, | |
| {plan_remove_terrible_jewellery, "remove_terrible_jewellery"}, | |
| {plan_teleport, "teleport"}, | |
| {plan_cure_bad_poison, "cure_bad_poison"}, | |
| {plan_cancellation, "cancellation"}, | |
| {plan_drain_life, "drain_life"}, | |
| {plan_heal_wounds, "heal_wounds"}, | |
| {plan_cloud_step, "cloud_step"}, | |
| {plan_hand, "hand"}, | |
| {plan_haste, "haste"}, | |
| {plan_resistance, "resistance"}, | |
| {plan_heroism, "heroism"}, | |
| {plan_bia, "bia"}, | |
| {plan_sgd, "sgd"}, | |
| {plan_divine_warrior, "divine_warrior"}, | |
| {plan_apocalypse, "try_apocalypse"}, | |
| {plan_slouch, "try_slouch"}, | |
| {plan_hydra_destruction, "try_hydra_destruction"}, | |
| {plan_grand_finale, "grand_finale"}, | |
| {plan_wield_weapon, "wield_weapon"}, | |
| {plan_swap_weapon, "swap_weapon"}, | |
| {plan_water_step, "water_step"}, | |
| {plan_zig_fog, "zig_fog"}, | |
| {plan_finesse, "finesse"}, | |
| {plan_dig_grate, "try_dig_grate"}, | |
| {plan_might, "might"}, | |
| {plan_blinking, "blinking"}, | |
| {plan_berserk, "berserk"}, | |
| {plan_continue_flee, "continue_flee"}, | |
| {plan_other_step, "other_step"}, | |
| } -- hack | |
| plan_orbrun_emergency = cascade { | |
| {plan_cure_starving, "cure_starving"}, | |
| {plan_special_purification, "special_purification"}, | |
| {plan_cure_confusion, "cure_confusion"}, | |
| {plan_orbrun_teleport, "orbrun_teleport"}, | |
| {plan_orbrun_heal_wounds, "orbrun_heal_wounds"}, | |
| {plan_orbrun_finesse, "orbrun_finesse"}, | |
| {plan_orbrun_haste, "orbrun_haste"}, | |
| {plan_orbrun_heroism, "orbrun_heroism"}, | |
| {plan_orbrun_divine_warrior, "orbrun_divine_warrior"}, | |
| {plan_hand, "hand"}, | |
| {plan_resistance, "resistance"}, | |
| {plan_wield_weapon, "wield_weapon"}, | |
| {plan_orbrun_might, "orbrun_might"}, | |
| } -- hack | |
| plan_eatrest = cascade { | |
| {plan_eat_chunk, "eat_chunk"}, | |
| {plan_eat_permafood, "eat_permafood"}, | |
| {plan_dd_hand_for_healing, "dd_hand_for_healing"}, | |
| --{plan_easy_rest, "try_easy_rest"}, | |
| {plan_rest, "rest"}, | |
| {plan_handle_corpses, "handle_corpses"}, | |
| {plan_find_corpses, "try_find_corpses"}, | |
| {plan_eat_anyway, "eat_anyway"}, | |
| } -- hack | |
| plan_abyss_eatrest = cascade { | |
| {plan_eat_chunk, "eat_chunk"}, | |
| {plan_eat_permafood, "eat_permafood"}, | |
| {plan_go_to_abyss_exit, "try_go_to_abyss_exit"}, | |
| {plan_abyss_hand, "abyss_hand"}, | |
| {plan_abyss_rest, "rest"}, | |
| {plan_handle_corpses, "handle_corpses"}, | |
| {plan_find_corpses, "try_find_corpses"}, | |
| {plan_eat_anyway, "eat_anyway"}, | |
| {plan_go_down_abyss, "go_down_abyss"}, | |
| {plan_go_to_abyss_downstairs, "try_go_to_abyss_downstairs"}, | |
| } -- hack | |
| plan_orbrun_eatrest = cascade { | |
| {plan_orbrun_eat_permafood, "orbrun_eat_permafood"}, | |
| {plan_orbrun_rest, "orbrun_rest"}, | |
| {plan_orbrun_hand, "orbrun_hand"}, | |
| } -- hack | |
| plan_explore = cascade { | |
| {plan_unshaft, "try_unshaft"}, | |
| {plan_continue_travel, "try_continue_travel"}, | |
| {plan_enter_portal, "enter_portal"}, | |
| {plan_go_to_portal_entrance, "try_go_to_portal_entrance"}, | |
| {plan_enter_abyss, "enter_abyss"}, | |
| {plan_go_to_abyss_portal, "try_go_to_abyss_portal"}, | |
| {plan_enter_pan, "enter_pan"}, | |
| {plan_go_to_pan_portal, "try_go_to_pan_portal"}, | |
| {plan_enter_zig, "enter_zig"}, | |
| {plan_go_to_zig, "try_go_to_zig"}, | |
| {plan_zig_dig, "zig_dig"}, | |
| {plan_go_to_zig_dig, "try_go_to_zig_dig"}, | |
| {plan_dive, "try_dive"}, | |
| {plan_dive_pan, "dive_pan"}, | |
| {plan_dive_go_to_pan_downstairs, "try_dive_go_to_pan_downstairs"}, | |
| {plan_early_new_travel, "try_early_new_travel"}, | |
| {plan_autoexplore, "try_autoexplore"}, | |
| } -- hack | |
| plan_explore2 = cascade { | |
| {plan_zig_leave_level, "zig_leave_level"}, | |
| {plan_zig_go_to_stairs, "try_zig_go_to_stairs"}, | |
| {plan_exit_portal, "exit_portal"}, | |
| {plan_go_to_portal_exit, "try_go_to_portal_exit"}, | |
| {plan_open_runed_doors, "open_runed_doors"}, | |
| {plan_exit_pan, "exit_pan"}, | |
| {plan_go_to_pan_exit, "try_go_to_pan_exit"}, | |
| {plan_go_down_pan, "try_go_down_pan"}, | |
| {plan_go_to_pan_downstairs, "try_go_to_pan_downstairs"}, | |
| {plan_enter_branch, "try_enter_branch"}, | |
| {plan_shopping_spree, "try_shopping_spree"}, | |
| {plan_simple_go_down, "try_simple_go_down"}, | |
| {plan_new_travel, "try_new_travel"}, | |
| } -- hack | |
| plan_move = cascade { | |
| {plan_ancestor_identity, "try_ancestor_identity"}, | |
| {plan_join_beogh, "join_beogh"}, | |
| {plan_shop, "shop"}, | |
| {plan_stairdance_up, "stairdance_up"}, | |
| {plan_emergency, "emergency"}, | |
| {plan_recall, "recall"}, | |
| {plan_recall_ancestor, "try_recall_ancestor"}, | |
| {plan_recite, "try_recite"}, | |
| {plan_wait_for_melee, "wait_for_melee"}, | |
| {plan_starting_spell, "try_starting_spell"}, | |
| {plan_wait_spit, "try_wait_spit"}, | |
| {plan_wait_throw, "try_wait_throw"}, | |
| {plan_wait_wait, "wait_wait"}, | |
| {plan_attack, "attack"}, | |
| {plan_cure_poison, "cure_poison"}, | |
| {plan_flail_at_invis, "try_flail_at_invis"}, | |
| {plan_burn_spellbooks, "try_burn_spellbooks"}, | |
| {plan_eatrest, "eatrest"}, | |
| {plan_pre_explore, "pre_explore"}, | |
| {plan_step_towards_lair, "step_towards_lair"}, | |
| {plan_continue_tab, "continue_tab"}, | |
| {plan_abandon_god, "abandon_god"}, | |
| {plan_unwield_weapon, "unwield_weapon"}, | |
| {plan_convert, "convert"}, | |
| {plan_join_god, "try_join_god"}, -- bug with faded altar not taking time | |
| {plan_find_conversion_altar, "try_find_conversion_altar"}, | |
| {plan_find_altar, "try_find_altar"}, | |
| {plan_go_to_temple, "try_go_to_temple"}, | |
| {plan_explore, "explore"}, | |
| {plan_pre_explore2, "pre_explore2"}, | |
| {plan_explore2, "explore2"}, | |
| {plan_tomb_go_to_final_hatch, "try_tomb_go_to_final_hatch"}, | |
| {plan_tomb_go_to_hatch, "try_tomb_go_to_hatch"}, | |
| {plan_tomb_use_hatch, "tomb_use_hatch"}, | |
| {plan_swamp_clear_exclusions, "try_swamp_clear_exclusions"}, | |
| {plan_swamp_go_to_rune, "try_swamp_go_to_rune"}, | |
| {plan_swamp_clouds_hack, "swamp_clouds_hack"}, | |
| {plan_stuck_clear_exclusions, "try_stuck_clear_exclusions"}, | |
| {plan_stuck_dig_grate, "try_stuck_dig_grate"}, | |
| {plan_stuck_cloudy, "stuck_cloudy"}, | |
| {plan_stuck_forget_map, "try_stuck_forget_map"}, | |
| {plan_stuck_teleport, "stuck_teleport"}, | |
| {plan_stuck, "stuck"}, | |
| } -- hack | |
| plan_orbrun_move = cascade { | |
| {plan_orbrun_emergency, "orbrun_emergency"}, | |
| {plan_recall, "recall"}, | |
| {plan_recall_ancestor, "try_recall_ancestor"}, | |
| {plan_attack, "attack"}, | |
| {plan_cure_poison, "cure_poison"}, | |
| {plan_orbrun_eatrest, "orbrun_eatrest"}, | |
| {plan_go_up, "go_up"}, | |
| {plan_fly, "fly"}, | |
| {plan_use_good_consumables, "use_good_consumables"}, | |
| {plan_find_upstairs, "try_find_upstairs"}, | |
| {plan_disturbance_random_step, "disturbance_random_step"}, | |
| {plan_stuck_clear_exclusions, "try_stuck_clear_exclusions"}, | |
| {plan_stuck_cloudy, "stuck_cloudy"}, | |
| {plan_stuck_teleport, "stuck_teleport"}, | |
| {plan_autoexplore, "try_autoexplore"}, | |
| {plan_gd1, "try_gd1"}, | |
| {plan_stuck, "stuck"}, | |
| } -- hack | |
| plan_abyss_move = cascade { | |
| {plan_lugonu_exit_abyss, "lugonu_exit_abyss"}, | |
| {plan_exit_abyss, "exit_abyss"}, | |
| {plan_emergency, "emergency"}, | |
| {plan_recall_ancestor, "try_recall_ancestor"}, | |
| {plan_recite, "try_recite"}, | |
| {plan_attack, "attack"}, | |
| {plan_cure_poison, "cure_poison"}, | |
| {plan_flail_at_invis, "try_flail_at_invis"}, | |
| {plan_abyss_eatrest, "abyss_eatrest"}, | |
| {plan_pre_explore, "pre_explore"}, | |
| {plan_autoexplore, "try_autoexplore"}, | |
| {plan_pre_explore2, "pre_explore2"}, | |
| {plan_stuck_cloudy, "stuck_cloudy"}, | |
| {plan_wait, "wait"}, | |
| } -- hack | |
| ---------------------------------------- | |
| -- skill selection | |
| local skill_list = {"Fighting","Short Blades","Long Blades","Axes","Maces & Flails", | |
| "Polearms","Staves","Unarmed Combat","Bows","Crossbows", | |
| "Throwing","Slings","Armour","Dodging","Shields", | |
| "Invocations","Evocations","Stealth","Spellcasting", | |
| "Conjurations","Hexes","Charms","Summonings","Necromancy", | |
| "Translocations","Transmutations","Fire Magic","Ice Magic", | |
| "Air Magic","Earth Magic","Poison Magic"} | |
| function choose_single_skill(sk) | |
| you.train_skill(sk, 1) | |
| for _,sk2 in ipairs(skill_list) do | |
| if sk ~= sk2 then | |
| you.train_skill(sk2, 0) | |
| end | |
| end | |
| end | |
| function skill_value(sk) | |
| if you.god() == "Okawaru" and sk ~= "Fighting" and sk ~= "Invocations" and you.base_skill(sk) >= 22 then | |
| return 0 | |
| end | |
| if sk == "Dodging" then | |
| local str,_ = you.strength() | |
| if str < 1 then | |
| str = 1 | |
| end | |
| local dex,_ = you.dexterity() | |
| local evp_adj = max(armour_evp() - 3, 0) | |
| local penalty_factor | |
| if evp_adj >= str then | |
| penalty_factor = str / (2 * evp_adj) | |
| else | |
| penalty_factor = 1 - evp_adj / (2 * str) | |
| end | |
| if you.race() == "Tengu" and you.xl() >= 14 then | |
| penalty_factor = penalty_factor * 1.2 -- flying EV mult | |
| end | |
| return 18 * math.log(1 + dex/18) / (20 + 2 * body_size()) * penalty_factor | |
| elseif sk == "Armour" then | |
| local str,_ = you.strength() | |
| if str < 0 then | |
| str = 0 | |
| end | |
| local val1 = 2/225 * armour_evp()^2 / (3 + str) | |
| local val2 = base_ac()/22 | |
| return val1 + val2 | |
| elseif sk == "Fighting" then | |
| return 0.75 | |
| elseif sk == "Shields" then | |
| local shield = items.equipped_at("Shield") | |
| if not shield then | |
| return 0 | |
| end | |
| return (at_target_shield_skill() and 0.2 or 0.75) | |
| elseif sk == "Invocations" then | |
| if you.god() == "Uskayaw" or you.god() == "Zin" then | |
| return 0.75 | |
| elseif you.god() == "Elyvilon" then | |
| if you.piety_rank() >= 4 and you.race() == "Deep Dwarf" then | |
| return 1 | |
| else | |
| return 0.5 | |
| end | |
| else | |
| return 0 | |
| end | |
| elseif sk == wskill() then | |
| return (at_min_delay() and 0.5 or 1.5) | |
| end | |
| end | |
| function choose_skills() | |
| skills = {} | |
| -- Choose one martial skill to train. | |
| martial_skills = { wskill(), "Fighting", "Shields", "Armour", "Dodging", "Invocations"} | |
| local best_sk | |
| local best_utility = 0 | |
| local utility | |
| for _,sk in ipairs(martial_skills) do | |
| if you.skill_cost(sk) then | |
| utility = skill_value(sk) / you.skill_cost(sk) | |
| if utility > best_utility then | |
| best_utility = utility | |
| best_sk = sk | |
| end | |
| end | |
| end | |
| if best_utility > 0 then | |
| table.insert(skills, best_sk) | |
| end | |
| -- Choose one MP skill to train. | |
| mp_skill = "Evocations" | |
| if you.god() == "Makhleb" or you.god() == "Cheibriados" or you.god() == "Okawaru" or you.god() == "Yredelemnul" or you.god() == "Beogh" or you.god() == "Qazlal" or you.god() == "the Shining One" or you.god() == "Lugonu" or you.god() == "Hepliaklqana" or you.god() == "Uskayaw" or you.god() == "Elyvilon" or you.god() == "Zin" then | |
| mp_skill = "Invocations" | |
| elseif you.god() == "Ru" or you.god() == "Xom" then | |
| mp_skill = "Spellcasting" | |
| end | |
| mp_skill_level = you.base_skill(mp_skill) | |
| bmp = you.base_mp() | |
| if you.god() == "Makhleb" and you.piety_rank() >= 2 and mp_skill_level < 15 then | |
| table.insert(skills, mp_skill) | |
| elseif you.god() == "the Shining One" and you.piety_rank() >= 5 and mp_skill_level < 12 then | |
| table.insert(skills, mp_skill) | |
| elseif you.god() == "Okawaru" and you.piety_rank() >= 1 and mp_skill_level < 4 then | |
| table.insert(skills, mp_skill) | |
| elseif you.god() == "Okawaru" and you.piety_rank() >= 4 and mp_skill_level < 10 then | |
| table.insert(skills, mp_skill) | |
| elseif you.god() == "Cheibriados" and you.piety_rank() >= 5 and mp_skill_level < 8 then | |
| table.insert(skills, mp_skill) | |
| elseif you.god() == "Yredelemnul" and you.piety_rank() >= 4 and (mp_skill_level < 8 or you.race() == "Deep Dwarf" and bmp < 10) then | |
| table.insert(skills, mp_skill) | |
| elseif you.race() == "Deep Dwarf" and you.god() ~= "No God" | |
| and (bmp < 5 or mp_skill_level < 2 or mp_skill ~= "Spellcasting" and mp_skill_level < 4) then | |
| table.insert(skills, mp_skill) | |
| elseif you.race() == "Vine Stalker" and you.god() ~= "No God" | |
| and mp_skill_level < 12 | |
| and (at_min_delay() or you.base_skill(wskill()) | |
| >= 3*mp_skill_level) then | |
| table.insert(skills, mp_skill) | |
| end | |
| skills2 = {} | |
| safe_count = 0 | |
| for _,sk in ipairs(skills) do | |
| if you.can_train_skill(sk) and you.base_skill(sk) < 27 then | |
| table.insert(skills2, sk) | |
| if you.base_skill(sk) < 26.5 then | |
| safe_count = safe_count + 1 | |
| end | |
| end | |
| end | |
| --if you.god() == "Xom" and safe_count == 1 and util.contains(skills2, weapon_skill) and you.base_skill(weapon_skill) < 26.5 and you.base_skill("Fighting") < 26.5 then | |
| -- Just in case Xom unwields our weapon in early game before we abandon, | |
| -- though currently we abandon Xom on T0. | |
| --table.insert(skills2, "Fighting") | |
| --end | |
| -- Try to avoid getting stuck in the skill screen. | |
| if safe_count == 0 then | |
| if you.base_skill("Fighting") < 26.5 then | |
| table.insert(skills2, "Fighting") | |
| elseif you.base_skill(mp_skill) < 26.5 then | |
| table.insert(skills2, mp_skill) | |
| else | |
| for _,sk in ipairs(skill_list) do | |
| if you.can_train_skill(sk) and you.base_skill(sk) < 26.5 then | |
| table.insert(skills2, sk) | |
| return skills2 | |
| end | |
| end | |
| end | |
| end | |
| return skills2 | |
| end | |
| function handle_skills() | |
| skills = choose_skills() | |
| choose_single_skill(skills[1]) | |
| for _,sk in ipairs(skills) do | |
| you.train_skill(sk, 1) | |
| end | |
| end | |
| function choose_stat_gain() | |
| local ap = armour_plan() | |
| if ap == "heavy" or ap == "large" then | |
| return "s" | |
| elseif ap == "light" then | |
| return "d" | |
| else | |
| local str,_ = you.strength() | |
| local dex,_ = you.dexterity() | |
| if 3*str < 2*dex then | |
| return "s" | |
| else | |
| return "d" | |
| end | |
| end | |
| end | |
| function auto_experience() | |
| return true | |
| end | |
| ------------------------------------------- | |
| -- a few utility functions | |
| function contains_string_in(name,t) | |
| for _, value in ipairs(t) do | |
| if string.find(name, value) then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function split(str, del) | |
| local res = { } | |
| local v | |
| for v in string.gmatch(str, "([^" .. del .. "]+)") do | |
| table.insert(res, v) | |
| end | |
| return res | |
| 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 vi_to_delta(c) | |
| local d2v = { | |
| [-1] = { [-1] = 'y', [0] = 'h', [1] = 'b'}, | |
| [0] = { [-1] = 'k', [1] = 'j'}, | |
| [1] = { [-1] = 'u', [0] = 'l', [1] = 'n'}, | |
| } -- hack | |
| local x,y | |
| for x = -1,1 do | |
| for y = -1,1 do | |
| if supdist(x,y) > 0 and d2v[x][y] == c then | |
| return x,y | |
| end | |
| end | |
| end | |
| end | |
| function sign(a) | |
| return a > 0 and 1 or a < 0 and -1 or 0 | |
| end | |
| function abs(a) | |
| return a * sign(a) | |
| end | |
| function vector_move(dx, dy) | |
| local str = '' | |
| for i = 1, abs(dx) do | |
| str = str .. delta_to_vi(sign(dx), 0) | |
| end | |
| for i = 1, abs(dy) do | |
| str = str .. delta_to_vi(0, sign(dy)) | |
| end | |
| return str | |
| end | |
| function max(x, y) | |
| if x > y then | |
| return x | |
| else | |
| return y | |
| end | |
| end | |
| function min(x, y) | |
| if x < y then | |
| return x | |
| else | |
| return y | |
| end | |
| end | |
| function supdist(dx, dy) | |
| return max(abs(dx), abs(dy)) | |
| end | |
| function adjacent(dx, dy) | |
| return abs(dx) <= 1 and abs(dy) <= 1 | |
| end | |
| --------------------------------------------- | |
| -- initialization/control/saving | |
| function make_endgame_plans() | |
| endgame_plan_list = split(ENDGAME_PLAN, ", ") | |
| for _,pl in ipairs(endgame_plan_list) do | |
| if pl == "slime" then | |
| SLIMY_RUNE = true | |
| elseif pl == "pan" then | |
| PAN_RUNE = true | |
| elseif pl == "abyss" then | |
| ABYSSAL_RUNE = true | |
| elseif pl == "hells" then | |
| HELL_RUNE = true | |
| elseif pl == "tomb" then | |
| GOLDEN_RUNE = true | |
| elseif pl == "tso" then | |
| TSO_CONVERSION = true | |
| elseif pl == "zig" then | |
| WILL_ZIG = true | |
| end | |
| end | |
| if endgame_plan_list[#endgame_plan_list] ~= "zot" then | |
| table.insert(endgame_plan_list, "zot") | |
| end | |
| if you.race() == "Deep Dwarf" and you.god() == "Lugonu" then | |
| LUGONU_CONVERSION = true | |
| end | |
| end | |
| function initialize() | |
| make_endgame_plans() | |
| if not did_first_turn and you.turns() == 0 then | |
| did_first_turn = true | |
| first_turn() | |
| end | |
| where = "nowhere" | |
| expect_new_location = true | |
| if c_persist.branches_entered == nil then | |
| c_persist.branches_entered = { "D" } | |
| end | |
| if c_persist.portals_found == nil then | |
| c_persist.portals_found = { } | |
| end | |
| if c_persist.plan_fail_count == nil then | |
| c_persist.plan_fail_count = { } | |
| end | |
| set_options() | |
| initialize_monster_array() | |
| if not level_map then | |
| level_map = {} | |
| stair_dists = {} | |
| clear_level_map(1) | |
| clear_level_map(2) | |
| waypoint_parity = 1 | |
| prev_where = "nowhere" | |
| end | |
| for _,god in ipairs(god_options()) do | |
| if god == "the Shining One" or god == "Elyvilon" or god == "Zin" then | |
| MIGHT_BE_GOOD = true | |
| end | |
| end | |
| initialized = true | |
| end | |
| function stop() | |
| automatic = false | |
| unset_options() | |
| end | |
| function start() | |
| automatic = true | |
| set_options() | |
| ready() | |
| end | |
| function panic(msg) | |
| crawl.mpr("<lightred>" .. msg .. "</lightred>") | |
| stop() | |
| end | |
| function startstop() | |
| if automatic then | |
| stop() | |
| else | |
| start() | |
| end | |
| end | |
| function hit_closest() | |
| startstop() | |
| end | |
| function set_counter() | |
| crawl.formatted_mpr("Set counter to what? ", "prompt") | |
| local res = crawl.c_input_line() | |
| c_persist.record.counter = tonumber(res) | |
| note("counter set to " .. c_persist.record.counter) | |
| end | |
| function first_turn_persist() | |
| if not c_persist.record then | |
| c_persist.record = {} | |
| end | |
| if not c_persist.record.counter then | |
| c_persist.record.counter = 1 | |
| else | |
| c_persist.record.counter = c_persist.record.counter + 1 | |
| end | |
| note("counter = " .. c_persist.record.counter) | |
| --if not c_persist.mlist then | |
| -- c_persist.mlist = {} | |
| --end | |
| --if not c_persist.record.mlist then | |
| -- c_persist.record.mlist = {} | |
| --end | |
| --for _,mname in ipairs(c_persist.mlist) do | |
| -- if not c_persist.record.mlist[mname] then | |
| -- c_persist.record.mlist[mname] = 1 | |
| -- else | |
| -- c_persist.record.mlist[mname] = c_persist.record.mlist[mname] + 1 | |
| -- end | |
| --end | |
| local god_list = c_persist.next_god_list | |
| for key,_ in pairs(c_persist) do | |
| if key ~= "record" then | |
| c_persist[key] = nil | |
| end | |
| end | |
| c_persist.cur_god_list = god_list | |
| if COMBO_CYCLE then | |
| local combo_string_list = split(COMBO_CYCLE_LIST, ", ") | |
| local combo_string = combo_string_list[1 + (c_persist.record.counter % (#combo_string_list))] | |
| local combo_parts = split(combo_string, "^") | |
| c_persist.options = "combo = " .. combo_parts[1] | |
| if #combo_parts > 1 then | |
| c_persist.next_god_list = { } | |
| for g in combo_parts[2]:gmatch(".") do | |
| table.insert(c_persist.next_god_list, fullgodname(g)) | |
| end | |
| end | |
| end | |
| end | |
| function fullgodname(g) | |
| if g == "B" then | |
| return "Beogh" | |
| elseif g == "C" then | |
| return "Cheibriados" | |
| elseif g == "E" then | |
| return "Elyvilon" | |
| elseif g == "H" then | |
| return "Hepliaklqana" | |
| elseif g == "L" then | |
| return "Lugonu" | |
| elseif g == "M" then | |
| return "Makhleb" | |
| elseif g == "O" then | |
| return "Okawaru" | |
| elseif g == "Q" then | |
| return "Qazlal" | |
| elseif g == "R" then | |
| return "Ru" | |
| elseif g == "T" then | |
| return "Trog" | |
| elseif g == "U" then | |
| return "Uskayaw" | |
| elseif g == "X" then | |
| return "Xom" | |
| elseif g == "Y" then | |
| return "Yredelemnul" | |
| elseif g == "Z" then | |
| return "Zin" | |
| elseif g == "1" then | |
| return "the Shining One" | |
| else | |
| return "???" | |
| end | |
| end | |
| function first_turn() | |
| first_turn_persist() | |
| if AUTO_START then | |
| automatic = true | |
| set_options() | |
| end | |
| end | |
| function run_update() | |
| if update_coroutine == nil then | |
| update_coroutine = coroutine.create(update_stuff) | |
| end | |
| local okay, err = coroutine.resume(update_coroutine) | |
| if not okay then | |
| error("Error in coroutine: " .. err) | |
| end | |
| if coroutine.status(update_coroutine) == "dead" then | |
| update_coroutine = nil | |
| do_dummy_action = false | |
| else | |
| do_dummy_action = true | |
| end | |
| end | |
| -- We want to call this exactly once each turn. | |
| function update_stuff() | |
| if not initialized then | |
| initialize() | |
| end | |
| if you.turns() == old_turn_count then | |
| time_passed = false | |
| return | |
| end | |
| time_passed = true | |
| old_turn_count = you.turns() | |
| if not did_first_turn and you.turns() == 0 then | |
| did_first_turn = true | |
| first_turn() | |
| end | |
| if you.turns() >= dump_count then | |
| dump_count = dump_count+100 | |
| crawl.dump_char() | |
| end | |
| if you.turns() >= skill_count then | |
| skill_count = skill_count+5 | |
| handle_skills() | |
| end | |
| if did_move then | |
| move_count = move_count + 1 | |
| else | |
| move_count = 0 | |
| end | |
| did_move = false | |
| dd_hw_meter = math.floor(6*dd_hw_meter/7) | |
| if did_move_towards_monster > 0 then | |
| did_move_towards_monster = did_move_towards_monster - 1 | |
| end | |
| if you.where() ~= where then | |
| if (where == "nowhere" or is_waypointable(where)) and is_waypointable(you.where()) then | |
| waypoint_parity = 3 - waypoint_parity | |
| if you.where() ~= prev_where or you.where():find("Tomb") then | |
| clear_level_map(waypoint_parity) | |
| set_waypoint() | |
| coroutine.yield() | |
| end | |
| cur_where = you.where() | |
| prev_where = where | |
| elseif is_waypointable(you.where()) and you.where() ~= cur_where then | |
| clear_level_map(waypoint_parity) | |
| set_waypoint() | |
| coroutine.yield() | |
| cur_where = you.where() | |
| end | |
| clear_ignores() | |
| target_stair = nil | |
| if expect_new_location then | |
| if where_shafted_from == you.where() then | |
| say("Successfully unshafted to " .. you.where() .. ".") | |
| where_shafted_from = nil | |
| end | |
| elseif automatic and not you.where():find("Abyss") then | |
| say("Shafted from " .. where .. " to " .. you.where() .. ".") | |
| if not where_shafted_from then | |
| where_shafted_from = where | |
| end | |
| end | |
| where = you.where() | |
| if cur_branch() and not util.contains(c_persist.branches_entered, cur_branch()) then | |
| say("Entered " .. cur_branch() .. ".") | |
| table.insert(c_persist.branches_entered, cur_branch()) | |
| end | |
| if expect_portal and in_portal() then | |
| say("Entered " .. where .. ".") | |
| end | |
| c_persist.portals_found = { } | |
| if where == "Vaults:5" and not v5_entry_turn then | |
| v5_entry_turn = you.turns() | |
| end | |
| end | |
| if is_waypointable(where) then | |
| update_level_map(waypoint_parity) | |
| end | |
| update_game_status() | |
| expect_new_location = false | |
| expect_portal = false | |
| check_messages() | |
| update_monster_array() | |
| danger = sense_danger(LOS) | |
| immediate_danger = sense_immediate_danger() | |
| find_good_stairs() | |
| cloudy = (not view.is_safe_square(0,0) and view.cloud_at(0,0) ~= nil) | |
| sense_sigmund() | |
| choose_tactical_step() | |
| if collectgarbage("count") > 7000 then | |
| collectgarbage() | |
| end | |
| end | |
| function ready() | |
| offlevel_travel = true | |
| run_update() | |
| if do_dummy_action then | |
| if not did_waypoint then | |
| crawl.process_keys(":" .. string.char(27) .. string.char(27)) | |
| else | |
| did_waypoint = false | |
| end | |
| return | |
| end | |
| if time_passed and SINGLE_STEP then | |
| stop() | |
| end | |
| if automatic then | |
| crawl.flush_input() | |
| crawl.more_autoclear(true) | |
| if have_message then | |
| plan_message() | |
| elseif you.where():find("Abyss") then | |
| plan_abyss_move() | |
| elseif you.have_orb() then | |
| plan_orbrun_move() | |
| else | |
| plan_move() | |
| end | |
| end | |
| end | |
| function magic(command) | |
| crawl.process_keys(command .. string.char(27) .. string.char(27) .. | |
| string.char(27)) | |
| end | |
| -------------------------------- | |
| -- a function to test various things conveniently | |
| function ttt() | |
| for i = -7,7 do | |
| for j = -7,7 do | |
| m = monster.get_monster_at(i,j) | |
| if m then | |
| crawl.mpr("(" .. i .. "," .. j .. "): name = " .. m:name() .. ", desc = " .. m:desc() .. ".") | |
| end | |
| end | |
| end | |
| --for it in inventory() do | |
| -- crawl.mpr("name = " .. it.name() .. ", ego = " .. (it.ego() or "none") .. ", subtype = " .. (it.subtype() or "none") .. ", slot = " .. slot(it) .. ".") | |
| --end | |
| for it in at_feet() do | |
| local val1,val2 = equip_value(it) | |
| local val3,val4 = equip_value(it, true) | |
| crawl.mpr("name = " .. it.name() .. ", ego = " .. (it.ego() or "none") .. it.ego_type .. ", subtype = " .. (it.subtype() or "none") .. ", slot = " .. (slot(it) or -1) .. ", values = " .. val1 .. " " .. val2 .. " " .. val3 .. " " .. val4 .. ".") | |
| end | |
| end | |
| function print_level_map() | |
| local num = waypoint_parity | |
| local dx,dy = travel.waypoint_delta(num) | |
| local x,y | |
| local str | |
| for y = -20,20 do | |
| str = "" | |
| for x = -20,20 do | |
| if level_map[num][dx+x][dy+y] == nil then | |
| str = str .. " " | |
| else | |
| str = str .. level_map[num][dx+x][dy+y] | |
| end | |
| end | |
| say(str) | |
| end | |
| end | |
| function print_stair_dists() | |
| local num = waypoint_parity | |
| local dx,dy = travel.waypoint_delta(num) | |
| local x,y | |
| local i | |
| local str | |
| for i = 1, #stair_dists[num] do | |
| say("---------------------------------------") | |
| for y = -20,20 do | |
| str = "" | |
| for x = -20,20 do | |
| if stair_dists[num][i][dx+x][dy+y] == nil then | |
| str = str .. " " | |
| else | |
| str = str .. string.char(string.byte('A') + stair_dists[num][i][dx+x][dy+y]) | |
| end | |
| end | |
| say(str) | |
| end | |
| end | |
| end | |
| function c_trap_is_safe(trap) | |
| return (trap ~= "permanent teleport") | |
| end | |
| function c_answer_prompt(prompt) | |
| if prompt == "Die?" then | |
| return false | |
| end | |
| if prompt:find("blurry vision") then | |
| return true | |
| end | |
| if prompt:find("Have to go through") then | |
| return offlevel_travel | |
| end | |
| if prompt:find("transient mutations") then | |
| return true | |
| end | |
| if prompt:find("Keep disrobing") then | |
| return false | |
| end | |
| if prompt:find("Really unwield") or prompt:find("Really take off") | |
| or prompt:find("Really remove") or prompt:find("Really wield") | |
| or prompt:find("Really wear") or prompt:find("Really put on") | |
| or prompt:find("Really drink") then | |
| return true | |
| end | |
| if prompt:find("Keep reading") then | |
| return true | |
| end | |
| if prompt:find("Keep eating") then | |
| return true | |
| end | |
| if prompt:find("This attack would place you under penance") then | |
| return false | |
| end | |
| if prompt:find("You cannot afford") and prompt:find("travel there anyways") then | |
| return true | |
| end | |
| if prompt:find("Shopping list") then | |
| return false | |
| end | |
| end | |
| function ch_stash_search_annotate_item(it) | |
| return "" | |
| end | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment