Template updates, API
This commit is contained in:
parent
9811a6abf9
commit
aebe890e3e
@ -1,6 +1,6 @@
|
||||
from flask import Flask
|
||||
from flask_admin import Admin
|
||||
from flask_admin.contrib.sqla import ModelView
|
||||
|
||||
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
@ -28,10 +28,22 @@ def create_app():
|
||||
app.cli.add_command(npc_cli)
|
||||
|
||||
# Load the Admin views
|
||||
from flask_admin import Admin
|
||||
from flask_admin.contrib.sqla import ModelView
|
||||
admin = Admin(app, name='acks', template_mode='bootstrap3')
|
||||
|
||||
from acks.npc.models import admin_models as npc_admin_models
|
||||
for mdl in npc_admin_models:
|
||||
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
|
||||
|
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
|
||||
from .models import (
|
||||
CharacterClass,
|
||||
ClassLevelProgression,
|
||||
EquipmentArmour,
|
||||
EquipmentRangedWeapon,
|
||||
EquipmentMeleeWeapon
|
||||
@ -39,4 +40,11 @@ def populate_npc_database():
|
||||
wep.two_handed = (wep.two_handed == 'True')
|
||||
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()
|
||||
|
@ -23,6 +23,25 @@ class CharacterClass(BaseModel):
|
||||
def __repr__(self):
|
||||
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):
|
||||
__tablename__ = 'eq_armour'
|
||||
|
||||
@ -61,18 +80,188 @@ class CharacterNPC(BaseModel):
|
||||
|
||||
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)
|
||||
|
||||
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))
|
||||
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
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)
|
||||
|
||||
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 (
|
||||
CharacterClass,
|
||||
@ -12,8 +15,11 @@ from .models import (
|
||||
def number_encountered():
|
||||
return (randint(1, 4) + 2)
|
||||
|
||||
def npc_class():
|
||||
classes = [c for cls in CharacterClass.query.all() for c in ([cls] * cls.frequency_modifier)]
|
||||
def npc_class(excluded_buckets):
|
||||
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)
|
||||
|
||||
def npc_alignment():
|
||||
@ -46,7 +52,7 @@ def npc_baselevel(base_level):
|
||||
return base_level
|
||||
|
||||
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)]
|
||||
return zip(ability_list, abilities)
|
||||
|
||||
@ -88,38 +94,62 @@ def select_ranged_weapon(guild, data):
|
||||
selection = randint(0, len(weapons) - 1)
|
||||
return weapons[selection]
|
||||
|
||||
def generate_npc(base_level, data):
|
||||
guild = npc_class()
|
||||
|
||||
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'])]
|
||||
def calc_hp(conmod, hit_die_size, level):
|
||||
hp = 0
|
||||
hitdice = [randint(1, hit_die_size) for x in range(0, level)]
|
||||
for die in hitdice:
|
||||
die += conmod
|
||||
if 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:
|
||||
armourval = 0
|
||||
if armourval > len(data['armours']) - 1:
|
||||
armourval = len(data['armours']) - 1
|
||||
armour = data['armours'][armourval]
|
||||
npc['armour'] = [armour.name, armour.gp_value, armour.ac_mod]
|
||||
melee = select_melee_weapon(guild, data)
|
||||
npc['melee'] = [melee.name, melee.gp_value, melee.damage_die] if melee else None
|
||||
ranged = select_ranged_weapon(guild, data)
|
||||
npc['ranged'] = [ranged.name, ranged.gp_value, ranged.damage_die] if ranged else None
|
||||
if armourval > len(armours) - 1:
|
||||
armourval = len(armours) - 1
|
||||
return armours[armourval]
|
||||
|
||||
def generate_npc(base_level, data):
|
||||
npc = CharacterNPC()
|
||||
|
||||
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
|
||||
|
||||
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):
|
||||
data = {
|
||||
'armours': EquipmentArmour.query.all(),
|
||||
@ -133,22 +163,22 @@ def create_party(base_level):
|
||||
'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_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(
|
||||
npc['str'], npc['int'], npc['wis'],
|
||||
npc['dex'], npc['con'], npc['chr']
|
||||
npc.str, npc.int, npc.wis,
|
||||
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('-------------------------------------------------------')
|
||||
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']:
|
||||
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')
|
||||
|
||||
for npc in party:
|
||||
|
@ -1,94 +1,204 @@
|
||||
{% extends "base.html" %}
|
||||
{% set active_page = "npcparty" %}
|
||||
|
||||
{% block title %}NPC Party Generation{% endblock %}
|
||||
{% block content %}
|
||||
<div>
|
||||
<label for="base_level">Base level of party to generate: </label>
|
||||
<input type="number" name="base_level" id="base_level" default="1">
|
||||
<div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
|
||||
<h1>Adventurer Conqueror King NPC Party Generator</h1>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="generateParty();">Generate</button>
|
||||
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
|
||||
<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>
|
||||
|
||||
<br>
|
||||
<hr class="uk-divider-icon">
|
||||
|
||||
{% 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 %}
|
||||
<div class="acks-npc-card">
|
||||
<div class="uk-card uk-card-body uk-card-default">
|
||||
<h4 class="uk-card-title">{{ npc.guild }}</h4>
|
||||
<div class="uk-card-badge uk-label">Level {{ npc.level }}</div>
|
||||
<div class="uk-card uk-card-body uk-card-default uk-box-shadow-hover-large">
|
||||
<h4 class="uk-card-title uk-margin-small-top">{{ npc.name }}</h4>
|
||||
<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>
|
||||
<div>{{ npc.hp }}</div>
|
||||
<div>HP</div>
|
||||
<div class="uk-text-bold">Level</div>
|
||||
<div>{{ npc.level }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ npc.armour[2] }}</div>
|
||||
<div>AC</div>
|
||||
<div class="uk-text-bold">HP</div>
|
||||
<div>{{ npc.hp }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="uk-text-bold">AC</div>
|
||||
<div>{{ npc.armour.ac_mod }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uk-flex uk-flex-around stat-block">
|
||||
<div>
|
||||
<div>{{ npc.str }}</div>
|
||||
<div>Str</div>
|
||||
<div title="{{ npc.str_mod }}">{{ npc.str }}</div>
|
||||
<div title="{{ npc.str_mod }}">Str</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ npc.int }}</div>
|
||||
<div>Int</div>
|
||||
<div title="{{ npc.int_mod }}">{{ npc.int }}</div>
|
||||
<div title="{{ npc.int_mod }}">Int</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ npc.wis }}</div>
|
||||
<div>Wis</div>
|
||||
<div title="{{ npc.wis_mod }}">{{ npc.wis }}</div>
|
||||
<div title="{{ npc.wis_mod }}">Wis</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ npc.dex }}</div>
|
||||
<div>Dex</div>
|
||||
<div title="{{ npc.dex_mod }}">{{ npc.dex }}</div>
|
||||
<div title="{{ npc.dex_mod }}">Dex</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ npc.con }}</div>
|
||||
<div>Con</div>
|
||||
<div title="{{ npc.con_mod }}">{{ npc.con }}</div>
|
||||
<div title="{{ npc.con_mod }}">Con</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ npc.chr }}</div>
|
||||
<div>Chr</div>
|
||||
<div title="{{ npc.chr_mod }}">{{ npc.chr }}</div>
|
||||
<div title="{{ npc.chr_mod }}">Chr</div>
|
||||
</div>
|
||||
</div>
|
||||
<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[0] }}</td>
|
||||
<td>{{ npc.melee[1] }}gp</td>
|
||||
<td>0</td>
|
||||
<td>{{ npc.melee[2] }}</td>
|
||||
</tr>
|
||||
{% if npc.ranged %}
|
||||
<tr>
|
||||
<td>{{ npc.ranged[0] }}</td>
|
||||
<td>{{ npc.ranged[1] }}gp</td>
|
||||
<td>0</td>
|
||||
<td>{{ npc.ranged[2] }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{{ npc.armour[0] }}</td>
|
||||
<td>{{ npc.armour[1] }}gp</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr class="uk-divider-small uk-text-center">
|
||||
<div class="uk-flex uk-flex-around uk-text-center uk-margin-bottom save-block uk-margin-top">
|
||||
<div>
|
||||
<div title="Petrification & Paralysis">{{ npc.save_pp }}</div>
|
||||
<div title="Petrification & Paralysis">P & P</div>
|
||||
</div>
|
||||
<div>
|
||||
<div title="Poison & Death">{{ npc.save_pd }}</div>
|
||||
<div title="Poison & Death">P & D</div>
|
||||
</div>
|
||||
<div>
|
||||
<div title="Blast & Breath">{{ npc.save_bb }}</div>
|
||||
<div title="Blast & Breath">B & B</div>
|
||||
</div>
|
||||
<div>
|
||||
<div title="Staffs & Wands">{{ npc.save_sw }}</div>
|
||||
<div title="Staffs & Wands">S & W</div>
|
||||
</div>
|
||||
<div>
|
||||
<div title="Spells">{{ npc.save_sp }}</div>
|
||||
<div title="Spells">Spells</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul uk-tab>
|
||||
<li class="uk-active"><a href="">Equipment</a></li>
|
||||
<li {% if not npc.spellcaster %}class="uk-disabled"{% endif %}><a href="">Spells</a></li>
|
||||
</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>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% 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>
|
||||
|
||||
<style>
|
||||
@ -109,14 +219,78 @@ div.stat-block > div > div:last-child {
|
||||
font-size: 12px;
|
||||
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 {
|
||||
width: 370px;
|
||||
width: 400px;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
var party = [];
|
||||
|
||||
function generateParty() {
|
||||
let bl = document.querySelector('#base_level').value;
|
||||
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>
|
||||
{% 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
|
||||
|
||||
@ -16,4 +22,8 @@ def generate_npc_party(base_level=None):
|
||||
party = None
|
||||
if 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>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %} - Atr0phy ACKS</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<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 %}
|
||||
</head>
|
||||
<body>
|
||||
@ -15,10 +33,11 @@
|
||||
</div>
|
||||
<div class="uk-navbar-center">
|
||||
<ul class="uk-navbar-nav">
|
||||
<li><a href="/handbook">Handbook</a></li>
|
||||
<li class="uk-active"><a href="/npc/party">NPC Party</a></li>
|
||||
<li><a href="">Treasure</a></li>
|
||||
<li><a href="">Henchmen</a></li>
|
||||
{% for href, id, label in navigation_bar %}
|
||||
<li {% if id == active_page %} class="uk-active" {% endif %}>
|
||||
<a href="{{ href|e }}">{{ label|e }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -1,4 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% set active_page = "handbook" %}
|
||||
|
||||
{% block title %}ACKS Handbook{% endblock %}
|
||||
{% block content %}
|
||||
<div id="frame-container">
|
||||
|
@ -1,4 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% set active_page = "index" %}
|
||||
|
||||
{% block title %}ACKS Toolset Home{% endblock %}
|
||||
{% block content %}
|
||||
<div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user