-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroller.py
More file actions
executable file
·247 lines (205 loc) · 7.83 KB
/
roller.py
File metadata and controls
executable file
·247 lines (205 loc) · 7.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#!/usr/bin/env python3
'''
The main module for the roller application. Command parsing resides here.
'''
import sys
import utils
def get_modifier(value):
'''
Function that returns the modifier for a skill of a given amount
'''
return (value - 10) // 2
def perform_attack(args, character):
'''
Parses the commands ands executes the attack. Assumes a strength-based
attack unless specified otherwise.
'''
# Variables to test for commands and their shorthands, also track modifications
advantage_types = ['advantage', 'adv']
disadvantage_types = ['disadvantage', 'dis']
base_skill = 'strength'
advantage_status = ''
# When only specifying advantage/disadvantage or a non-strength stat to use
if len(args) == 1:
if args[0] in advantage_types + disadvantage_types:
advantage_status = args[0]
elif args[0] in character.traits:
base_skill = args[0]
else:
print('Error: expected advantage/disadvantage or base skill')
return
# When using both advantage/disadvantage and a non-strength stat
# because we allow for either order in the command
elif len(args) == 2:
all_words = advantage_types + disadvantage_types + list(character.traits.keys())
if args[0] not in all_words or args[1] not in all_words:
print('Error: expected advantage/disadvantage or base skill')
return
if args[0] in advantage_types + disadvantage_types:
advantage_status = args[0]
base_skill = args[1]
else:
advantage_status = args[1]
base_skill = args[0]
elif len(args) != 0:
print('Error: expecting zero, one, or two modifiers for attacks')
return
# Perform the attack
modifier = get_modifier(int(character.traits[base_skill]))
modifier += int(character.traits['proficiency'])
res, nat_res = utils.skill_check(modifier)
# Roll again for advantage or disadvantage
if advantage_status in advantage_types:
re_res, re_nat_res = utils.skill_check(modifier)
res, nat_res = max(res, re_res), max(nat_res, re_nat_res)
elif advantage_status in disadvantage_types:
re_res, re_nat_res = utils.skill_check(modifier)
res, nat_res = min(res, re_res), min(nat_res, re_nat_res)
print('Attack rolled a %d with a natural %d' % (res, nat_res))
def handle_skill(args, character):
'''
Determines what kind of skill check is required and loads the appropriate
character information to deal with it. Prints the information directly
rather than returning.
'''
# Variables to test for commands and their shorthands
advantage_types = ['advantage', 'adv']
disadvantage_types = ['disadvantage', 'dis']
check_types = ['save', 'check']
# A properly formatted command will have the skill and either 'check' or
# 'save' followed by an optional advantage/disadvantage
skill = variant = advantage_status = ''
reroll = False
if len(args) == 2:
skill, variant = args
elif len(args) == 3:
skill, variant, advantage_status = args
reroll = True
if advantage_status not in advantage_types + disadvantage_types:
print('Error: third argument must be one of:', advantage_types + disadvantage_types)
return
else:
print('Error: expecting two or three arguments')
return
if variant not in check_types:
print('Error: second argument must be one of:', check_types)
return
# Check for proficiency
modifier = get_modifier(int(character.traits[skill]))
if (variant == 'save' and skill in character.traits['save_proficiencies']) \
or (variant == 'check' and skill in character.traits['check_proficiencies']):
modifier += int(character.traits['proficiency'])
res, nat_res = utils.skill_check(modifier)
# Roll again for advantage or disadvantage
if reroll:
re_res, re_nat_res = utils.skill_check(modifier)
if advantage_status in advantage_types:
res, nat_res = max(res, re_res), max(nat_res, re_nat_res)
else:
res, nat_res = min(res, re_res), min(nat_res, re_nat_res)
print('%s check rolled a %d with a natural %d' % (skill, res, nat_res))
def deal_damage(args, character, crit_mul):
'''
Function that calculates damage for a given roll, to include multiple
types of damage/dice
Assumes input will be a comma seperated list of dice groups and an optional
corresponding comma seperated list of skill modifiers. For instance:
./roller damage 3d4,2d6,1d8 strength,charisma,strength or
./roller damage 3d4,2d6
where the second defaults to strength for all values
'''
# Variables for tracking damage characteristics
dice_list = []
stat_list = []
if len(args) == 1:
dice_list = args[0].split(',')
stat_list = ['strength'] * len(dice_list)
elif len(args) == 2:
dice_list = args[0].split(',')
stat_list = args[1].split(',')
if len(stat_list) != len(dice_list):
print('Error: if providing a stat modifier list, you must provide the ' \
'same number of modifiers as the dice list')
return
else:
print('Error: expected one or two arguments')
return
# Calculate total damage
total = 0
for die, stat in zip(dice_list, stat_list):
quantity, die_type = die.split('d')
modifier = get_modifier(int(character.traits[stat]))
total += utils.damage_roll(int(quantity), int(die_type), modifier, crit_mul)
print('Rolled a total of %d damage' % total)
def process_args(args, character='character.txt'):
'''
Function that processes arguments, allowing for a more free-form input
like ./roller con save
The character argument is implemented for recursive calls from helper
functions after determining that there is a different character file
specified.
'''
# Key words for skills
skills = [
'strength', 'str',
'dexterity', 'dex',
'constitution', 'con',
'intelligence', 'int',
'wisdom', 'wis',
'charisma', 'cha',
'athletics', 'ath',
'acrobatics', 'acro',
'slight of hand', 'slightofhand', 'slight',
'stealth',
'arcana',
'history', 'hist',
'investigation', 'invest',
'nature', 'nat',
'religion', 'rel',
'animal handling', 'animalhandling', 'anim',
'insight', 'ins',
'medicine', 'med',
'perception', 'per',
'survival', 'sur',
'deception', 'dec',
'intimidation', 'intim',
'performance', 'perform', 'perf',
'persuasion', 'persuade', 'pers'
]
# Key words for top level commands
commands = [
'attack',
'damage',
'critical',
'autogen',
'gen'
]
first = args[0]
# Determine if this is a skill check of some sort
if first in skills:
player_char = utils.Character.from_file(character)
handle_skill(args, player_char)
# Otherwise it is a command
elif first in commands:
player_char = utils.Character.from_file(character)
if first == 'attack':
perform_attack(args[1::], player_char)
elif first == 'damage':
deal_damage(args[1::], player_char, 1)
elif first == 'critical':
deal_damage(args[1::], player_char, 2)
elif first == 'gen':
print('Pending implementation')
elif first == 'autogen':
print('Pending implementation')
else:
pass
else:
print("Error: command %s is invalid" % first)
def main():
'''
Main input loop before calling handler functions
'''
process_args(sys.argv[1::], 'character.txt')
if __name__ == '__main__':
main()