You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

388 lines
12 KiB

  1. from flask_sqlalchemy import SQLAlchemy
  2. from ..models import db, BaseModel
  3. class CharacterClass(BaseModel):
  4. __tablename__ = 'character_class'
  5. name = db.Column(db.String(50), unique=True, nullable=False)
  6. spellcaster = db.Column(db.String(10))
  7. bucket = db.Column(db.String(50))
  8. frequency_modifier = db.Column(db.Integer, default=1)
  9. prime_requisite = db.Column(db.String(3))
  10. hit_die_size = db.Column(db.Integer)
  11. maximum_level = db.Column(db.Integer)
  12. armour_modifier = db.Column(db.Integer)
  13. melee_light = db.Column(db.Integer)
  14. melee_medium = db.Column(db.Integer)
  15. melee_heavy = db.Column(db.Integer)
  16. ranged_light = db.Column(db.Integer)
  17. ranged_heavy = db.Column(db.Integer)
  18. def __repr__(self):
  19. return '<CharacterClass: {0}>'.format(self.name)
  20. @property
  21. def is_divine_spellcaster(self):
  22. return self.spellcaster == 'Divine'
  23. @property
  24. def is_arcane_spellcaster(self):
  25. return self.spellcaster == 'Arcane'
  26. class ClassLevelProgression(BaseModel):
  27. __tablename__ = 'class_progression'
  28. level = db.Column(db.Integer)
  29. attack_throw = db.Column(db.Integer)
  30. save_petrification_paralysis = db.Column(db.Integer)
  31. save_poison_death = db.Column(db.Integer)
  32. save_blast_breath = db.Column(db.Integer)
  33. save_staffs_wands = db.Column(db.Integer)
  34. save_spells = db.Column(db.Integer)
  35. spellslots1 = db.Column(db.Integer)
  36. spellslots2 = db.Column(db.Integer)
  37. spellslots3 = db.Column(db.Integer)
  38. spellslots4 = db.Column(db.Integer)
  39. spellslots5 = db.Column(db.Integer)
  40. spellslots6 = db.Column(db.Integer)
  41. guild_id = db.Column(db.Integer, db.ForeignKey('character_class.id'), nullable=False)
  42. guild = db.relationship('CharacterClass', backref=db.backref('progressions', lazy=True))
  43. def spell_slots(self, level=None):
  44. if level > self.guild.maximum_level:
  45. level = self.guild.maximum_level
  46. spellslots = [
  47. self.spellslots1,
  48. self.spellslots2,
  49. self.spellslots3,
  50. self.spellslots4,
  51. self.spellslots5,
  52. self.spellslots6
  53. ]
  54. if level:
  55. return spellslots[level - 1]
  56. return spellslots
  57. def __repr__(self):
  58. return '<LevelProgression: {0} {1}>'.format(self.level, self.guild.name)
  59. class Spell(BaseModel):
  60. __tablename__ = 'spells'
  61. name = db.Column(db.String(50), unique=True, nullable=False)
  62. range = db.Column(db.String(50))
  63. duration = db.Column(db.String(50))
  64. arcane = db.Column(db.Integer, nullable=False)
  65. divine = db.Column(db.Integer, nullable=False)
  66. description = db.Column(db.Text(1000), nullable=False)
  67. def __repr__(self):
  68. return '<Spell: {0} ({1})>'.format(self.name, self.school)
  69. def level_for(self, npc):
  70. return self.arcane if npc.is_arcane_spellcaster else self.divine
  71. @property
  72. def school(self):
  73. if self.is_arcane and self.is_divine:
  74. return 'Multi'
  75. if self.is_arcane:
  76. return 'Arcane'
  77. if self.is_divine:
  78. return 'Divine'
  79. @property
  80. def is_divine(self):
  81. return bool(self.divine and self.divine > 0)
  82. @property
  83. def is_arcane(self):
  84. return bool(self.arcane and self.arcane > 0)
  85. @property
  86. def roll20_format(self):
  87. spell_dict = {
  88. 'id': self.id,
  89. 'name': self.name,
  90. 'range': self.range,
  91. 'duration': self.duration,
  92. 'divine': self.divine,
  93. 'is_divine': self.is_divine,
  94. 'arcane': self.arcane,
  95. 'is_arcane': self.is_arcane,
  96. 'description': self.description #.replace('"', '\\"').replace("'", "\\'")
  97. }
  98. return spell_dict
  99. class EquipmentArmour(BaseModel):
  100. __tablename__ = 'eq_armour'
  101. name = db.Column(db.String(50), unique=True, nullable=False)
  102. gp_value = db.Column(db.Integer, nullable=False)
  103. ac_mod = db.Column(db.Integer, nullable=False)
  104. def __repr__(self):
  105. return '<EquipmentArmour: {0}>'.format(self.name)
  106. class EquipmentRangedWeapon(BaseModel):
  107. __tablename__ = 'eq_ranged_wep'
  108. name = db.Column(db.String(50), unique=True, nullable=False)
  109. bucket = db.Column(db.String(20), nullable=False)
  110. gp_value = db.Column(db.Integer, nullable=False)
  111. damage_die = db.Column(db.String(10), nullable=False)
  112. def __repr__(self):
  113. return '<EquipmentRangedWeapon: {0}>'.format(self.name)
  114. class EquipmentMeleeWeapon(BaseModel):
  115. __tablename__ = 'eq_melee_wep'
  116. name = db.Column(db.String(50), unique=False, nullable=False)
  117. bucket = db.Column(db.String(20), nullable=False)
  118. gp_value = db.Column(db.Integer, nullable=False)
  119. damage_die = db.Column(db.String(10), nullable=False)
  120. two_handed = db.Column(db.Boolean, nullable=False)
  121. def __repr__(self):
  122. return '<EquipmentMeleeWeapon: {0}>'.format(self.name)
  123. class CharacterNPC(BaseModel):
  124. __tablename__ = 'npcs'
  125. name = db.Column(db.String(50), unique=False, nullable=True)
  126. level = db.Column(db.Integer, nullable=False)
  127. alignment = db.Column(db.String(20), unique=False, nullable=False)
  128. hit_points = db.Column(db.Integer, nullable=False)
  129. strength = db.Column(db.Integer, nullable=False)
  130. intelligence = db.Column(db.Integer, nullable=False)
  131. wisdom = db.Column(db.Integer, nullable=False)
  132. dexterity = db.Column(db.Integer, nullable=False)
  133. constitution = db.Column(db.Integer, nullable=False)
  134. charisma = db.Column(db.Integer, nullable=False)
  135. spells = db.Column(db.String(200))
  136. guild_id = db.Column(db.Integer, db.ForeignKey('character_class.id'), nullable=False)
  137. guild = db.relationship('CharacterClass', backref=db.backref('npcs', lazy=True))
  138. melee_id = db.Column(db.Integer, db.ForeignKey('eq_melee_wep.id'), nullable=False)
  139. melee = db.relationship('EquipmentMeleeWeapon')
  140. ranged_id = db.Column(db.Integer, db.ForeignKey('eq_ranged_wep.id'), nullable=True)
  141. ranged = db.relationship('EquipmentRangedWeapon')
  142. armour_id = db.Column(db.Integer, db.ForeignKey('eq_armour.id'), nullable=False)
  143. armour = db.relationship('EquipmentArmour')
  144. def __repr__(self):
  145. return '<CharacterNPC: {0}>'.format(self.name)
  146. @staticmethod
  147. def calculate_attr_mod(attr):
  148. mod = 0
  149. if attr <= 3:
  150. mod = -3
  151. elif attr >= 4 and attr <= 5:
  152. mod = -2
  153. elif attr >= 6 and attr <= 8:
  154. mod = -1
  155. elif attr >= 9 and attr <= 12:
  156. mod = 0
  157. elif attr >= 13 and attr <= 15:
  158. mod = 1
  159. elif attr >= 16 and attr <= 16:
  160. mod = 2
  161. elif attr >= 18:
  162. mod = 3
  163. return mod
  164. def spell_slots(self, level=None):
  165. if level > self.guild.maximum_level:
  166. level = self.guild.maximum_level
  167. return self.current_progression.spell_slots(level)
  168. def spells_known(self, level=None):
  169. if level:
  170. if level > self.guild.maximum_level:
  171. level = self.guild.maximum_level
  172. # Return for a specific level
  173. if self.is_arcane_spellcaster:
  174. return (self.spell_slots(level) + self.wis_mod)
  175. return self.spell_slots(level)
  176. # Return for all levels
  177. if self.is_arcane_spellcaster:
  178. return [self.spell_slots(x) + self.wis_mod for x in range(1,6)]
  179. return [self.spell_slots(x) for x in range(1,6)]
  180. def spell_list(self, level=None):
  181. spell_ids = self.spells.split(',')
  182. spells = Spell.query.filter(Spell.id.in_(spell_ids)).all()
  183. spells_by_level = {}
  184. for spell in spells:
  185. spells_by_level.setdefault(spell.level_for(self), []).append(spell)
  186. return spells_by_level
  187. @property
  188. def roll20_format(self):
  189. npc_dict = {
  190. 'name': self.name,
  191. 'level': self.level,
  192. 'hp': self.hit_points,
  193. 'guild': self.guild.name,
  194. 'alignment': self.alignment,
  195. "attack_throw": self.attack_throw,
  196. 'attributes': {
  197. 'str': self.strength,
  198. 'int': self.intelligence,
  199. 'wis': self.wisdom,
  200. 'dex': self.dexterity,
  201. 'con': self.constitution,
  202. 'chr': self.charisma
  203. },
  204. 'attribute_mods': {
  205. 'str': self.str_mod,
  206. 'int': self.int_mod,
  207. 'wis': self.wis_mod,
  208. 'dex': self.dex_mod,
  209. 'con': self.con_mod,
  210. 'chr': self.chr_mod
  211. },
  212. "saves": {
  213. "pp": self.save_pp,
  214. "pd": self.save_pd,
  215. "bb": self.save_bb,
  216. "sw": self.save_sw,
  217. "sp": self.save_sp
  218. },
  219. 'armour': {
  220. 'name': self.armour.name,
  221. 'value': self.armour.gp_value,
  222. 'ac_mod': self.armour.ac_mod
  223. },
  224. 'melee': {
  225. 'name': self.melee.name,
  226. 'value': self.melee.gp_value,
  227. 'damage': self.melee.damage_die,
  228. 'throw_mod': 0,
  229. 'two_handed': self.melee.two_handed
  230. }
  231. }
  232. if self.ranged:
  233. npc_dict['ranged'] = {
  234. 'name': self.ranged.name,
  235. 'value': self.ranged.gp_value,
  236. 'damage': self.ranged.damage_die,
  237. 'throw_mod': 0
  238. }
  239. return npc_dict
  240. @property
  241. def str(self):
  242. return self.strength
  243. @property
  244. def int(self):
  245. return self.intelligence
  246. @property
  247. def wis(self):
  248. return self.wisdom
  249. @property
  250. def dex(self):
  251. return self.dexterity
  252. @property
  253. def con(self):
  254. return self.constitution
  255. @property
  256. def chr(self):
  257. return self.charisma
  258. @property
  259. def str_mod(self):
  260. return self.calculate_attr_mod(self.strength)
  261. @property
  262. def int_mod(self):
  263. return self.calculate_attr_mod(self.intelligence)
  264. @property
  265. def wis_mod(self):
  266. return self.calculate_attr_mod(self.wisdom)
  267. @property
  268. def dex_mod(self):
  269. return self.calculate_attr_mod(self.dexterity)
  270. @property
  271. def con_mod(self):
  272. return self.calculate_attr_mod(self.constitution)
  273. @property
  274. def chr_mod(self):
  275. return self.calculate_attr_mod(self.charisma)
  276. @property
  277. def current_progression(self):
  278. return ClassLevelProgression.query.filter_by(guild_id=self.guild.id, level=self.level).first()
  279. @property
  280. def attack_throw(self):
  281. return self.current_progression.attack_throw
  282. @property
  283. def save_pp(self):
  284. return self.current_progression.save_petrification_paralysis
  285. @property
  286. def save_pd(self):
  287. return self.current_progression.save_petrification_paralysis
  288. @property
  289. def save_bb(self):
  290. return self.current_progression.save_blast_breath
  291. @property
  292. def save_sw(self):
  293. return self.current_progression.save_staffs_wands
  294. @property
  295. def save_sp(self):
  296. return self.current_progression.save_spells
  297. @property
  298. def hp(self):
  299. return self.hit_points
  300. @property
  301. def is_divine_spellcaster(self):
  302. return self.guild.is_divine_spellcaster
  303. @property
  304. def is_arcane_spellcaster(self):
  305. return self.guild.is_arcane_spellcaster
  306. def update(self, kwargs):
  307. # Allows us to update like a dict, used for inserting attributes zip
  308. self.__dict__.update(kwargs)
  309. admin_models = [CharacterClass, ClassLevelProgression, EquipmentArmour, EquipmentRangedWeapon, EquipmentMeleeWeapon, CharacterNPC, Spell]