Quest management start
This commit is contained in:
parent
bbce8c6f86
commit
c91ed2f213
@ -20,6 +20,9 @@ def create_app():
|
||||
from acks.npc.views import npc_views
|
||||
app.register_blueprint(npc_views)
|
||||
|
||||
from acks.quest.views import quest_views
|
||||
app.register_blueprint(quest_views)
|
||||
|
||||
# Load our CLI commands
|
||||
from acks.commands import default_cli
|
||||
app.cli.add_command(default_cli)
|
||||
|
61
acks/quest/commands.py
Normal file
61
acks/quest/commands.py
Normal file
@ -0,0 +1,61 @@
|
||||
import click
|
||||
from flask.cli import AppGroup
|
||||
|
||||
from ..models import db
|
||||
|
||||
|
||||
npc_cli = AppGroup('npc')
|
||||
|
||||
@npc_cli.command('populate')
|
||||
def populate_npc_database():
|
||||
import csv
|
||||
from .models import (
|
||||
CharacterClass,
|
||||
ClassLevelProgression,
|
||||
EquipmentArmour,
|
||||
EquipmentRangedWeapon,
|
||||
EquipmentMeleeWeapon,
|
||||
Spell
|
||||
)
|
||||
|
||||
def load_csv_data(file_name, cls):
|
||||
rows = []
|
||||
with open('acks/npc/data/{}'.format(file_name), newline='') as data:
|
||||
reader = csv.DictReader(data)
|
||||
for row in reader:
|
||||
rows.append(cls(**row))
|
||||
return rows
|
||||
|
||||
# Character Classes
|
||||
db.session.bulk_save_objects(load_csv_data('default_classes.csv', CharacterClass))
|
||||
|
||||
# Equipment Armour
|
||||
db.session.bulk_save_objects(load_csv_data('default_armours.csv', EquipmentArmour))
|
||||
|
||||
# Ranged Weapons
|
||||
db.session.bulk_save_objects(load_csv_data('default_ranged.csv', EquipmentRangedWeapon))
|
||||
|
||||
# Melee Weapons
|
||||
melee_weps = load_csv_data('default_melee.csv', EquipmentMeleeWeapon)
|
||||
for wep in melee_weps:
|
||||
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)
|
||||
|
||||
# Spells
|
||||
spells = load_csv_data('default_spells.csv', Spell)
|
||||
for spell in spells:
|
||||
if spell.arcane == '':
|
||||
spell.arcane = 0
|
||||
if spell.divine == '':
|
||||
spell.divine = 0
|
||||
spell.description = spell.description.strip()
|
||||
db.session.bulk_save_objects(spells)
|
||||
|
||||
db.session.commit()
|
85
acks/quest/quest_manager.py
Normal file
85
acks/quest/quest_manager.py
Normal file
@ -0,0 +1,85 @@
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from zipfile import ZipFile
|
||||
|
||||
FS_ROOT = '/srv/www/atr0phy.net/acks/quests'
|
||||
URL_ROOT = 'https://atr0phy.net/acks/quests'
|
||||
IMG_EXTENSIONS = ('png', 'jpg', 'jpeg')
|
||||
|
||||
def load_quests():
|
||||
quest_list = {}
|
||||
|
||||
fs = Path(FS_ROOT)
|
||||
for e in fs.iterdir():
|
||||
if not e.is_dir():
|
||||
continue
|
||||
|
||||
# Each directory correlates to a level group
|
||||
quest_level_url_name = e.name.replace('Level', '').replace('-', '').replace(' ', '')
|
||||
|
||||
# Gather quests under each level
|
||||
quest_list[quest_level_url_name] = load_quest_level_directory(e)
|
||||
return quest_list
|
||||
|
||||
def load_quest_level_directory(qdir):
|
||||
this_level = []
|
||||
for e in qdir.iterdir():
|
||||
if not e.is_dir():
|
||||
continue
|
||||
|
||||
# Each directory is a quest for this level
|
||||
this_level.append(e.name)
|
||||
return this_level
|
||||
|
||||
def get_quest_details(level, quest_name):
|
||||
level_range = str(level)
|
||||
folder_name = "Level {0} - {1}".format(level_range[0], level_range[1])
|
||||
|
||||
quest_path = "{}/{}/{}".format(FS_ROOT, folder_name, quest_name)
|
||||
fs = Path(quest_path)
|
||||
|
||||
if not fs.exists() or not fs.is_dir():
|
||||
print('EXITING {}'.format(fs))
|
||||
return None
|
||||
|
||||
quest = {}
|
||||
quest['name'] = fs.name
|
||||
quest['download'] = (level, quest_name)
|
||||
quest['assets'] = []
|
||||
|
||||
def urlify(path):
|
||||
return {'display': path.name, 'url': path.as_posix().replace(FS_ROOT, URL_ROOT)}
|
||||
|
||||
for e in fs.iterdir():
|
||||
if e.name.endswith(IMG_EXTENSIONS):
|
||||
quest['assets'].append(urlify(e))
|
||||
elif e.name == 'info.html':
|
||||
quest['info'] = urlify(e)
|
||||
elif e.name == 'tsv.txt':
|
||||
quest['tsv'] = urlify(e)
|
||||
|
||||
print("Quest: {}".format(quest))
|
||||
return quest
|
||||
|
||||
def get_quest_archive(level, quest_name):
|
||||
level_range = str(level)
|
||||
folder_name = "Level {0} - {1}".format(level_range[0], level_range[1])
|
||||
|
||||
quest_path = "{}/{}/{}".format(FS_ROOT, folder_name, quest_name)
|
||||
fs = Path(quest_path)
|
||||
|
||||
if not fs.exists() or not fs.is_dir():
|
||||
print('EXITING {}'.format(fs))
|
||||
return None
|
||||
|
||||
file_path_list = []
|
||||
for e in fs.iterdir():
|
||||
file_path_list.append((e.as_posix(), e.name))
|
||||
|
||||
archive_mem = BytesIO()
|
||||
with ZipFile(archive_mem, 'w') as archive:
|
||||
for f in file_path_list:
|
||||
archive.write(f[0], f[1])
|
||||
archive_mem.seek(0)
|
||||
|
||||
return archive_mem
|
86
acks/quest/templates/quest_detail.html
Normal file
86
acks/quest/templates/quest_detail.html
Normal file
@ -0,0 +1,86 @@
|
||||
{% extends "base.html" %}
|
||||
{% set active_page = "quest_detail" %}
|
||||
|
||||
|
||||
{% block title %}ACKS Quest Detail{% endblock %}
|
||||
{% block content %}
|
||||
<div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
|
||||
<h1 class="uk-text-center"><strong>Adventurer Conqueror King</strong>Quest Detail</h1>
|
||||
</div>
|
||||
{% if quest %}
|
||||
<div class="uk-container uk-container-small">
|
||||
<div class="uk-text-center uk-margin-large">
|
||||
<h1 class="uk-heading-xlarge">{{ quest['name'] }}</h1>
|
||||
</div>
|
||||
<div class="uk-flex uk-flex-center uk-margin-large-bottom">
|
||||
<a href="{{ quest['info']['url'] }}" class="uk-button uk-button-default" target="_blank">Info Window</a>
|
||||
{% if quest['tsv'] %}
|
||||
<a href="{{ quest['tsv']['url'] }}" class="uk-button uk-button-default">TSV File</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('quest_manager.quest_download', level=quest['download'][0], quest_name=quest['download'][1]) }}" class="uk-button uk-button-default" target="_blank">Download</a>
|
||||
</div>
|
||||
<ul uk-accordion="multiple: true">
|
||||
<li>
|
||||
<a class="uk-accordion-title" href="#">Information</a>
|
||||
<div class="uk-accordion-content">
|
||||
<h3 class="uk-text-center">Quest Info</h3>
|
||||
<div id="frame-container">
|
||||
<iframe id="quest-frame" src="{{ quest['info']['url'] }}"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="uk-open">
|
||||
<a class="uk-accordion-title" href="#">Assets</a>
|
||||
<div class="uk-accordion-content">
|
||||
<h3 class="uk-text-center">Quest Assets</h3>
|
||||
<div uk-slideshow="animation: push">
|
||||
<ul class="uk-slideshow-nav uk-dotnav uk-flex-center uk-margin"></ul>
|
||||
<div class="uk-position-relative uk-visible-toggle uk-light" tabindex="-1">
|
||||
<ul class="uk-slideshow-items" uk-lightbox>
|
||||
{% for a in quest['assets'] %}
|
||||
<li>
|
||||
<a href="{{ a['url'] }}"><img src="{{ a['url'] }}" alt="" width="800" uk-cover></a>
|
||||
<div class="uk-overlay uk-overlay-primary uk-position-bottom uk-text-center uk-transition-slide-bottom">
|
||||
<h3 class="uk-margin-remove">{{ a['display'] }}</h3>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="uk-slideshow-nav uk-dotnav uk-flex-center uk-margin"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
#asset-view img {
|
||||
max-width: 300px;
|
||||
max-height: 500px;
|
||||
}
|
||||
#frame-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 85vh;
|
||||
flex-direction: column:
|
||||
}
|
||||
#quest-frame {
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1 strong {
|
||||
display: block;
|
||||
font-size: 50%;
|
||||
opacity: 0.65;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
{% endblock %}
|
39
acks/quest/templates/quest_list.html
Normal file
39
acks/quest/templates/quest_list.html
Normal file
@ -0,0 +1,39 @@
|
||||
{% extends "base.html" %}
|
||||
{% set active_page = "quest_list" %}
|
||||
|
||||
|
||||
{% block title %}ACKS Quest List{% endblock %}
|
||||
{% block content %}
|
||||
<div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
|
||||
<h1 class="uk-text-center"><strong>Adventurer Conqueror King</strong>Quest Board (Internal)</h1>
|
||||
</div>
|
||||
{% if quest_map %}
|
||||
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
|
||||
<ul class="uk-list">
|
||||
{% for level, quests in quest_map.items() | sort(attribute=0) %}
|
||||
<li>
|
||||
<strong>Level {{ level[0] }} - {{ level[1] }}</strong>
|
||||
<ul class="uk-list">
|
||||
{% for quest in quests %}
|
||||
<li><a href="{{ url_for('quest_manager.quest_detail', level=level, quest_name=quest) }}">{{ quest }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
h1 strong {
|
||||
display: block;
|
||||
font-size: 50%;
|
||||
opacity: 0.65;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
{% endblock %}
|
71
acks/quest/views.py
Normal file
71
acks/quest/views.py
Normal file
@ -0,0 +1,71 @@
|
||||
from flask import (
|
||||
request,
|
||||
jsonify,
|
||||
current_app,
|
||||
render_template,
|
||||
send_file,
|
||||
Blueprint
|
||||
)
|
||||
|
||||
from .quest_manager import (
|
||||
load_quests,
|
||||
get_quest_details,
|
||||
get_quest_archive
|
||||
)
|
||||
|
||||
|
||||
quest_views = Blueprint(
|
||||
'quest_manager',
|
||||
__name__,
|
||||
template_folder='templates',
|
||||
url_prefix='/quest'
|
||||
)
|
||||
|
||||
@quest_views.route('/list')
|
||||
def quest_list():
|
||||
quests = load_quests()
|
||||
return render_template('quest_list.html', quest_map=quests)
|
||||
|
||||
@quest_views.route('/detail/<int:level>/<string:quest_name>')
|
||||
def quest_detail(level, quest_name):
|
||||
quest = get_quest_details(level, quest_name)
|
||||
return render_template('quest_detail.html', quest=quest)
|
||||
|
||||
@quest_views.route('/detail/<int:level>/<string:quest_name>/download')
|
||||
def quest_download(level, quest_name):
|
||||
archive = get_quest_archive(level, quest_name)
|
||||
return send_file(archive, attachment_filename="acks_{0}.zip".format(quest_name), as_attachment=True)
|
||||
|
||||
|
||||
'''
|
||||
@npc_views.route('/party')
|
||||
@npc_views.route('/party/<int:base_level>')
|
||||
def generate_npc_party(base_level=None):
|
||||
party = None
|
||||
if base_level:
|
||||
party = create_party(base_level)
|
||||
|
||||
# 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)
|
||||
|
||||
@npc_views.route('/single')
|
||||
@npc_views.route('/single/<int:base_level>/<int:guild_id>')
|
||||
def generate_single_npc(base_level=None, guild_id=0):
|
||||
guilds = CharacterClass.query.filter(CharacterClass.bucket.notin_(['Demi-Human'])).all()
|
||||
|
||||
npc = None
|
||||
if base_level:
|
||||
npc = create_npc(base_level, guild_id)
|
||||
|
||||
# If asked for JSON, return the npc, otherwise render HTML template
|
||||
if request.args.get('format', 'html') == 'json':
|
||||
return jsonify(npc)
|
||||
return render_template('generate_single_npc.html', npc=npc, base_level=base_level, guilds=guilds, guild_id=guild_id)
|
||||
|
||||
@npc_views.route('/spells')
|
||||
def spell_list():
|
||||
spells = Spell.query.all()
|
||||
return render_template('spell_list.html', spells=spells)
|
||||
'''
|
Binary file not shown.
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 4.4 MiB |
@ -5,6 +5,7 @@
|
||||
('.header', 'generate', 'Generate'),
|
||||
('.header', 'other', 'Other'),
|
||||
('/npc/spells', 'spells', 'Spells'),
|
||||
('/quest/list', 'questlist', 'Quests'),
|
||||
('/api/schema', 'api', 'API'),
|
||||
] %}
|
||||
{% set generation_bar = [
|
||||
|
@ -7,7 +7,7 @@
|
||||
<h1 class="uk-text-center"><strong>Adventurer Conqueror King</strong>Handbook</h1>
|
||||
</div>
|
||||
<div id="frame-container">
|
||||
<iframe id="handbook-frame" src="https://atr0phy.net/acks/handbook"/>
|
||||
<iframe id="handbook-frame" src="https://atr0phy.net/acks/handbook"></iframe>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -11,7 +11,8 @@
|
||||
<p>
|
||||
Welcome to the Adventurer Conqueror King toolkit for the Legends of Palisma campaign.
|
||||
You've had a long journey... three months aboard a ship to reach the new continent,
|
||||
many thousands of miles across the Immortal Sea from Branborne and any sense of familiarity.
|
||||
many thousands of miles across the Immortal Sea from Branborne and away from any sense
|
||||
of familiarity.
|
||||
</p>
|
||||
<p>
|
||||
What adventures await you in an age when empires totter on the brink of war, and terrible
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% set active_page = "handbook" %}
|
||||
{% set active_page = "treasure" %}
|
||||
|
||||
{% set treasure_letters = [
|
||||
("A", "Incidental 275gp"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user