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
|
from acks.npc.views import npc_views
|
||||||
app.register_blueprint(npc_views)
|
app.register_blueprint(npc_views)
|
||||||
|
|
||||||
|
from acks.quest.views import quest_views
|
||||||
|
app.register_blueprint(quest_views)
|
||||||
|
|
||||||
# Load our CLI commands
|
# Load our CLI commands
|
||||||
from acks.commands import default_cli
|
from acks.commands import default_cli
|
||||||
app.cli.add_command(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', 'generate', 'Generate'),
|
||||||
('.header', 'other', 'Other'),
|
('.header', 'other', 'Other'),
|
||||||
('/npc/spells', 'spells', 'Spells'),
|
('/npc/spells', 'spells', 'Spells'),
|
||||||
|
('/quest/list', 'questlist', 'Quests'),
|
||||||
('/api/schema', 'api', 'API'),
|
('/api/schema', 'api', 'API'),
|
||||||
] %}
|
] %}
|
||||||
{% set generation_bar = [
|
{% set generation_bar = [
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<h1 class="uk-text-center"><strong>Adventurer Conqueror King</strong>Handbook</h1>
|
<h1 class="uk-text-center"><strong>Adventurer Conqueror King</strong>Handbook</h1>
|
||||||
</div>
|
</div>
|
||||||
<div id="frame-container">
|
<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>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
<p>
|
<p>
|
||||||
Welcome to the Adventurer Conqueror King toolkit for the Legends of Palisma campaign.
|
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,
|
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>
|
||||||
<p>
|
<p>
|
||||||
What adventures await you in an age when empires totter on the brink of war, and terrible
|
What adventures await you in an age when empires totter on the brink of war, and terrible
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% set active_page = "handbook" %}
|
{% set active_page = "treasure" %}
|
||||||
|
|
||||||
{% set treasure_letters = [
|
{% set treasure_letters = [
|
||||||
("A", "Incidental 275gp"),
|
("A", "Incidental 275gp"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user