#!/usr/bin/python # # battleman.py - RPG Battle Manager # # A table-top RPG battle flow manager # Tuned pretty specifically to D&D 4e for now... need a templatized system # to do anything fancier... may develop that at some point. # # future features: # * an option for passing in multiple files that contain combatant definitions import sys sys.path.append('lib/') import cPickle as pickle import argparse import os.path import battle from battle import CombatGroup from battle import Combatant import easyinput def main(): btl = battle.Battle() ### This is the pickling jar battle_pickle = None bp_io_failed = False BP_FILE = os.path.expanduser('~/.config/4etools/battleman/battle.pickle') ### # Make sure config directory exists if not os.path.exists(os.path.dirname(BP_FILE)): os.makedirs(os.path.dirname(BP_FILE)) # Get command-line args settings = parse_args() print "Welcome to 4e Battle Manager.\n" # Resume battle if needed if settings.resume: try: with open(BP_FILE, 'r') as f: btl = pickle.load(f) battle_pickle = pickle.dumps(btl) except: print "Error: Couldn't resume. Quitting to preserve our pickle." sys.exit(1) else: # hard-coding test cases for now. # Eventually, use a state-saving text file that's easy to edit, or at least copy... btl.add_group(CombatGroup("Adele", [Combatant("Adele", hp=26, pc=True, surges=8, sw=1)], 2)) btl.add_group(CombatGroup("Aristaire", [Combatant("Aristaire", hp=20, pc=True, surges=6, sw=1)], 0)) btl.add_group(CombatGroup("Foobolds", [Combatant("Foobold", hp=50), Combatant("Foobold", hp=50), Combatant("Foobold", hp=50), Combatant("Foobold", hp=50), Combatant("Foobold", hp=50)], 20)) btl.add_group(CombatGroup("Barglins", [Combatant("Barglin", hp=1), Combatant("Barglin", hp=1)], 3)) btl.add_group(CombatGroup("Orcs of Baz", [Combatant("Orc", hp=32), Combatant("Orc", hp=32)], 1)) print btl while True: do_prompt(btl, battle_pickle, bp_io_failed, BP_FILE) def do_prompt(btl, battle_pickle, bp_io_failed, BP_FILE): print '' (comm, rdata) = easyinput.input_str('', default='n', show_default=False, prompt_str='>').partition(' ')[::2] data = rdata.split(' ') if data == ['']: data = [] if comm == '?': do_help() # fixme - add ability to get command-specific help elif comm == 'a': do_add_combatants(btl, data) elif comm == 'p': do_print_combatant_info(btl, data) elif comm == 'l': print btl.format_combatants() elif comm == 'b': btl.begin() elif comm == 'd': do_damage(btl, data) elif comm == 'h': do_heal(btl, data) elif comm == 't': do_add_temp_hp(btl, data) elif comm == 'T': do_remove_temp_hp(btl, data) elif comm == 's': do_surge(btl, data) elif comm == 'so': do_surge(btl, data, heal=False) elif comm == 'sw': do_second_wind(btl, data) elif comm == 'c': do_add_condition(btl, data) elif comm == 'C': do_remove_condition(btl, data) elif comm == 'n': btl.next_combatant() elif comm == 'r': do_use_recharge_power(btl, data) elif comm == 'w': do_wait(btl, data) elif comm == 'W': do_unwait(btl, data) elif comm == 'x': do_stub() elif comm == 'q': sys.exit(0) # Re-pickle and write if changed after every query. It's cheap # and we only have to run at user-speed anyway old_bp = battle_pickle battle_pickle = pickle.dumps(btl) if old_bp != battle_pickle: try: with open(BP_FILE, 'w') as f: f.write(battle_pickle) except: if not bp_io_failed: print("Warning: can't write the battle pickle. Resuming later will fail.") bp_io_failed = True def do_help(): print("""Possible commands: ? - print this help menu (yay, you already figured that one out) a - add more combatants (works during battle) b - begin the battle l - list combatants p - print info for combatant/group with initiative d - deal damage to someone h - heal someone t - add temporary hit points T - remove temporary hit points [stub] s - use a healing surge so - use a healing surge, but don't regain hit points sw - use a second wind c/C - apply / remove a condition r - use a rechargable power n - next (end the current combat group's turn) w/W - wait / unwait (remove a combatant from the initiative order and into a separate pool, then put them back) [stub] x - force save the progress to the current battle cache (for use with --resume) [stub] q - quit""") # Core data parsing functions def do_add_combatants(btl, data): if len(data) >= 1: ngroups = int(data[0]) else: ngroups = easyinput.input_int('number of groups') for i in range(1, ngroups+1): print "Adding group {}".format(i) btl.add_group(CombatGroup.from_input()) def do_print_combatant_info(btl, data): if len(data) >= 1: c = btl.get_combatant(int(data[0])) if not c: print('Error: Invalid combatant index.') else: print c.format_full_info() else: print btl.format_current_group() def do_damage(btl, data): c = do_combatant_select(btl, data) if not c: return if len(data) >= 2: amount = int(data[1]) else: amount = easyinput.input_int('damage') c.damage(amount) def do_heal(btl, data): c = do_combatant_select(btl, data) if not c: return if len(data) >= 2: amount = int(data[1]) else: amount = easyinput.input_int('amount') c.heal(amount) def do_add_temp_hp(btl, data): c = do_combatant_select(btl, data) if not c: return if len(data) >= 2: amount = int(data[1]) else: amount = easyinput.input_int('amount') c.add_temp_hp(amount) def do_remove_temp_hp(btl, data): do_stub() def do_surge(btl, data, heal=True): c = do_combatant_select(btl, data) if not c: return c.use_surge(heal) def do_second_wind(btl, data): c = do_combatant_select(btl, data) if not c: return c.use_second_wind() def do_add_condition(btl, data): duration = None end_type = 'e' c = do_combatant_select(btl, data) if not c: return name = easyinput.do_data_input_str(data, 1, 'condition name') ctype = easyinput.do_data_input_str(data, 2, 'condition type', default='s', show_default=True) if ctype == 't': duration = easyinput.do_data_input_int(data, 3, 'duration') end_type = easyinput.do_data_input_str(data, 4, '(s)tart|(e)nd', default='e', show_default=True) c.add_condition(name, ctype, duration, end_type) def do_remove_condition(btl, data): c = do_combatant_select(btl, data) if not c: return if len(data) >= 2: index = int(data[1]) else: cond = c.choose_condition() index = None if cond: index = cond['index'] if index != None: c.remove_condition(index) def do_use_recharge_power(btl, data): c = do_combatant_select(btl, data) if not c: return if len(data) >= 2: index = int(data[1]) else: r = c.choose_recharge_power() index = None if r: index = r['index'] if index != None: c.use_recharge_power(index) def do_wait(btl, data): do_stub() def do_unwait(btl, data): do_stub() def do_stub(): print "Sorry, this is a stub function" def parse_args(): parser = argparse.ArgumentParser(description='Command-line interface to manage battle data for D&D 4e', formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('--resume', '-r', action='store_true', help='Resume the battle from the last run of the program') return parser.parse_args() if __name__ == '__main__': main()