Template updates, API
This commit is contained in:
parent
9811a6abf9
commit
aebe890e3e
@ -1,6 +1,6 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_admin import Admin
|
|
||||||
from flask_admin.contrib.sqla import ModelView
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@ -28,10 +28,22 @@ def create_app():
|
|||||||
app.cli.add_command(npc_cli)
|
app.cli.add_command(npc_cli)
|
||||||
|
|
||||||
# Load the Admin views
|
# Load the Admin views
|
||||||
|
from flask_admin import Admin
|
||||||
|
from flask_admin.contrib.sqla import ModelView
|
||||||
admin = Admin(app, name='acks', template_mode='bootstrap3')
|
admin = Admin(app, name='acks', template_mode='bootstrap3')
|
||||||
|
|
||||||
from acks.npc.models import admin_models as npc_admin_models
|
from acks.npc.models import admin_models as npc_admin_models
|
||||||
for mdl in npc_admin_models:
|
for mdl in npc_admin_models:
|
||||||
admin.add_view(ModelView(mdl, db.session))
|
admin.add_view(ModelView(mdl, db.session))
|
||||||
|
|
||||||
|
# Initialize API and load resources
|
||||||
|
from flask_potion import Api, ModelResource
|
||||||
|
api = Api(app)
|
||||||
|
# from acks.api import api
|
||||||
|
# api.init_app(app)
|
||||||
|
|
||||||
|
from acks.npc.api import resources as npc_resources
|
||||||
|
for resource in npc_resources:
|
||||||
|
api.add_resource(resource)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
8
acks/api.py
Normal file
8
acks/api.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from flask_potion import Api, ModelResource
|
||||||
|
|
||||||
|
|
||||||
|
# api = Api()
|
||||||
|
|
||||||
|
class BaseModelResource(ModelResource):
|
||||||
|
class Meta:
|
||||||
|
exclude_fields = ['created_at']
|
38
acks/npc/api.py
Normal file
38
acks/npc/api.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from ..api import BaseModelResource
|
||||||
|
from .models import (
|
||||||
|
CharacterClass,
|
||||||
|
EquipmentArmour,
|
||||||
|
EquipmentMeleeWeapon,
|
||||||
|
EquipmentRangedWeapon,
|
||||||
|
CharacterNPC
|
||||||
|
)
|
||||||
|
|
||||||
|
class CharacterClassResource(BaseModelResource):
|
||||||
|
class Meta(BaseModelResource.Meta):
|
||||||
|
model = CharacterClass
|
||||||
|
name = 'classes'
|
||||||
|
|
||||||
|
class EquipmentArmourResource(BaseModelResource):
|
||||||
|
class Meta(BaseModelResource.Meta):
|
||||||
|
model = EquipmentArmour
|
||||||
|
|
||||||
|
class EquipmentMeleeWeaponResource(BaseModelResource):
|
||||||
|
class Meta(BaseModelResource.Meta):
|
||||||
|
model = EquipmentMeleeWeapon
|
||||||
|
|
||||||
|
class EquipmentRangedWeaponResource(BaseModelResource):
|
||||||
|
class Meta(BaseModelResource.Meta):
|
||||||
|
model = EquipmentRangedWeapon
|
||||||
|
|
||||||
|
class CharacterNPCResource(BaseModelResource):
|
||||||
|
class Meta(BaseModelResource.Meta):
|
||||||
|
model = CharacterNPC
|
||||||
|
|
||||||
|
|
||||||
|
resources = [
|
||||||
|
CharacterClassResource,
|
||||||
|
EquipmentArmourResource,
|
||||||
|
EquipmentMeleeWeaponResource,
|
||||||
|
EquipmentRangedWeaponResource,
|
||||||
|
CharacterNPCResource
|
||||||
|
]
|
@ -11,6 +11,7 @@ def populate_npc_database():
|
|||||||
import csv
|
import csv
|
||||||
from .models import (
|
from .models import (
|
||||||
CharacterClass,
|
CharacterClass,
|
||||||
|
ClassLevelProgression,
|
||||||
EquipmentArmour,
|
EquipmentArmour,
|
||||||
EquipmentRangedWeapon,
|
EquipmentRangedWeapon,
|
||||||
EquipmentMeleeWeapon
|
EquipmentMeleeWeapon
|
||||||
@ -39,4 +40,11 @@ def populate_npc_database():
|
|||||||
wep.two_handed = (wep.two_handed == 'True')
|
wep.two_handed = (wep.two_handed == 'True')
|
||||||
db.session.bulk_save_objects(melee_weps)
|
db.session.bulk_save_objects(melee_weps)
|
||||||
|
|
||||||
|
# Level Progressions
|
||||||
|
progressions = load_csv_data('default_progression.csv', ClassLevelProgression)
|
||||||
|
classes = {c.name: c.id for c in CharacterClass.query.all()}
|
||||||
|
for prog in progressions:
|
||||||
|
prog.guild_id = classes[prog.guild_id]
|
||||||
|
db.session.bulk_save_objects(progressions)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -23,6 +23,25 @@ class CharacterClass(BaseModel):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<CharacterClass {0}>'.format(self.name)
|
return '<CharacterClass {0}>'.format(self.name)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
guild_id = db.Column(db.Integer, db.ForeignKey('character_class.id'), nullable=False)
|
||||||
|
guild = db.relationship('CharacterClass', backref=db.backref('progressions', lazy=True))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<LevelProgression {0} {1}>'.format(self.level, self.guild.name)
|
||||||
|
|
||||||
|
|
||||||
class EquipmentArmour(BaseModel):
|
class EquipmentArmour(BaseModel):
|
||||||
__tablename__ = 'eq_armour'
|
__tablename__ = 'eq_armour'
|
||||||
|
|
||||||
@ -61,18 +80,188 @@ class CharacterNPC(BaseModel):
|
|||||||
|
|
||||||
name = db.Column(db.String(50), unique=False, nullable=True)
|
name = db.Column(db.String(50), unique=False, nullable=True)
|
||||||
level = db.Column(db.Integer, nullable=False)
|
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)
|
hit_points = db.Column(db.Integer, nullable=False)
|
||||||
|
|
||||||
guild_id = db.Column(db.Integer, db.ForeignKey('guild.id'), 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)
|
||||||
|
|
||||||
|
guild_id = db.Column(db.Integer, db.ForeignKey('character_class.id'), nullable=False)
|
||||||
guild = db.relationship('CharacterClass', backref=db.backref('npcs', lazy=True))
|
guild = db.relationship('CharacterClass', backref=db.backref('npcs', lazy=True))
|
||||||
|
|
||||||
melee_id = db.Column(db.Integer, db.ForeignKey('melee.id'), nullable=False)
|
melee_id = db.Column(db.Integer, db.ForeignKey('eq_melee_wep.id'), nullable=False)
|
||||||
melee = db.relationship('EquipmentMeleeWeapon')
|
melee = db.relationship('EquipmentMeleeWeapon')
|
||||||
|
|
||||||
ranged_id = db.Column(db.Integer, db.ForeignKey('ranged.id'), nullable=False)
|
ranged_id = db.Column(db.Integer, db.ForeignKey('eq_ranged_wep.id'), nullable=True)
|
||||||
ranged = db.relationship('EquipmentRangedWeapon')
|
ranged = db.relationship('EquipmentRangedWeapon')
|
||||||
|
|
||||||
|
armour_id = db.Column(db.Integer, db.ForeignKey('eq_armour.id'), nullable=False)
|
||||||
|
armour = db.relationship('EquipmentArmour')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<CharacterNPC {0}>'.format(self.name)
|
return '<CharacterNPC {0}>'.format(self.name)
|
||||||
|
|
||||||
admin_models = [CharacterClass, EquipmentArmour, EquipmentRangedWeapon, EquipmentMeleeWeapon, CharacterNPC]
|
@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
|
||||||
|
|
||||||
|
@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.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
|
||||||
|
|
||||||
|
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]
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
from random import randint, choice
|
import requests
|
||||||
|
|
||||||
|
from math import floor, ceil
|
||||||
|
from random import randint, choice, shuffle
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
CharacterClass,
|
CharacterClass,
|
||||||
@ -12,8 +15,11 @@ from .models import (
|
|||||||
def number_encountered():
|
def number_encountered():
|
||||||
return (randint(1, 4) + 2)
|
return (randint(1, 4) + 2)
|
||||||
|
|
||||||
def npc_class():
|
def npc_class(excluded_buckets):
|
||||||
classes = [c for cls in CharacterClass.query.all() for c in ([cls] * cls.frequency_modifier)]
|
if excluded_buckets:
|
||||||
|
classes = [c for cls in CharacterClass.query.filter(CharacterClass.bucket.notin_(excluded_buckets)).all() for c in ([cls] * cls.frequency_modifier)]
|
||||||
|
else:
|
||||||
|
classes = [c for cls in CharacterClass.query.all() for c in ([cls] * cls.frequency_modifier)]
|
||||||
return choice(classes)
|
return choice(classes)
|
||||||
|
|
||||||
def npc_alignment():
|
def npc_alignment():
|
||||||
@ -46,7 +52,7 @@ def npc_baselevel(base_level):
|
|||||||
return base_level
|
return base_level
|
||||||
|
|
||||||
def npc_abilities():
|
def npc_abilities():
|
||||||
ability_list = ['str', 'int', 'wis', 'dex', 'con', 'chr']
|
ability_list = ['strength', 'intelligence', 'wisdom', 'dexterity', 'constitution', 'charisma']
|
||||||
abilities = [(randint(1, 6) + randint(1, 6) + randint(1, 6)) for x in range(0,6)]
|
abilities = [(randint(1, 6) + randint(1, 6) + randint(1, 6)) for x in range(0,6)]
|
||||||
return zip(ability_list, abilities)
|
return zip(ability_list, abilities)
|
||||||
|
|
||||||
@ -88,38 +94,62 @@ def select_ranged_weapon(guild, data):
|
|||||||
selection = randint(0, len(weapons) - 1)
|
selection = randint(0, len(weapons) - 1)
|
||||||
return weapons[selection]
|
return weapons[selection]
|
||||||
|
|
||||||
def generate_npc(base_level, data):
|
def calc_hp(conmod, hit_die_size, level):
|
||||||
guild = npc_class()
|
hp = 0
|
||||||
|
hitdice = [randint(1, hit_die_size) for x in range(0, level)]
|
||||||
npc = {}
|
|
||||||
npc['guild'] = guild.name
|
|
||||||
|
|
||||||
npc['level'] = npc_baselevel(base_level)
|
|
||||||
npc.update(npc_abilities())
|
|
||||||
|
|
||||||
npc['hp'] = 0
|
|
||||||
conmod = attribute_mod(npc['con'])
|
|
||||||
hitdice = [randint(1, guild.hit_die_size) for x in range(0, npc['level'])]
|
|
||||||
for die in hitdice:
|
for die in hitdice:
|
||||||
die += conmod
|
die += conmod
|
||||||
if die < 1:
|
if die < 1:
|
||||||
die = 1
|
die = 1
|
||||||
npc['hp'] += die
|
hp += die
|
||||||
|
|
||||||
armourval = randint(0, len(data['armours']) - 1) + guild.armour_modifier
|
return hp
|
||||||
|
|
||||||
|
def calc_armour(armour_mod, armours):
|
||||||
|
armourval = randint(0, len(armours) - 1) + armour_mod
|
||||||
if armourval < 0:
|
if armourval < 0:
|
||||||
armourval = 0
|
armourval = 0
|
||||||
if armourval > len(data['armours']) - 1:
|
if armourval > len(armours) - 1:
|
||||||
armourval = len(data['armours']) - 1
|
armourval = len(armours) - 1
|
||||||
armour = data['armours'][armourval]
|
return armours[armourval]
|
||||||
npc['armour'] = [armour.name, armour.gp_value, armour.ac_mod]
|
|
||||||
melee = select_melee_weapon(guild, data)
|
def generate_npc(base_level, data):
|
||||||
npc['melee'] = [melee.name, melee.gp_value, melee.damage_die] if melee else None
|
npc = CharacterNPC()
|
||||||
ranged = select_ranged_weapon(guild, data)
|
|
||||||
npc['ranged'] = [ranged.name, ranged.gp_value, ranged.damage_die] if ranged else None
|
npc.guild = npc_class(['Demi-Human'])
|
||||||
|
npc.level = npc_baselevel(base_level)
|
||||||
|
npc.alignment = npc_alignment()
|
||||||
|
|
||||||
|
abilities = npc_abilities()
|
||||||
|
npc.update(npc_abilities())
|
||||||
|
|
||||||
|
npc.hit_points = calc_hp(attribute_mod(npc.constitution), npc.guild.hit_die_size, npc.level)
|
||||||
|
|
||||||
|
npc.armour = calc_armour(npc.guild.armour_modifier, data['armours'])
|
||||||
|
npc.melee = select_melee_weapon(npc.guild, data)
|
||||||
|
npc.ranged = select_ranged_weapon(npc.guild, data)
|
||||||
|
|
||||||
return npc
|
return npc
|
||||||
|
|
||||||
|
def name_party(party):
|
||||||
|
male_names = requests.get(
|
||||||
|
'http://names.drycodes.com/{}'.format(ceil(len(party))),
|
||||||
|
params={'nameOptions': 'boy_names', 'separator': 'space'}
|
||||||
|
).json()
|
||||||
|
|
||||||
|
female_names = requests.get(
|
||||||
|
'http://names.drycodes.com/{}'.format(floor(len(party))),
|
||||||
|
params={'nameOptions': 'girl_names', 'separator': 'space'}
|
||||||
|
).json()
|
||||||
|
|
||||||
|
names = female_names + male_names
|
||||||
|
shuffle(names)
|
||||||
|
|
||||||
|
for i in range(0, len(party)):
|
||||||
|
party[i].name = names[i]
|
||||||
|
|
||||||
|
return party
|
||||||
|
|
||||||
def create_party(base_level):
|
def create_party(base_level):
|
||||||
data = {
|
data = {
|
||||||
'armours': EquipmentArmour.query.all(),
|
'armours': EquipmentArmour.query.all(),
|
||||||
@ -133,22 +163,22 @@ def create_party(base_level):
|
|||||||
'heavy': EquipmentMeleeWeapon.query.filter_by(bucket='Heavy').all()
|
'heavy': EquipmentMeleeWeapon.query.filter_by(bucket='Heavy').all()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
party_size = number_encountered()
|
|
||||||
return [generate_npc(base_level, data) for x in range(0, party_size)]
|
return name_party([generate_npc(base_level, data) for x in range(0, number_encountered())])
|
||||||
|
|
||||||
def print_party(party):
|
def print_party(party):
|
||||||
def print_npc(npc):
|
def print_npc(npc):
|
||||||
print('Level {0} NPC, {1}, {2} HP'.format(npc['level'], npc['guild'], npc['hp']))
|
print('Level {0} NPC, {1}, {2} HP'.format(npc.level, npc.guild, npc.hp))
|
||||||
print('{0} Str, {1} Int, {2} Wis, {3} Dex, {4} Con, {5} Chr'.format(
|
print('{0} Str, {1} Int, {2} Wis, {3} Dex, {4} Con, {5} Chr'.format(
|
||||||
npc['str'], npc['int'], npc['wis'],
|
npc.str, npc.int, npc.wis,
|
||||||
npc['dex'], npc['con'], npc['chr']
|
npc.dex, npc.con, npc.chr
|
||||||
))
|
))
|
||||||
print('Armour Class: {0} - {1}, {2}gp'.format(npc['armour'][2], npc['armour'][0], npc['armour'][1]))
|
print('Armour Class: {0} - {1}, {2}gp'.format(npc.armour.name, npc.armour.ac_mod, npc.armour.gp_value))
|
||||||
print('{:^16} - {:^10} - {:^10} - {:^10}'.format('Weapon', 'Gold', 'Throw Mod', 'Damage'))
|
print('{:^16} - {:^10} - {:^10} - {:^10}'.format('Weapon', 'Gold', 'Throw Mod', 'Damage'))
|
||||||
print('-------------------------------------------------------')
|
print('-------------------------------------------------------')
|
||||||
print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc['melee'][0], npc['melee'][1], 0, npc['melee'][2]))
|
print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.melee.name, 0, npc.melee.damage_die))
|
||||||
if npc['ranged']:
|
if npc['ranged']:
|
||||||
print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc['ranged'][0], npc['ranged'][1], 0, npc['ranged'][2]))
|
print('{:^16} | {:^10} | {:^10} | {:^10}'.format(npc.ranged.name, 0, npc.melee.damage_die))
|
||||||
print('\n')
|
print('\n')
|
||||||
|
|
||||||
for npc in party:
|
for npc in party:
|
||||||
|
@ -1,94 +1,204 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "npcparty" %}
|
||||||
|
|
||||||
{% block title %}NPC Party Generation{% endblock %}
|
{% block title %}NPC Party Generation{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div>
|
<div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
|
||||||
<label for="base_level">Base level of party to generate: </label>
|
<h1>Adventurer Conqueror King NPC Party Generator</h1>
|
||||||
<input type="number" name="base_level" id="base_level" default="1">
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
|
||||||
<button onclick="generateParty();">Generate</button>
|
<div>
|
||||||
|
<label for="base_level">Base level of party to generate: </label>
|
||||||
|
<input type="number" name="base_level" id="base_level" class="uk-input" value="{{ base_level if base_level else 1 }}" >
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin-left">
|
||||||
|
<button class="uk-button uk-button-primary" onclick="generateParty();">Generate</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<hr class="uk-divider-icon">
|
||||||
|
|
||||||
{% if party %}
|
{% if party %}
|
||||||
<h3>NPC Party of Size {{ party | length }}</h3>
|
<div class="uk-flex uk-flex-between uk-margin-large-top">
|
||||||
|
<h3 class="uk-display-inline-block">NPC Party of Size {{ party | length }} </h3>
|
||||||
|
<div class="uk-text-right uk-display-inline-block">
|
||||||
|
<a class="uk-button uk-button-small uk-button-secondary uk-border-rounded" onclick="showExportModal();">Roll20 Export</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="uk-grid-medium uk-grid-match" uk-grid>
|
<div class="uk-grid-medium uk-grid-match uk-flex-center" uk-grid>
|
||||||
{% for npc in party %}
|
{% for npc in party %}
|
||||||
<div class="acks-npc-card">
|
<div class="acks-npc-card">
|
||||||
<div class="uk-card uk-card-body uk-card-default">
|
<div class="uk-card uk-card-body uk-card-default uk-box-shadow-hover-large">
|
||||||
<h4 class="uk-card-title">{{ npc.guild }}</h4>
|
<h4 class="uk-card-title uk-margin-small-top">{{ npc.name }}</h4>
|
||||||
<div class="uk-card-badge uk-label">Level {{ npc.level }}</div>
|
<div class="uk-card-badge uk-label uk-label-primary">{{ npc.guild.name }}</div>
|
||||||
<div class="uk-flex uk-flex-around uk-text-center uk-margin-bottom">
|
<div class="uk-flex uk-flex-around uk-text-center uk-margin-bottom">
|
||||||
<div>
|
<div>
|
||||||
<div>{{ npc.hp }}</div>
|
<div class="uk-text-bold">Level</div>
|
||||||
<div>HP</div>
|
<div>{{ npc.level }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>{{ npc.armour[2] }}</div>
|
<div class="uk-text-bold">HP</div>
|
||||||
<div>AC</div>
|
<div>{{ npc.hp }}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="uk-text-bold">AC</div>
|
||||||
|
<div>{{ npc.armour.ac_mod }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-flex uk-flex-around stat-block">
|
<div class="uk-flex uk-flex-around stat-block">
|
||||||
<div>
|
<div>
|
||||||
<div>{{ npc.str }}</div>
|
<div title="{{ npc.str_mod }}">{{ npc.str }}</div>
|
||||||
<div>Str</div>
|
<div title="{{ npc.str_mod }}">Str</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>{{ npc.int }}</div>
|
<div title="{{ npc.int_mod }}">{{ npc.int }}</div>
|
||||||
<div>Int</div>
|
<div title="{{ npc.int_mod }}">Int</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>{{ npc.wis }}</div>
|
<div title="{{ npc.wis_mod }}">{{ npc.wis }}</div>
|
||||||
<div>Wis</div>
|
<div title="{{ npc.wis_mod }}">Wis</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>{{ npc.dex }}</div>
|
<div title="{{ npc.dex_mod }}">{{ npc.dex }}</div>
|
||||||
<div>Dex</div>
|
<div title="{{ npc.dex_mod }}">Dex</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>{{ npc.con }}</div>
|
<div title="{{ npc.con_mod }}">{{ npc.con }}</div>
|
||||||
<div>Con</div>
|
<div title="{{ npc.con_mod }}">Con</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>{{ npc.chr }}</div>
|
<div title="{{ npc.chr_mod }}">{{ npc.chr }}</div>
|
||||||
<div>Chr</div>
|
<div title="{{ npc.chr_mod }}">Chr</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="uk-table uk-table-hover uk-table-small item-table">
|
<hr class="uk-divider-small uk-text-center">
|
||||||
<thead>
|
<div class="uk-flex uk-flex-around uk-text-center uk-margin-bottom save-block uk-margin-top">
|
||||||
<tr> <th>Name</th><th>Worth</th><th>Thr</th><th>Dmg</th> </tr>
|
<div>
|
||||||
</thead>
|
<div title="Petrification & Paralysis">{{ npc.save_pp }}</div>
|
||||||
<tbody>
|
<div title="Petrification & Paralysis">P & P</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>{{ npc.melee[0] }}</td>
|
<div>
|
||||||
<td>{{ npc.melee[1] }}gp</td>
|
<div title="Poison & Death">{{ npc.save_pd }}</div>
|
||||||
<td>0</td>
|
<div title="Poison & Death">P & D</div>
|
||||||
<td>{{ npc.melee[2] }}</td>
|
</div>
|
||||||
</tr>
|
<div>
|
||||||
{% if npc.ranged %}
|
<div title="Blast & Breath">{{ npc.save_bb }}</div>
|
||||||
<tr>
|
<div title="Blast & Breath">B & B</div>
|
||||||
<td>{{ npc.ranged[0] }}</td>
|
</div>
|
||||||
<td>{{ npc.ranged[1] }}gp</td>
|
<div>
|
||||||
<td>0</td>
|
<div title="Staffs & Wands">{{ npc.save_sw }}</div>
|
||||||
<td>{{ npc.ranged[2] }}</td>
|
<div title="Staffs & Wands">S & W</div>
|
||||||
</tr>
|
</div>
|
||||||
{% endif %}
|
<div>
|
||||||
<tr>
|
<div title="Spells">{{ npc.save_sp }}</div>
|
||||||
<td>{{ npc.armour[0] }}</td>
|
<div title="Spells">Spells</div>
|
||||||
<td>{{ npc.armour[1] }}gp</td>
|
</div>
|
||||||
<td></td>
|
</div>
|
||||||
<td></td>
|
<ul uk-tab>
|
||||||
</tr>
|
<li class="uk-active"><a href="">Equipment</a></li>
|
||||||
</tbody>
|
<li {% if not npc.spellcaster %}class="uk-disabled"{% endif %}><a href="">Spells</a></li>
|
||||||
</table>
|
</ul>
|
||||||
|
<ul class="uk-switcher uk-margin">
|
||||||
|
<li>
|
||||||
|
<table class="uk-table uk-table-hover uk-table-small item-table">
|
||||||
|
<thead>
|
||||||
|
<tr> <th>Name</th><th>Worth</th><th>Thr</th><th>Dmg</th> </tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ npc.melee.name }}</td>
|
||||||
|
<td>{{ npc.melee.gp_value }}gp</td>
|
||||||
|
<td>{{ npc.attack_throw }}</td>
|
||||||
|
<td>{{ npc.melee.damage_die }}</td>
|
||||||
|
</tr>
|
||||||
|
{% if npc.ranged %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ npc.ranged.name }}</td>
|
||||||
|
<td>{{ npc.ranged.gp_value }}gp</td>
|
||||||
|
<td>{{ npc.attack_throw }}</td>
|
||||||
|
<td>{{ npc.ranged.damage_die }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ npc.armour.name }}</td>
|
||||||
|
<td>{{ npc.armour.gp_value }}gp</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<table class="uk-table uk-table-hover uk-table-small item-table">
|
||||||
|
<thead>
|
||||||
|
<tr> <th>Name</th><th>Worth</th><th>Thr</th><th>Dmg</th> </tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ npc.melee.name }}</td>
|
||||||
|
<td>{{ npc.melee.gp_value }}gp</td>
|
||||||
|
<td>{{ npc.attack_throw }}</td>
|
||||||
|
<td>{{ npc.melee.damage_die }}</td>
|
||||||
|
</tr>
|
||||||
|
{% if npc.ranged %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ npc.ranged.name }}</td>
|
||||||
|
<td>{{ npc.ranged.gp_value }}gp</td>
|
||||||
|
<td>{{ npc.attack_throw }}</td>
|
||||||
|
<td>{{ npc.ranged.damage_die }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ npc.armour.name }}</td>
|
||||||
|
<td>{{ npc.armour.gp_value }}gp</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="party-export-modal" uk-modal>
|
||||||
|
<div class="uk-modal-dialog uk-margin-auto-vertical">
|
||||||
|
<button class="uk-modal-close-default" type="button" uk-close></button>
|
||||||
|
<div class="uk-modal-header">
|
||||||
|
<h2 class="uk-modal-title">Roll20 Export</h2>
|
||||||
|
</div>
|
||||||
|
<div class="uk-modal-body">
|
||||||
|
<div class="uk-margin">
|
||||||
|
<h5>Characters to Export:</h5>
|
||||||
|
<div id="pe_selects" class="uk-grid-small uk-grid uk-child-width-auto">
|
||||||
|
{% if party %}
|
||||||
|
{% for npc in party %}
|
||||||
|
<label><input class="uk-checkbox" type="checkbox" name="ch{{ loop.index }}" onchange="prepareSelectiveExport();" checked> {{ npc.name }}</label>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin uk-align-right">
|
||||||
|
<div class="uk-button-group">
|
||||||
|
<button id="pe_none" class="uk-button uk-button-small uk-button-default" onclick="partyExportToggle();">None</button>
|
||||||
|
<button id="pe_all" class="uk-button uk-button-small uk-button-secondary" onclick="partyExportToggle();">All</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<textarea id="party-export-show" class="uk-textarea uk-text-small" rows="10"></textarea>
|
||||||
|
<textarea id="party-export-data" class="uk-textarea uk-hidden"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="uk-modal-footer uk-text-right">
|
||||||
|
<button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
|
||||||
|
<button class="uk-button uk-button-primary" type="button" onclick="exportParty();">Copy</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -109,14 +219,78 @@ div.stat-block > div > div:last-child {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
div.save-block > div > div:first-child {
|
||||||
|
font-weight: bold;
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
div.save-block > div > div:last-child {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
div.acks-npc-card {
|
div.acks-npc-card {
|
||||||
width: 370px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
var party = [];
|
||||||
|
|
||||||
function generateParty() {
|
function generateParty() {
|
||||||
let bl = document.querySelector('#base_level').value;
|
let bl = document.querySelector('#base_level').value;
|
||||||
window.location = "/npc/party/" + bl.toString();
|
window.location = "/npc/party/" + bl.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showExportModal() {
|
||||||
|
{% if party %}
|
||||||
|
{% for npc in party %}
|
||||||
|
party.push(JSON.parse('{{ npc.roll20_format | tojson }}'));
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
for(let cb of document.querySelectorAll('#pe_selects > label > input')) {
|
||||||
|
cb.checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareSelectiveExport();
|
||||||
|
UIkit.modal(document.querySelector('#party-export-modal')).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareSelectiveExport() {
|
||||||
|
var selected_party = [];
|
||||||
|
var checkboxes = document.querySelectorAll('#pe_selects > label > input');
|
||||||
|
for(let [i, cb] of checkboxes.entries()) {
|
||||||
|
if(cb.checked) {
|
||||||
|
selected_party.push(party[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector('#party-export-data').value = "!acksimport " + JSON.stringify(selected_party);
|
||||||
|
document.querySelector('#party-export-show').value = JSON.stringify(selected_party, undefined, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportParty() {
|
||||||
|
let party_data = document.querySelector('#party-export-data')
|
||||||
|
party_data.classList.remove('uk-hidden');
|
||||||
|
|
||||||
|
party_data.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
|
||||||
|
party_data.classList.add('uk-hidden');
|
||||||
|
UIkit.modal(document.querySelector('#party-export-modal')).hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function partyExportToggle() {
|
||||||
|
var checkboxes = document.querySelectorAll('#pe_selects > label > input');
|
||||||
|
for(let cb of checkboxes) {
|
||||||
|
if(event.target.id === "pe_none") {
|
||||||
|
cb.checked = false;
|
||||||
|
}
|
||||||
|
if(event.target.id === "pe_all") {
|
||||||
|
cb.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareSelectiveExport();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
from flask import current_app, Blueprint, render_template
|
from flask import (
|
||||||
|
request,
|
||||||
|
jsonify,
|
||||||
|
current_app,
|
||||||
|
render_template,
|
||||||
|
Blueprint
|
||||||
|
)
|
||||||
|
|
||||||
from .npc_party import create_party
|
from .npc_party import create_party
|
||||||
|
|
||||||
@ -16,4 +22,8 @@ def generate_npc_party(base_level=None):
|
|||||||
party = None
|
party = None
|
||||||
if base_level:
|
if base_level:
|
||||||
party = create_party(base_level)
|
party = create_party(base_level)
|
||||||
return render_template('generate_npc_party.html', party=party)
|
|
||||||
|
# If asked for JSON, return the party, otherwise render HTML template
|
||||||
|
if request.args.get('format', 'html') == 'json':
|
||||||
|
return jsonify([npc.roll20_format for npc in party])
|
||||||
|
return render_template('generate_npc_party.html', party=party, base_level=base_level)
|
||||||
|
BIN
acks/static/favicon.ico
Normal file
BIN
acks/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
@ -1,11 +1,29 @@
|
|||||||
|
{% set navigation_bar = [
|
||||||
|
('/', 'index', 'Home'),
|
||||||
|
('/handbook', 'handbook', 'Handbook'),
|
||||||
|
('/npc/party', 'npcparty', 'NPC Party'),
|
||||||
|
('#', 'treasure', 'Treasure Generator'),
|
||||||
|
('#', 'henchmen', 'Henchmen')
|
||||||
|
] %}
|
||||||
|
{% set active_page = active_page|default('index') %}
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{% block title %}{% endblock %} - Atr0phy ACKS</title>
|
<title>{% block title %}{% endblock %} - Atr0phy ACKS</title>
|
||||||
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" contents="width=device-width, initial-scale=1">
|
<meta name="viewport" contents="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/css/uikit.min.css" integrity="sha256-5YtK9j+Nl/245lAkSjrIs600d6edKTevi+3JYdjuHhY=" crossorigin="anonymous" />
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.min.js" integrity="sha256-rhLALrRmAQVu/OxzVDpQaiHAEMxiRSN8h8RDydUEh2g=" crossorigin="anonymous"></script>
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
|
|
||||||
|
<!-- UIkit CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.2.4/dist/css/uikit.min.css" />
|
||||||
|
|
||||||
|
<!-- UIkit JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/uikit@3.2.4/dist/js/uikit.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/uikit@3.2.4/dist/js/uikit-icons.min.js"></script>
|
||||||
|
|
||||||
{% block head %} {% endblock %}
|
{% block head %} {% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -15,10 +33,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-center">
|
<div class="uk-navbar-center">
|
||||||
<ul class="uk-navbar-nav">
|
<ul class="uk-navbar-nav">
|
||||||
<li><a href="/handbook">Handbook</a></li>
|
{% for href, id, label in navigation_bar %}
|
||||||
<li class="uk-active"><a href="/npc/party">NPC Party</a></li>
|
<li {% if id == active_page %} class="uk-active" {% endif %}>
|
||||||
<li><a href="">Treasure</a></li>
|
<a href="{{ href|e }}">{{ label|e }}</a>
|
||||||
<li><a href="">Henchmen</a></li>
|
</li>
|
||||||
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "handbook" %}
|
||||||
|
|
||||||
{% block title %}ACKS Handbook{% endblock %}
|
{% block title %}ACKS Handbook{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="frame-container">
|
<div id="frame-container">
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "index" %}
|
||||||
|
|
||||||
{% block title %}ACKS Toolset Home{% endblock %}
|
{% block title %}ACKS Toolset Home{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div>
|
<div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user