~ryanprior/+junk/python-srd

« back to all changes in this revision

Viewing changes to srd/dnd.py

  • Committer: Ryan Prior
  • Date: 2010-12-10 22:05:08 UTC
  • Revision ID: ryanprior@gmail.com-20101210220508-8cs3nu9454hksvv2
initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from random import randint
 
2
from math import floor, ceil
 
3
 
 
4
#todo: ranged weapon AC, damage
 
5
#todo: 20 20 is auto confirm
 
6
 
 
7
def roll(dice): # todo: replace jank parsing routine
 
8
  result = 0
 
9
  rolls = [i.strip() for i in dice.split("+")]
 
10
  for i in rolls:
 
11
    roll = i.split("d")
 
12
    if len(roll) > 1:
 
13
      number, die = [int(x) for x in roll]
 
14
      for i in xrange(0, number):
 
15
        result += randint(1, die)
 
16
    else:
 
17
      result += int(roll[0])
 
18
  return result
 
19
 
 
20
class Combatant:
 
21
  def __init__(self,
 
22
               name="Creature",
 
23
               stats = {"str": 10,
 
24
                        "dex": 10,
 
25
                        "con": 10,
 
26
                        "int": 10,
 
27
                        "wis": 10,
 
28
                        "cha": 10,},
 
29
               hitdice="1d4",
 
30
               max_hp=0,
 
31
               bab=0,
 
32
               two_weapon_fighting=0,
 
33
               power_attack=0,
 
34
               two_handed=False,
 
35
               weapons=[],
 
36
               wounds=[],
 
37
               hit_bonuses=[],
 
38
               init_bonuses=[],
 
39
               ac_bonuses=[],
 
40
               dr_bonuses=[]):
 
41
    self.name = name
 
42
    self.stats = stats
 
43
    self.hitdice = hitdice
 
44
    if max_hp == 0:
 
45
      self.max_hp = roll(hitdice)
 
46
    else:
 
47
      self.max_hp = max_hp
 
48
    self.hp = self.max_hp
 
49
    self.bab = bab
 
50
    self.two_weapon_fighting = two_weapon_fighting # 1 for TWF, 2 for ITWF, 3 for STWF, etc for epic
 
51
    self.power_attack = power_attack
 
52
    self.two_handed = two_handed # True if wielding a weapon two-handed
 
53
    self.weapons = weapons # contains Weapon objects
 
54
    self.wounds = wounds # contains Wound objects
 
55
    self.hit_bonuses = hit_bonuses
 
56
    self.init_bonuses = init_bonuses
 
57
    self.ac_bonuses = ac_bonuses # ex: ("+1 dodge", "+4 armor", "+2 shield", "+2 dex", "+1 deflection")
 
58
    self.dr_bonuses = dr_bonuses # ex: ("2/-", "5/good", "5/slashing")
 
59
  def ac(self, touch=False, denied_dex=False):
 
60
    armor_bonus = 0
 
61
    shield_bonus = 0
 
62
    dodge_bonus = 0
 
63
    misc_bonus = 0
 
64
    for i in self.ac_bonuses:
 
65
      bonus = int(i.split()[0])
 
66
      bonus_type = i.split()[1].lower()
 
67
      if bonus_type == "dodge" or bonus_type == "dex":
 
68
        dodge_bonus += bonus
 
69
      elif bonus_type == "armor":
 
70
        if bonus > armor_bonus:
 
71
          armor_bonus = bonus
 
72
      elif bonus_type == "shield":
 
73
        if bonus > shield_bonus:
 
74
          shield_bonus = bonus
 
75
      else:
 
76
        misc_bonus += bonus
 
77
    return (10
 
78
            + (not touch) * (armor_bonus + shield_bonus)
 
79
            + (not denied_dex) * dodge_bonus
 
80
            + misc_bonus)
 
81
  def dr(self, damage_type):
 
82
    best_dr = 0
 
83
    for i in self.dr_bonuses:
 
84
      amt = int(i.split("/")[0])
 
85
      overcome_by = i.split("/")[1].lower()
 
86
      if not overcome_by in damage_type.split():
 
87
        if amt > best_dr:
 
88
          best_dr = amt
 
89
    return best_dr
 
90
  def init(self):
 
91
    total = self.stat_bonus("dex")
 
92
    for i in self.init_bonuses:
 
93
      total += int(i.split()[0])
 
94
    return total
 
95
  def hit_bonus(self):
 
96
    morale_bonus = 0
 
97
    misc_bonus = 0
 
98
    for i in self.hit_bonuses:
 
99
      bonus = int(i.split()[0])
 
100
      bonus_type = i.split()[1].lower()
 
101
      if bonus_type == "morale":
 
102
        if bonus > morale_bonus:
 
103
          morale_bonus = bonus
 
104
      else:
 
105
        misc_bonus += bonus
 
106
    return self.stat_bonus("str") + self.bab - self.power_attack + morale_bonus + misc_bonus
 
107
  def stat_bonus(self, stat):
 
108
    if self.stats[stat] == 10:
 
109
      return 0
 
110
    if self.stats[stat] < 10:
 
111
      return int(floor((self.stats[stat]-10)/2))
 
112
    else:
 
113
      return int(ceil((self.stats[stat]-10)/2))
 
114
  def damage_bonus(self, offhand=False):
 
115
    str_bonus = self.stat_bonus("str")
 
116
    if offhand:
 
117
      str_bonus = floor(str_bonus * 1.5) - str_bonus
 
