#!/usr/bin/python from subprocess import call from time import sleep import sys, os, re, time from ConfigParser import ConfigParser import locale ####################################################################################################### # # - Setup for Grotto town's altar. # - Check if pudding on altar before trying to move # - Sanity check to avoid/limit YASD : HP MONITOR, FIGHT COUNTER, ... # - Gain calculation (how much HP/AC/SCORE gained, etc) # - Auto-saccing # - Auto-praying # - Possibility to keep a luckstone to max (or lower, if cursed) luck # - .Ini file for easier configuration # - "All time best" High Score comparison on NAO # # # TODO: # - checking if the monster we are fighting is a pudding, else, get stormy out of the stash and bash him ! # - if too much time to move to the altar, check if monster in the middle and bash him to death # - improve the automatic saccing code : # - detect and do something for old corpses (move them to the Elbereth or poly them) # - auto-clear the altar with a wand of polymorph (or teleport!) if loot page > 10 for example # - improve eating code : if rotten food, unconscious and/or confused : handle this ! # - correct the "stat detection problem #1" that happen sometime # # NOTE: I might have screwed some global var use. If bot acting weird, check that "global" is used in function. # ####################################################################################################### ####################################### # !!!! READ ME !!!! important! # ####################################### ############### # AUTOPICKUP must be enabled for desired items, and some FOOD exception: ############### # OPTIONS=autopickup # OPTIONS=pickup_types:=!"?/ # AUTOPICKUP_EXCEPTION="<*magic lamp*" # AUTOPICKUP_EXCEPTION="<*magic marker*" # AUTOPICKUP_EXCEPTION="<*food ration*" # AUTOPICKUP_EXCEPTION="<*ration*" # AUTOPICKUP_EXCEPTION=">*tripe*" # AUTOPICKUP_EXCEPTION="<*lembas*" # AUTOPICKUP_EXCEPTION="<*horn*" # AUTOPICKUP_EXCEPTION="<*magic whistle*" # you can add spellbook, bag, specific armor, etc... ############### # Wearing a ring of Slow Digestion is strongly advised (and "oLS if possible). # Geno L h c strongly advised ! # Good AC/HP if possible, to avoid YASD if something go wrong ############### ############### # The bane must be wielded at the starting of the script. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ! IMPORTANT: the screen session must be started from the same directory as the bot script ! # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ############### ############### # Map configuration (grotto town) : # At the spot marked x there is the stash named l00t and a perma-Elbereth. # Farming start at the spot marked x. # Just below is the altar. Pudding come from far down. # # 000 # 0x0 # 0_0 # 0P0 ############################################################################################################ THE_BOT="Simpl3t's Nethack 3.4.3 Pudding Farming Bot\n(Thanks Kerio for showing me \"KCAFB\" to control Nethack within a screen session)" ########################## # CHECKING COMMAND LINE # ########################## if len(sys.argv) != 2: print THE_BOT print "\n Usage: %s \n" % sys.argv[0] print "Wield the bane before starting the bot !" print "/!\ Read the \"!!!! READ ME !!!!\" section in the source code\n Lot of advices (what to geno, autopickup setup, ...)" print "\nParameters example (brown pudding) :" print "- Fast loots, almost never stop the bot : disable auto-sac/auto-pray and use 25 (or higher, 100 is OK) for the number of attack." print "- HP (/AC:enable luckstone) from $DEITY but slow loot, may stop the bot : enable auto-sac/auto-pray and use a small (10) number of attack." print "\nParameters example (black pudding) :" print "- Fast loots : 150 attack count seems to work fine" print "- Saccing : ?" print "\n\nWith Brown pudding the bot can run forever (saccing or looting, ...).\nWith black pudding the attack function must be tweaked or you have to find the right bot parameters.\n" sys.exit(0) ######################################### # LOADING CONFIGURATION FROM INI FILE ######################################### config = ConfigParser() try: config.read(sys.argv[1]) TITLE_CONFIG = config.get('BOT', 'TITLE') STASH = config.get('BOT', 'STASH') ALTAR_DIRECTION = config.get('BOT', 'ALTAR_DIRECTION') STASH_DIRECTION = config.get('BOT', 'STASH_DIRECTION') PUDDING_BANE = config.get('BOT', 'PUDDING_BANE') LUCKSTONE = config.get('BOT', 'LUCKSTONE') ATTACK_COUNT_ABORT = int(config.get('BOT', 'ATTACK_COUNT_ABORT')) INFINITE_ATTACK_CHECKING = int(config.get('BOT', 'INFINITE_ATTACK_CHECKING')) HP_DROP_LIMIT = int(config.get('BOT', 'HP_DROP_LIMIT')) STAT_DISPLAY_CYCLE = int(config.get('BOT', 'STAT_DISPLAY_CYCLE')) MAX_CYCLE = int(config.get('BOT', 'MAX_CYCLE')) ATTACK_COUNT = int(config.get('BOT', 'ATTACK_COUNT')) iSACCING = int(config.get('BOT', 'ENABLE_SACCING')) iPRAYING = int(config.get('BOT', 'ENABLE_PRAYING')) iKEEP_LUCKSTONE = int(config.get('BOT', 'KEEP_LUCKSTONE')) NAO_HIGH_SCORE_COMPARISON = config.get('BOT', 'NAO_HIGH_SCORE_COMPARISON') if (iSACCING == 0) and (iPRAYING == 1): print "auto-saccing must be enabled if you want to enable auto-praying !" sys.exit(0) except ValueError: print "ERROR: check the ini file, dammit !" sys.exit(0) ######################################### # ----------------------- # Global vars gCURRENT_HP=0 gMAX_HP=0 gSTARTING_MAX_HP=0 gCURRENT_SCORE=0 gSTARTING_SCORE=0 gCURRENT_AC=0 gSTARTING_AC=0 gCURRENT_TURN=0 gSTARTING_TURN=0 gPRAYER_COUNT=0 gSTART_TIME=time.time() # ---------------------- def sendcommand(command): call(("screen", "-X", "stuff", command)) def dumpscreen(): call(("screen", "-X", "hardcopy", "screendump.nh")) sleep(0.5) return file("screendump.nh").read() def hunger(): screen = dumpscreen() match = re.search("(?i).*Exp:(\d*).*(Hun?g?r?y?|Wea?k?|Fai?n?t?i?n?g?)", screen) return match def on_altar(): sendcommand("\x1b\x1b\x1b:") sleep(1.5) screen = dumpscreen() sendcommand("\x1b\x1b\x1b") return "There is an altar to " in screen def on_elbereth(): sendcommand("\x1b\x1b\x1b:") sleep(1.5) screen = dumpscreen() sendcommand("\x1b\x1b\x1b") return "Elbereth" in screen or STASH in screen def GetStats(): screen = dumpscreen() sleep(0.5) match = re.search("HP:(\d*)\((\d*)\) Pw:(\d*)\((\d*)\) AC:\-?(\d*) Exp:(\d*) T:(\d*)", screen) # Dlvl:6 $:0 HP:538(538) Pw:133(133) AC:-38 Exp:23 T:107132 if match: global gSTARTING_MAX_HP, gCURRENT_HP, gMAX_HP, gSTARTING_AC, gCURRENT_AC, gCURRENT_TURN, gSTARTING_TURN gCURRENT_HP = int(match.group(1)) gMAX_HP = int(match.group(2)) if gSTARTING_MAX_HP == 0: gSTARTING_MAX_HP = gMAX_HP gCURRENT_AC = int(match.group(5)) if gSTARTING_AC == 0: gSTARTING_AC = gCURRENT_AC gCURRENT_TURN = int(match.group(7)) if gSTARTING_TURN == 0: gSTARTING_TURN = gCURRENT_TURN else: print "ABORT: Stats detection problem #1 !" sys.exit(0) matchB = re.search("Ch:\d*.* S:(\d*)", screen) # AlphaBlend the Slayer St:18/12 Dx:18 Co:18 In:11 Wi:18 Ch:10 Chaotic S:3874322 if matchB: global gCURRENT_SCORE, gSTARTING_SCORE gCURRENT_SCORE = int(matchB.group(1)) if gSTARTING_SCORE == 0: gSTARTING_SCORE = gCURRENT_SCORE else: print "ABORT: Stats detection problem #2 !" sys.exit(0) def PressAnyKeyDEBUG(WaitKey, sMsg): if WaitKey == 1: raw_input("DEBUG Hit ENTER please... [%s]" % sMsg) else: print "DEBUG... [%s]" % sMsg def Attack_Pudding(): iTry = 0; while True: # Attack sendcommand(("F%s\x1b\x1b\x1b" % ALTAR_DIRECTION) * ATTACK_COUNT) sleep(1.5) # Check if pudding still on the altar sendcommand(";%s." % ALTAR_DIRECTION) sleep(1.5) screen = dumpscreen() if not "a pudding or ooze (" in screen: break iTry += 1 # do we need to eat ? Hunger_CheckFix() # Should we abort ? if (iTry >= ATTACK_COUNT_ABORT) and (INFINITE_ATTACK_CHECKING == 0): print "ABORT: Attack problem (too much time spent attacking) !" sys.exit(0) def Hunger_CheckFix(): iTry = 0 while True: if hunger(): # #loot, loot it? yes, out, comestibles, 1 of the first, drop all comestibles, eat, yes sendcommand("\x1b\x1b\x1b#lo\ny\no%\n1a\n\x1b\x1b\x1bD%\n.\n\x1b\x1b\x1bey\x1b\x1b\x1b") sleep(1.5) iTry+=1 if iTry >= 5: print "ABORT: too much time to eat !" sys.exit(0) continue else: break def OfferingAndPray(): SafeToPray = True while True: sendcommand("\x1b\x1b\x1b#of\n") sleep(1.5) screen = dumpscreen() if ("You don't have anything to sacrifice." in screen) or ("You are not standing on an altar." in screen): sendcommand("\x1b\x1b\x1b\x1b\x1b") break else: if ("pudding corpse here; sacrifice it? [ynq]" in screen) or \ ("pudding corpses here; sacrifice one? [ynq]" in screen) or \ (("There is a " in screen) and (" pudding corpse named" in screen) and ("sacrifice" in screen) and ("? [ynq]" in screen)): sendcommand("y") iTry=0 while True: sleep(1) screen = dumpscreen() sleep(1) if "You have a hopeful feeling." in screen: SafeToPray = False break elif "You have a feeling of reconciliation." in screen: Pray() SafeToPray = False break elif "Your sacrifice is consumed in a burst of flame!" in screen: sendcommand(" ") continue elif "You glimpse a four-leaf clover at your feet." in screen: sendcommand(" ") continue elif "Unknown command ' '." in screen: break elif "Nothing happens." in screen: return else: iTry += 1 if iTry > 3: return continue if SafeToPray: Pray() SafeToPray = False else: print "ABORTING: saccing problem after trying to offer !" sys.exit(0) # There is a brown pudding corpse here; sacrifice it? [ynq] (n) # There are 2 brown pudding corpses here; sacrifice one? [ynq] (n) # You don't have anything to sacrifice. # You have a hopeful feeling. # Your sacrifice is consumed in a burst of flame! # You glimpse a four-leaf clover at your feet. # Unknown command ' '. # You are not standing on an altar. # Nothing happens. # You have a feeling of reconciliation. def Pray(): if iPRAYING == 0: return global gPRAYER_COUNT gPRAYER_COUNT += 1 # do not wield the bane or it may be restored by $DEITY sendcommand("w-") sleep(1) sendcommand("#pr\ny\x1b\x1b\x1b\x1b\x1b") sleep(1) sendcommand("w%s" % PUDDING_BANE) # Are you sure you want to pray? [yn] (n) # You begin praying to Set. # You finish your prayer. # You feel that Set is well-pleased. # An object appears at your feet! # You are surrounded by a shimmering light. # to attack you, but pull def StatsWon(): GetStats() global gCURRENT_SCORE, gSTARTING_SCORE, gSTARTING_AC, gCURRENT_AC, gSTARTING_TURN, gCURRENT_TURN, gCURRENT_HP, gSTARTING_MAX_HP, gMAX_HP, gSTART_TIME print "\n-------------------\nStats Won\n-------------------" print "HP: +%d" % (gMAX_HP - gSTARTING_MAX_HP) print "SCORE: +%d (+%d/mn)" % ((gCURRENT_SCORE-gSTARTING_SCORE), round(((gCURRENT_SCORE-gSTARTING_SCORE)/(time.time()-gSTART_TIME))*60)) print "AC: +%d" % abs(gSTARTING_AC - gCURRENT_AC) print "Prayed %d times" % gPRAYER_COUNT print "%d TURNS elapsed" % (gCURRENT_TURN - gSTARTING_TURN) print "%d minutes elapsed" % (round((time.time()-gSTART_TIME)/60)) print "-------------------" # Compare NAO High Score ? if NAO_HIGH_SCORE_COMPARISON != "": CompareHighScoreNAO() # Get the rank on NAO High Score list for the specified score def GetNAORank(iScore): rank = -1 the_regexp = "(\d*)([0-9,]*) int(match[2].replace(',', '')): rank = int(match[1]) else: break return rank # Display some rank stats from NAO, for the current score def CompareHighScoreNAO(): global gCURRENT_SCORE myrank = GetNAORank(gCURRENT_SCORE) myrankasc = GetNAORank(gCURRENT_SCORE*2) if myrank > -1: print "\n>> You rank #%d in the NAO HIGH SCORE LIST ! <<\t(#%d after ascension)\n" % (myrank, myrankasc) ########################################################################### ########################################################################### ########################################################################### ########################################################################### ########################################################################### ########################################################################### ########################################################################### ########################################################################### try: print THE_BOT print "\nConfig file \"%s\" used...\n\"%s\"" % (sys.argv[1], TITLE_CONFIG) print "\nRunning the farming bot with the following options:" print "%d cycles, %d attacks per turn (INFINITE_ATTACK_CHECKING=%d)" % (MAX_CYCLE, ATTACK_COUNT, INFINITE_ATTACK_CHECKING) if iSACCING == 1: print "Auto-Saccing enabled" else: print "Auto-Saccing disabled" if iPRAYING == 1: print "Auto-Praying enabled" else: print "Auto-Praying disabled" if iKEEP_LUCKSTONE == 1: print "Keeping the luckstone in inventory" else: print "Not keeping the luckstone" if NAO_HIGH_SCORE_COMPARISON != "": print "Real time NAO High Score ranking enabled" print "" GetStats() print "Starting stats\n---------------" print "HP: %d/%d" % (gCURRENT_HP, gMAX_HP) print "SCORE: %d" % (gCURRENT_SCORE) print "AC: %d" % gCURRENT_AC print "TURN: %d\n" % gCURRENT_TURN # Compare NAO High Score ? if NAO_HIGH_SCORE_COMPARISON != "": if (not os.path.exists("nao.hscore")) or ((time.time() - os.path.getmtime("nao.hscore")) > 3600): print "Fetching NAO high score list..." call(("wget", "--quiet", "--output-document=nao.hscore", "http://alt.org/nethack/top.php")) CompareHighScoreNAO() # Let's go start farming ! iCycle = 0 while iCycle < MAX_CYCLE: print "Cycle %d/%d.................................." % (iCycle+1, MAX_CYCLE) # Refresh the stats GetStats() # Check HP if HP_DROP_LIMIT != 0: if (gMAX_HP - gCURRENT_HP) > HP_DROP_LIMIT: print "ABORT: HP dropped below the limit !" sys.exit(0) # Check if hungry (and eat if necessary) Hunger_CheckFix() # Attack those damn pudding Attack_Pudding() # move towards the altar iTry = 0 while True: sendcommand("%s\x1b\x1b\x1b" % ALTAR_DIRECTION) if on_altar(): break else: iTry+=1 if iTry >= ATTACK_COUNT: print "ABORT: too much time to move to the altar !" sys.exit(0) # Drop unknown BUC, all of them sendcommand("DX\n.\n\x1b\x1b\x1b") # Offering to our god and praying if possible if iSACCING == 1: OfferingAndPray() # Move towards the Stash/Elbereth iTry = 0 while True: sendcommand("%s\x1b\x1b\x1b" % STASH_DIRECTION) if on_elbereth(): break else: iTry+=1 if iTry >= 20: print "ABORT: too much time to move to the Stash/Elbereth !" sys.exit(0) # drop the puddingbane sendcommand("d%s\x1b\x1b\x1b" % PUDDING_BANE) # drop the luckstone if necessary if iKEEP_LUCKSTONE == 1: sendcommand("d%s\x1b\x1b\x1b" % LUCKSTONE) # .:: put everything in the chest ::. sendcommand("#lo\ny\nia\n.\n\x1b\x1b\x1b") # pick & wield the bane sendcommand(",)\n\x1b\x1b\x1bw%s\x1b\x1b\x1b" % PUDDING_BANE) # pick the luckstone if necessary if iKEEP_LUCKSTONE == 1: sendcommand(",*\n\x1b\x1b\x1b") iCycle+=1 # Display some stats ? if (iCycle % STAT_DISPLAY_CYCLE) == 0: StatsWon() print "\nEnd of farming [%d cycle(s) done]." % iCycle StatsWon() except KeyboardInterrupt: print "\nKeyboard Interruption by the user !" print "Going home..." StatsWon()