diff --git a/battleman.py b/battleman.py index 234d3e8..e701cef 100755 --- a/battleman.py +++ b/battleman.py @@ -14,6 +14,7 @@ sys.path.append('lib/') import cPickle as pickle import argparse +import cmd import os.path import battle from battle import CombatGroup @@ -61,243 +62,295 @@ def main(): 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) + + cmd_parser = CommandParser(btl, battle_pickle, BP_FILE) + cmd_parser.cmdloop() -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 = [] +class CommandParser(cmd.Cmd): + """Parse the commands from the command-line.""" - 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) + def __init__(self, btl, battle_pickle, BP_FILE): + cmd.Cmd.__init__(self) - # 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 + self.btl = btl + self.battle_pickle = battle_pickle + self.BP_FILE = BP_FILE + self.prompt = '\n> ' -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""") + def postloop(self): + # Re-pickle and write if changed after every query. It's cheap + # and we only have to run at user-speed anyway + old_bp = self.battle_pickle + self.battle_pickle = pickle.dumps(btl) + + if old_bp != self.battle_pickle: + try: + with open(self.BP_FILE, 'w') as f: + f.write(self.battle_pickle) + except Exception: + if not self.bp_io_failed: + print("Warning: can't write the battle pickle. Resuming later will fail.") + self.bp_io_failed = True -# Core data parsing functions + # a + def do_add(self, line): + """add [N] + Add the specified number of groups""" -def do_add_combatants(btl, data): - if len(data) >= 1: - ngroups = int(data[0]) - else: - ngroups = easyinput.input_int('number of groups') + data = line.split(' ') - 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.') + if len(data) >= 1: + num_groups = int(data[0]) else: - print c.format_full_info() - else: - print btl.format_current_group() + num_groups = easyinput.input_int('number of groups') + + for i in range(1, num_groups+1): + print "Adding group {}".format(i) + self.btl.add_group(CombatGroup.from_input()) -def do_damage(btl, data): - c = do_combatant_select(btl, data) - if not c: - return + # b + def do_begin(self, line): + """begin + Begins the battle. Rolls initiative for NPCs and prompts for PCs""" - if len(data) >= 2: - amount = int(data[1]) - else: - amount = easyinput.input_int('damage') - - c.damage(amount) + self.btl.begin() -def do_heal(btl, data): - c = do_combatant_select(btl, data) - if not c: - return + # p + def do_print(self, line): + """print [index] + Print detailed info for combatant with index, or combatant or group with initiative""" - if len(data) >= 2: - amount = int(data[1]) - else: - amount = easyinput.input_int('amount') + data = line.split(' ') - c.heal(amount) + if len(data) >= 1: + c = self.btl.get_combatant(int(data[0])) + if not c: + print 'Error: Invalid combatant index.' + else: + print c.format_full_info() + else: + print self.btl.format_current_group() -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) + # l + def do_list(self, line): + """list + Lists a summary of all of the combat groups and their members""" + + print self.btl.format_combatants() -def do_remove_temp_hp(btl, data): - do_stub() + # d + def do_damage(self, line): + """damage [index] [amount] + Deals damage to the specified combatant""" + + data = line.split(' ') + + c = battle.do_combatant_select(self.btl, data) + if not c: + return + + if len(data) >= 2: + amount = int(data[1]) + else: + amount = easyinput.input_int('damage') + + c.damage(amount) + + + # h + def do_heal(self, line): + """heal [index] [amount] + Heal hit points for the specified combatant""" + + data = line.split(' ') + + c = battle.do_combatant_select(self.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_surge(btl, data, heal=True): - c = do_combatant_select(btl, data) - if not c: - return - c.use_surge(heal) + # t + def do_temp(self, line): + """temp [index] [amount] + Add temporary hit points to the specified combatant""" + + data = line.split(' ') + + c = battle.do_combatant_select(self.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_second_wind(btl, data): - c = do_combatant_select(btl, data) - if not c: - return - c.use_second_wind() + # T + def do_rmtemp(self, line): + """rmtemp [index] [amount] + Remove temporary hit points from the specified combatant""" + + do_stub() -def do_add_condition(btl, data): - duration = None - end_type = 'e' + # s, so + def do_surge(self, line): + """surge [index] [heal] + Combatant with index uses a healing surge. If heal is 0, don't heal the combatant""" - c = do_combatant_select(btl, data) - if not c: - return + data = line.split(' ') - 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) + c = battle.do_combatant_select(self.btl, data) + if not c: + return - 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) + heal = True + if len(data) >= 2 and data[1] == '0': + heal = False + c.use_surge(heal) -def do_remove_condition(btl, data): - c = do_combatant_select(btl, data) - if not c: - return + # sw + def do_wind(self, line): + """wind [index] + Use Second Wind for combatant""" - if len(data) >= 2: - index = int(data[1]) - else: - cond = c.choose_condition() - index = None - if cond: - index = cond['index'] + data = line.split(' ') - if index != None: - c.remove_condition(index) + c = battle.do_combatant_select(self.btl, data) + if not c: + return + c.use_second_wind() -def do_use_recharge_power(btl, data): - c = do_combatant_select(btl, data) - if not c: - return + # c + def do_cond(self, line): + """cond [index] [name] [type] [duration] [start|end] + Add a temporary condition to a combatant, optionally specifying the condition name, type (s or t), duration and what phase of the combatant's turn it expires on""" - if len(data) >= 2: - index = int(data[1]) - else: - r = c.choose_recharge_power() - index = None - if r: - index = r['index'] + data = line.split(' ') - if index != None: - c.use_recharge_power(index) + duration = None + end_type = 'e' + + c = battle.do_combatant_select(self.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_wait(btl, data): - do_stub() + # C + def do_rmcond(self, line): + """rmcond [index] [condition_index] + Remove a condition from a combatant early.""" + + data = line.split(' ') + + c = battle.do_combatant_select(self.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_unwait(btl, data): - do_stub() + # r + def do_recharge(self, line): + """recharge [index] [recharge_index] + Use a rechargable power""" + + data = line.split(' ') + + c = battle.do_combatant_select(self.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) + + + # w + def do_wait(self, line): + """wait + This function is still a stub""" + + do_stub() + + + # W + def do_unwait(self, line): + """unwait + This function is still a stub""" + + do_stub() + + + # x + def do_sync(self, line): + """sync + This function is still a stub""" + + do_stub() + + + def do_EOF(self, line): + self.do_quit(line) + + + # q + def do_quit(self, line): + """quit + Exits the program. If a battle is in progress, it is temporarily saved and can be resumed by running the program with --resume next time.""" + + sys.exit(0) 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')