diff --git a/acks/__init__.py b/acks/__init__.py index 3e85d55..7953ce2 100644 --- a/acks/__init__.py +++ b/acks/__init__.py @@ -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) diff --git a/acks/quest/commands.py b/acks/quest/commands.py new file mode 100644 index 0000000..a1c631c --- /dev/null +++ b/acks/quest/commands.py @@ -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() diff --git a/acks/quest/quest_manager.py b/acks/quest/quest_manager.py new file mode 100644 index 0000000..ac6e730 --- /dev/null +++ b/acks/quest/quest_manager.py @@ -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 diff --git a/acks/quest/templates/quest_detail.html b/acks/quest/templates/quest_detail.html new file mode 100644 index 0000000..09d54ad --- /dev/null +++ b/acks/quest/templates/quest_detail.html @@ -0,0 +1,86 @@ +{% extends "base.html" %} +{% set active_page = "quest_detail" %} + + +{% block title %}ACKS Quest Detail{% endblock %} +{% block content %} +
+

Adventurer Conqueror KingQuest Detail

+
+{% if quest %} +
+
+

{{ quest['name'] }}

+
+
+ Info Window + {% if quest['tsv'] %} + TSV File + {% endif %} + Download +
+ +
+{% endif %} +{% endblock %} + +{% block head %} + + + +{% endblock %} diff --git a/acks/quest/templates/quest_list.html b/acks/quest/templates/quest_list.html new file mode 100644 index 0000000..0b23b78 --- /dev/null +++ b/acks/quest/templates/quest_list.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} +{% set active_page = "quest_list" %} + + +{% block title %}ACKS Quest List{% endblock %} +{% block content %} +
+

Adventurer Conqueror KingQuest Board (Internal)

+
+{% if quest_map %} +
+ +
+{% endif %} +{% endblock %} + +{% block head %} + + + +{% endblock %} diff --git a/acks/quest/views.py b/acks/quest/views.py new file mode 100644 index 0000000..a1cce18 --- /dev/null +++ b/acks/quest/views.py @@ -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//') +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///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/') +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//') +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) +''' diff --git a/acks/static/PalismaContinent.png b/acks/static/PalismaContinent.png index 64ebd6f..a833aa9 100644 Binary files a/acks/static/PalismaContinent.png and b/acks/static/PalismaContinent.png differ diff --git a/acks/templates/base.html b/acks/templates/base.html index 1c6a61a..534d9f1 100644 --- a/acks/templates/base.html +++ b/acks/templates/base.html @@ -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 = [ diff --git a/acks/templates/handbook.html b/acks/templates/handbook.html index 0cb1a5e..67ee341 100644 --- a/acks/templates/handbook.html +++ b/acks/templates/handbook.html @@ -7,7 +7,7 @@

Adventurer Conqueror KingHandbook

-
{% endblock %} diff --git a/acks/templates/index.html b/acks/templates/index.html index 939bd69..a48034e 100644 --- a/acks/templates/index.html +++ b/acks/templates/index.html @@ -11,7 +11,8 @@

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.

What adventures await you in an age when empires totter on the brink of war, and terrible diff --git a/acks/templates/treasure.html b/acks/templates/treasure.html index 0594117..a3c2477 100644 --- a/acks/templates/treasure.html +++ b/acks/templates/treasure.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% set active_page = "handbook" %} +{% set active_page = "treasure" %} {% set treasure_letters = [ ("A", "Incidental 275gp"),