309 lines
8.0 KiB
Python
Executable File
309 lines
8.0 KiB
Python
Executable File
#!/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()
|