118
    return str_bonus + self.power_attack
 
119
  def attack(self):
 
120
    a = Attack()
 
121
    a.weapon = self.weapons[0]
 
122
    a.bonus = self.hit_bonus()
 
123
    a.bonusdamage = self.damage_bonus() + self.two_handed * self.damage_bonus(offhand=True)
 
124
    return a
 
125
  def full_attack(self):
 
126
    attacks = []
 
127
    twf_attacks = self.two_weapon_fighting
 
128
    main_weapon = self.weapons[0]
 
129
    offhand_weapon = None
 
130
    if len(self.weapons) > 1:
 
131
      offhand_weapon = self.weapons[1]
 
132
    elif main_weapon.offhand != None:
 
133
      offhand_weapon = main_weapon.offhand 
 
134
    if offhand_weapon:
 
135
      twf_attacks = 1 if not twf_attacks else twf_attacks
 
136
      twf_penalty_mainhand = -6
 
137
      twf_penalty_offhand = -10
 
138
      if offhand_weapon.light:
 
139
        twf_penalty_mainhand += 2
 
140
        twf_penalty_offhand += 2
 
141
      if self.two_weapon_fighting:
 
142
        twf_penalty_mainhand += 2
 
143
        twf_penalty_offhand += 6
 
144
    else:
 
145
      twf_penalty_mainhand = 0
 
146
    for i in xrange(0, self.bab, 5):
 
147
      a = Attack()
 
148
      a.weapon = main_weapon
 
149
      a.bonus = self.hit_bonus() - i + twf_penalty_mainhand
 
150
      a.bonusdamage = self.damage_bonus() + self.two_handed * self.damage_bonus(offhand=True)
 
151
      attacks.append(a)
 
152
      if twf_attacks > 0:
 
153
        b = Attack()
 
154
        b.weapon = offhand_weapon
 
155
        b.bonus = self.hit_bonus() - i + twf_penalty_offhand
 
156
        b.bonusdamage = self.damage_bonus(offhand=True)
 
157
        attacks.append(b)
 
158
        twf_attacks -= 1
 
159
    return tuple(attacks)
 
160
  def __repr__(self):
 
161
    return self.name
 
162
 
 
163
class Wound:
 
164
  def __init__(self, damage=0, dealt_by=None, dealt_with=None):
 
165
    self.damage = damage
 
166
    self.dealt_by = dealt_by
 
167
    self.dealt_with = dealt_with
 
168
 
 
169
class Weapon:
 
170
  def __init__(self,
 
171
               name = "",          # common user-friendly name (ex. "Spiked Armor", not "Armor, Spiked")
 
172
               sort_name = None,   # specify only if different from common name (ex. "Spiked Chain" vs "Chain, Spiked")
 
173
               cost = 0,           # in gold pieces
 
174
               damage = "1d2",     # damage for a weapon suitable for medium-sized creatures
 
175
               size="medium",      # size of creature weapon was created for; modifies damage
 
176
               crit = 20,          # scores critical hit on this roll or better
 
177
               multiplier = 2,     # critical hit damage multiplier
 
178
               weight = 0,         # in pounds
 
179
               damage_types = (),  # ex. ("piercing", "bludgeoning") for morningstar
 
180
               two_handed = False, # indicates whether weapon _must_ be held two-handed
 
181
               light = False,      # indicates whether weapon _cannot_ be held two-handed
 
182
               offhand = None):    # assign weapon object to be used as offhand for double weapons
 
183
    self.name = name
 
184
    if sort_name == None:
 
185
      self.sort_name = self.name
 
186
    else:
 
187
      self.sort_name = sort_name
 
188
    self.cost = cost
 
189
    self.damage = damage
 
190
    self.size = size
 
191
    self.crit = crit
 
192
    self.multiplier = multiplier
 
193
    self.weight = weight
 
194
    self.damage_types = damage_types
 
195
    self.two_handed = two_handed
 
196
    self.light = light
 
197
    self.offhand = offhand
 
198
  def roll_damage(self):
 
199
    return roll(self.damage)
 
200
  def crit_damage(self):
 
201
    damage = 0
 
202
    for i in xrange(1, self.multiplier):
 
203
      damage += self.roll_damage()
 
204
    return damage;
 
205
  def __str__(self):
 
206
    return self.name
 
207
 
 
208
class Attack:
 
209
  def __init__(self, bonus=0, damage=0, bonusdamage=0):
 
210
    self.bonus, self.damage, self.bonusdamage = [bonus, damage, bonusdamage]
 
211
  bonus = None
 
212
  weapon = None
 
213
  bonusdamage = 0
 
214
  confirm_bonus = 0
 
215
  def roll(self, ac):
 
216
    damage = 0
 
217
    result = roll("1d20")
 
218
    if result+self.bonus >= ac or result == 20 and not result == 1:
 
219
      damage += self.roll_damage()
 
220
      if result >= self.weapon.crit:
 
221
        confirm_result = roll("1d20")
 
222
        if confirm_result+self.bonus+self.confirm_bonus > ac or confirm_result == 20 and not confirm_result == 1:
 
223
          damage += self.weapon.crit_damage()
 
224
    wound = Wound(damage=damage, dealt_with = self)
 
225
    return wound
 
226
  def roll_damage(self):
 
227
    return self.weapon.roll_damage() + self.bonusdamage
 
228
  def __repr__(self):
 
229
    return self.weapon.name