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.
401 lines
12 KiB
401 lines
12 KiB
import base64
|
|
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from ..models import db, BaseModel
|
|
|
|
|
|
class CharacterClass(BaseModel):
|
|
__tablename__ = 'character_class'
|
|
|
|
name = db.Column(db.String(50), unique=True, nullable=False)
|
|
spellcaster = db.Column(db.String(10))
|
|
bucket = db.Column(db.String(50))
|
|
frequency_modifier = db.Column(db.Integer, default=1)
|
|
|
|
prime_requisite = db.Column(db.String(3))
|
|
hit_die_size = db.Column(db.Integer)
|
|
maximum_level = db.Column(db.Integer)
|
|
|
|
armour_modifier = db.Column(db.Integer)
|
|
melee_light = db.Column(db.Integer)
|
|
melee_medium = db.Column(db.Integer)
|
|
melee_heavy = db.Column(db.Integer)
|
|
ranged_light = db.Column(db.Integer)
|
|
ranged_heavy = db.Column(db.Integer)
|
|
|
|
def __repr__(self):
|
|
return '<CharacterClass: {0}>'.format(self.name)
|
|
|
|
@property
|
|
def is_divine_spellcaster(self):
|
|
return self.spellcaster == 'Divine'
|
|
|
|
@property
|
|
def is_arcane_spellcaster(self):
|
|
return self.spellcaster == 'Arcane'
|
|
|
|
class ClassLevelProgression(BaseModel):
|
|
__tablename__ = 'class_progression'
|
|
|
|
level = db.Column(db.Integer)
|
|
attack_throw = db.Column(db.Integer)
|
|
|
|
save_petrification_paralysis = db.Column(db.Integer)
|
|
save_poison_death = db.Column(db.Integer)
|
|
save_blast_breath = db.Column(db.Integer)
|
|
save_staffs_wands = db.Column(db.Integer)
|
|
save_spells = db.Column(db.Integer)
|
|
|
|
spellslots1 = db.Column(db.Integer)
|
|
spellslots2 = db.Column(db.Integer)
|
|
spellslots3 = db.Column(db.Integer)
|
|
spellslots4 = db.Column(db.Integer)
|
|
spellslots5 = db.Column(db.Integer)
|
|
spellslots6 = db.Column(db.Integer)
|
|
|
|
guild_id = db.Column(db.Integer, db.ForeignKey('character_class.id'), nullable=False)
|
|
guild = db.relationship('CharacterClass', backref=db.backref('progressions', lazy=True))
|
|
|
|
def spell_slots(self, level=None):
|
|
if level > self.guild.maximum_level:
|
|
level = self.guild.maximum_level
|
|
|
|
spellslots = [
|
|
self.spellslots1,
|
|
self.spellslots2,
|
|
self.spellslots3,
|
|
self.spellslots4,
|
|
self.spellslots5,
|
|
self.spellslots6
|
|
]
|
|
|
|
if level:
|
|
return spellslots[level - 1]
|
|
|
|
return spellslots
|
|
|
|
def __repr__(self):
|
|
return '<LevelProgression: {0} {1}>'.format(self.level, self.guild.name)
|
|
|
|
|
|
class Spell(BaseModel):
|
|
__tablename__ = 'spells'
|
|
|
|
name = db.Column(db.String(50), unique=True, nullable=False)
|
|
range = db.Column(db.String(50))
|
|
duration = db.Column(db.String(50))
|
|
arcane = db.Column(db.Integer, nullable=False)
|
|
divine = db.Column(db.Integer, nullable=False)
|
|
description = db.Column(db.Text(1000), nullable=False)
|
|
|
|
def __repr__(self):
|
|
return '<Spell: {0} ({1})>'.format(self.name, self.school)
|
|
|
|
def level_for(self, npc):
|
|
return self.arcane if npc.is_arcane_spellcaster else self.divine
|
|
|
|
@property
|
|
def school(self):
|
|
if self.is_arcane and self.is_divine:
|
|
return 'Multi'
|
|
if self.is_arcane:
|
|
return 'Arcane'
|
|
if self.is_divine:
|
|
return 'Divine'
|
|
|
|
@property
|
|
def is_divine(self):
|
|
return bool(self.divine and self.divine > 0)
|
|
|
|
@property
|
|
def is_arcane(self):
|
|
return bool(self.arcane and self.arcane > 0)
|
|
|
|
@property
|
|
def dom_id(self):
|
|
return self.name.lower().replace("*", "").replace("'", "").replace(",", "").replace(" ", "_")
|
|
|
|
@property
|
|
def roll20_format(self):
|
|
spell_dict = {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'range': self.range,
|
|
'duration': self.duration,
|
|
'divine': self.divine,
|
|
'is_divine': self.is_divine,
|
|
'arcane': self.arcane,
|
|
'is_arcane': self.is_arcane,
|
|
'description': base64.b64encode(self.description.encode('ascii')).decode('ascii')
|
|
}
|
|
return spell_dict
|
|
|
|
class EquipmentArmour(BaseModel):
|
|
__tablename__ = 'eq_armour'
|
|
|
|
name = db.Column(db.String(50), unique=True, nullable=False)
|
|
gp_value = db.Column(db.Integer, nullable=False)
|
|
ac_mod = db.Column(db.Integer, nullable=False)
|
|
|
|
def __repr__(self):
|
|
return '<EquipmentArmour: {0}>'.format(self.name)
|
|
|
|
class EquipmentRangedWeapon(BaseModel):
|
|
__tablename__ = 'eq_ranged_wep'
|
|
|
|
name = db.Column(db.String(50), unique=True, nullable=False)
|
|
bucket = db.Column(db.String(20), nullable=False)
|
|
gp_value = db.Column(db.Integer, nullable=False)
|
|
damage_die = db.Column(db.String(10), nullable=False)
|
|
|
|
def __repr__(self):
|
|
return '<EquipmentRangedWeapon: {0}>'.format(self.name)
|
|
|
|
class EquipmentMeleeWeapon(BaseModel):
|
|
__tablename__ = 'eq_melee_wep'
|
|
|
|
name = db.Column(db.String(50), unique=False, nullable=False)
|
|
bucket = db.Column(db.String(20), nullable=False)
|
|
gp_value = db.Column(db.Integer, nullable=False)
|
|
damage_die = db.Column(db.String(10), nullable=False)
|
|
two_handed = db.Column(db.Boolean, nullable=False)
|
|
|
|
def __repr__(self):
|
|
return '<EquipmentMeleeWeapon: {0}>'.format(self.name)
|
|
|
|
class CharacterNPC(BaseModel):
|
|
__tablename__ = 'npcs'
|
|
|
|
name = db.Column(db.String(50), unique=False, nullable=True)
|
|
level = db.Column(db.Integer, nullable=False)
|
|
alignment = db.Column(db.String(20), unique=False, nullable=False)
|
|
hit_points = db.Column(db.Integer, nullable=False)
|
|
|
|
strength = db.Column(db.Integer, nullable=False)
|
|
intelligence = db.Column(db.Integer, nullable=False)
|
|
wisdom = db.Column(db.Integer, nullable=False)
|
|
dexterity = db.Column(db.Integer, nullable=False)
|
|
constitution = db.Column(db.Integer, nullable=False)
|
|
charisma = db.Column(db.Integer, nullable=False)
|
|
|
|
spells = db.Column(db.String(200))
|
|
|
|
guild_id = db.Column(db.Integer, db.ForeignKey('character_class.id'), nullable=False)
|
|
guild = db.relationship('CharacterClass', backref=db.backref('npcs', lazy=True))
|
|
|
|
melee_id = db.Column(db.Integer, db.ForeignKey('eq_melee_wep.id'), nullable=False)
|
|
melee = db.relationship('EquipmentMeleeWeapon')
|
|
|
|
ranged_id = db.Column(db.Integer, db.ForeignKey('eq_ranged_wep.id'), nullable=True)
|
|
ranged = db.relationship('EquipmentRangedWeapon')
|
|
|
|
armour_id = db.Column(db.Integer, db.ForeignKey('eq_armour.id'), nullable=False)
|
|
armour = db.relationship('EquipmentArmour')
|
|
|
|
def __repr__(self):
|
|
return '<CharacterNPC: {0}>'.format(self.name)
|
|
|
|
@staticmethod
|
|
def calculate_attr_mod(attr):
|
|
mod = 0
|
|
if attr <= 3:
|
|
mod = -3
|
|
elif attr >= 4 and attr <= 5:
|
|
mod = -2
|
|
elif attr >= 6 and attr <= 8:
|
|
mod = -1
|
|
elif attr >= 9 and attr <= 12:
|
|
mod = 0
|
|
elif attr >= 13 and attr <= 15:
|
|
mod = 1
|
|
elif attr >= 16 and attr <= 16:
|
|
mod = 2
|
|
elif attr >= 18:
|
|
mod = 3
|
|
return mod
|
|
|
|
def spell_slots(self, level=None):
|
|
if level > self.guild.maximum_level:
|
|
level = self.guild.maximum_level
|
|
return self.current_progression.spell_slots(level)
|
|
|
|
def spells_known(self, level=None):
|
|
if level:
|
|
if level > self.guild.maximum_level:
|
|
level = self.guild.maximum_level
|
|
# Return for a specific level
|
|
if self.is_arcane_spellcaster:
|
|
return (self.spell_slots(level) + self.wis_mod)
|
|
return self.spell_slots(level)
|
|
|
|
# Return for all levels
|
|
if self.is_arcane_spellcaster:
|
|
return [self.spell_slots(x) + self.wis_mod for x in range(1,6)]
|
|
return [self.spell_slots(x) for x in range(1,6)]
|
|
|
|
def spell_list(self, level=None):
|
|
spell_ids = self.spells.split(',')
|
|
spells = Spell.query.filter(Spell.id.in_(spell_ids)).all()
|
|
|
|
spells_by_level = {}
|
|
for spell in spells:
|
|
spells_by_level.setdefault(spell.level_for(self), []).append(spell)
|
|
|
|
return spells_by_level
|
|
|
|
@property
|
|
def roll20_format(self):
|
|
npc_dict = {
|
|
'name': self.name,
|
|
'level': self.level,
|
|
'hp': self.hit_points,
|
|
'guild': self.guild.name,
|
|
'alignment': self.alignment,
|
|
"attack_throw": self.attack_throw,
|
|
'attributes': {
|
|
'str': self.strength,
|
|
'int': self.intelligence,
|
|
'wis': self.wisdom,
|
|
'dex': self.dexterity,
|
|
'con': self.constitution,
|
|
'chr': self.charisma
|
|
},
|
|
'attribute_mods': {
|
|
'str': self.str_mod,
|
|
'int': self.int_mod,
|
|
'wis': self.wis_mod,
|
|
'dex': self.dex_mod,
|
|
'con': self.con_mod,
|
|
'chr': self.chr_mod
|
|
},
|
|
"saves": {
|
|
"pp": self.save_pp,
|
|
"pd": self.save_pd,
|
|
"bb": self.save_bb,
|
|
"sw": self.save_sw,
|
|
"sp": self.save_sp
|
|
},
|
|
'armour': {
|
|
'name': self.armour.name,
|
|
'value': self.armour.gp_value,
|
|
'ac_mod': self.armour.ac_mod
|
|
},
|
|
'melee': {
|
|
'name': self.melee.name,
|
|
'value': self.melee.gp_value,
|
|
'damage': self.melee.damage_die,
|
|
'throw_mod': 0,
|
|
'two_handed': self.melee.two_handed
|
|
}
|
|
}
|
|
|
|
if self.guild.is_divine_spellcaster or self.guild.is_arcane_spellcaster:
|
|
npc_dict['spells'] = []
|
|
spell_list = self.spell_list()
|
|
for level in spell_list:
|
|
for spell in spell_list[level]:
|
|
npc_dict['spells'].append(spell.roll20_format)
|
|
|
|
if self.ranged:
|
|
npc_dict['ranged'] = {
|
|
'name': self.ranged.name,
|
|
'value': self.ranged.gp_value,
|
|
'damage': self.ranged.damage_die,
|
|
'throw_mod': 0
|
|
}
|
|
|
|
return npc_dict
|
|
|
|
@property
|
|
def str(self):
|
|
return self.strength
|
|
|
|
@property
|
|
def int(self):
|
|
return self.intelligence
|
|
|
|
@property
|
|
def wis(self):
|
|
return self.wisdom
|
|
|
|
@property
|
|
def dex(self):
|
|
return self.dexterity
|
|
|
|
@property
|
|
def con(self):
|
|
return self.constitution
|
|
|
|
@property
|
|
def chr(self):
|
|
return self.charisma
|
|
|
|
@property
|
|
def str_mod(self):
|
|
return self.calculate_attr_mod(self.strength)
|
|
|
|
@property
|
|
def int_mod(self):
|
|
return self.calculate_attr_mod(self.intelligence)
|
|
|
|
@property
|
|
def wis_mod(self):
|
|
return self.calculate_attr_mod(self.wisdom)
|
|
|
|
@property
|
|
def dex_mod(self):
|
|
return self.calculate_attr_mod(self.dexterity)
|
|
|
|
@property
|
|
def con_mod(self):
|
|
return self.calculate_attr_mod(self.constitution)
|
|
|
|
@property
|
|
def chr_mod(self):
|
|
return self.calculate_attr_mod(self.charisma)
|
|
|
|
@property
|
|
def current_progression(self):
|
|
return ClassLevelProgression.query.filter_by(guild_id=self.guild.id, level=self.level).first()
|
|
|
|
@property
|
|
def attack_throw(self):
|
|
return self.current_progression.attack_throw
|
|
|
|
@property
|
|
def save_pp(self):
|
|
return self.current_progression.save_petrification_paralysis
|
|
|
|
@property
|
|
def save_pd(self):
|
|
return self.current_progression.save_petrification_paralysis
|
|
|
|
@property
|
|
def save_bb(self):
|
|
return self.current_progression.save_blast_breath
|
|
|
|
@property
|
|
def save_sw(self):
|
|
return self.current_progression.save_staffs_wands
|
|
|
|
@property
|
|
def save_sp(self):
|
|
return self.current_progression.save_spells
|
|
|
|
@property
|
|
def hp(self):
|
|
return self.hit_points
|
|
|
|
@property
|
|
def is_divine_spellcaster(self):
|
|
return self.guild.is_divine_spellcaster
|
|
|
|
@property
|
|
def is_arcane_spellcaster(self):
|
|
return self.guild.is_arcane_spellcaster
|
|
|
|
def update(self, kwargs):
|
|
# Allows us to update like a dict, used for inserting attributes zip
|
|
self.__dict__.update(kwargs)
|
|
|
|
admin_models = [CharacterClass, ClassLevelProgression, EquipmentArmour, EquipmentRangedWeapon, EquipmentMeleeWeapon, CharacterNPC, Spell]
|