Browse Source

Template updates, API

master
Brandon Cornejo 5 years ago
parent
commit
aebe890e3e
  1. 16
      acks/__init__.py
  2. 8
      acks/api.py
  3. 38
      acks/npc/api.py
  4. 8
      acks/npc/commands.py
  5. 197
      acks/npc/models.py
  6. 96
      acks/npc/npc_party.py
  7. 244
      acks/npc/templates/generate_npc_party.html
  8. 14
      acks/npc/views.py
  9. BIN
      acks/static/favicon.ico
  10. 31
      acks/templates/base.html
  11. 2
      acks/templates/handbook.html
  12. 2
      acks/templates/index.html

16
acks/__init__.py

@ -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

@ -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

@ -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
]

8
acks/npc/commands.py

@ -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()

197
acks/npc/models.py

@ -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]

96
acks/npc/npc_party.py

@ -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,7 +15,10 @@ 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):
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)] classes = [c for cls in CharacterClass.query.all() for c in ([cls] * cls.frequency_modifier)]
return choice(classes) return choice(classes)
@ -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):
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: for die in hitdice:
die += conmod die += conmod
if die < 1: if die < 1:
die = 1 die = 1
npc['hp'] += die
hp += die
return hp
armourval = randint(0, len(data['armours']) - 1) + guild.armour_modifier
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:
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 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['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('{:^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:

244
acks/npc/templates/generate_npc_party.html

@ -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 class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
<h1>Adventurer Conqueror King NPC Party Generator</h1>
</div>
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
<div> <div>
<label for="base_level">Base level of party to generate: </label> <label for="base_level">Base level of party to generate: </label>
<input type="number" name="base_level" id="base_level" default="1">
<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>
<button onclick="generateParty();">Generate</button>
</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">
<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 class="uk-flex uk-flex-around uk-text-center uk-margin-bottom">
<div> <div>
<div class="uk-text-bold">Level</div>
<div>{{ npc.level }}</div>
</div>
<div>
<div class="uk-text-bold">HP</div>
<div>{{ npc.hp }}</div> <div>{{ npc.hp }}</div>
<div>HP</div>
</div> </div>
<div> <div>
<div>{{ npc.armour[2] }}</div>
<div>AC</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>Str</div>
<div title="{{ npc.str_mod }}">{{ npc.str }}</div>
<div title="{{ npc.str_mod }}">Str</div>
</div> </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> <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> <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> <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> <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>
</div> </div>
<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 &amp; P</div>
</div>
<div>
<div title="Poison & Death">{{ npc.save_pd }}</div>
<div title="Poison & Death">P &amp; D</div>
</div>
<div>
<div title="Blast & Breath">{{ npc.save_bb }}</div>
<div title="Blast & Breath">B &amp; B</div>
</div>
<div>
<div title="Staffs & Wands">{{ npc.save_sw }}</div>
<div title="Staffs & Wands">S &amp; 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"> <table class="uk-table uk-table-hover uk-table-small item-table">
<thead> <thead>
<tr> <th>Name</th><th>Worth</th><th>Thr</th><th>Dmg</th> </tr> <tr> <th>Name</th><th>Worth</th><th>Thr</th><th>Dmg</th> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>{{ npc.melee[0] }}</td>
<td>{{ npc.melee[1] }}gp</td>
<td>0</td>
<td>{{ npc.melee[2] }}</td>
<td>{{ npc.melee.name }}</td>
<td>{{ npc.melee.gp_value }}gp</td>
<td>{{ npc.attack_throw }}</td>
<td>{{ npc.melee.damage_die }}</td>
</tr> </tr>
{% if npc.ranged %} {% if npc.ranged %}
<tr> <tr>
<td>{{ npc.ranged[0] }}</td>
<td>{{ npc.ranged[1] }}gp</td>
<td>0</td>
<td>{{ npc.ranged[2] }}</td>
<td>{{ npc.ranged.name }}</td>
<td>{{ npc.ranged.gp_value }}gp</td>
<td>{{ npc.attack_throw }}</td>
<td>{{ npc.ranged.damage_die }}</td>
</tr> </tr>
{% endif %} {% endif %}
<tr> <tr>
<td>{{ npc.armour[0] }}</td>
<td>{{ npc.armour[1] }}gp</td>
<td>{{ npc.armour.name }}</td>
<td>{{ npc.armour.gp_value }}gp</td>
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
</tbody> </tbody>
</table> </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 %}

14
acks/npc/views.py

@ -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

After

Width: 200  |  Height: 200  |  Size: 4.2 KiB

31
acks/templates/base.html

@ -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>
<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> </ul>
</div> </div>
</nav> </nav>

2
acks/templates/handbook.html

@ -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">

2
acks/templates/index.html

@ -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…
Cancel
Save