Compare commits
5 Commits
a683cd9007
...
d00d53f304
Author | SHA1 | Date | |
---|---|---|---|
d00d53f304 | |||
9017792b3c | |||
79f9260d13 | |||
5c02d8f9fa | |||
b3c8ae7328 |
@ -55,6 +55,7 @@ def populate_npc_database():
|
|||||||
spell.arcane = 0
|
spell.arcane = 0
|
||||||
if spell.divine == '':
|
if spell.divine == '':
|
||||||
spell.divine = 0
|
spell.divine = 0
|
||||||
|
spell.description = spell.description.strip()
|
||||||
db.session.bulk_save_objects(spells)
|
db.session.bulk_save_objects(spells)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import base64
|
||||||
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from ..models import db, BaseModel
|
from ..models import db, BaseModel
|
||||||
|
|
||||||
@ -109,6 +111,10 @@ class Spell(BaseModel):
|
|||||||
def is_arcane(self):
|
def is_arcane(self):
|
||||||
return bool(self.arcane and self.arcane > 0)
|
return bool(self.arcane and self.arcane > 0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dom_id(self):
|
||||||
|
return self.name.lower().replace("*", "").replace("'", "").replace(",", "").replace(" ", "_")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def roll20_format(self):
|
def roll20_format(self):
|
||||||
spell_dict = {
|
spell_dict = {
|
||||||
@ -120,7 +126,7 @@ class Spell(BaseModel):
|
|||||||
'is_divine': self.is_divine,
|
'is_divine': self.is_divine,
|
||||||
'arcane': self.arcane,
|
'arcane': self.arcane,
|
||||||
'is_arcane': self.is_arcane,
|
'is_arcane': self.is_arcane,
|
||||||
'description': self.description #.replace('"', '\\"').replace("'", "\\'")
|
'description': base64.b64encode(self.description.encode('ascii')).decode('ascii')
|
||||||
}
|
}
|
||||||
return spell_dict
|
return spell_dict
|
||||||
|
|
||||||
@ -283,6 +289,13 @@ class CharacterNPC(BaseModel):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.guild.is_divine_spellcaster or self.guild.is_arcane_spellcaster:
|
||||||
|
npc_dict['spells'] = []
|
||||||
|
spell_list = self.spell_list()
|
||||||
|
for level in spell_list:
|
||||||
|
for spell in spell_list[level]:
|
||||||
|
npc_dict['spells'].append(spell.roll20_format)
|
||||||
|
|
||||||
if self.ranged:
|
if self.ranged:
|
||||||
npc_dict['ranged'] = {
|
npc_dict['ranged'] = {
|
||||||
'name': self.ranged.name,
|
'name': self.ranged.name,
|
||||||
|
@ -127,18 +127,20 @@ def calc_armour(armour_mod, armours):
|
|||||||
armourval = len(armours) - 1
|
armourval = len(armours) - 1
|
||||||
return armours[armourval]
|
return armours[armourval]
|
||||||
|
|
||||||
def generate_npc(base_level, data):
|
def generate_npc(base_level, data, guild_id=False):
|
||||||
npc = CharacterNPC()
|
npc = CharacterNPC()
|
||||||
|
|
||||||
|
if not guild_id:
|
||||||
npc.guild = npc_class(['Demi-Human'])
|
npc.guild = npc_class(['Demi-Human'])
|
||||||
|
else:
|
||||||
|
npc.guild = CharacterClass.query.filter_by(id=guild_id).first()
|
||||||
|
|
||||||
npc.level = npc_baselevel(base_level)
|
npc.level = npc_baselevel(base_level)
|
||||||
if npc.level > npc.guild.maximum_level:
|
if npc.level > npc.guild.maximum_level:
|
||||||
npc.level = npc.guild.maximum_level
|
npc.level = npc.guild.maximum_level
|
||||||
npc.alignment = npc_alignment()
|
npc.alignment = npc_alignment()
|
||||||
|
|
||||||
abilities = npc_abilities()
|
|
||||||
npc.update(npc_abilities())
|
npc.update(npc_abilities())
|
||||||
|
|
||||||
npc.hit_points = calc_hp(attribute_mod(npc.constitution), npc.guild.hit_die_size, npc.level)
|
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.armour = calc_armour(npc.guild.armour_modifier, data['armours'])
|
||||||
@ -194,7 +196,7 @@ def name_party(party):
|
|||||||
|
|
||||||
return party
|
return party
|
||||||
|
|
||||||
def create_party(base_level):
|
def load_db_data():
|
||||||
data = {
|
data = {
|
||||||
'armours': EquipmentArmour.query.all(),
|
'armours': EquipmentArmour.query.all(),
|
||||||
'ranged': {
|
'ranged': {
|
||||||
@ -224,6 +226,16 @@ def create_party(base_level):
|
|||||||
spells.extend(data['spells']['divine'][spell.divine])
|
spells.extend(data['spells']['divine'][spell.divine])
|
||||||
data['spells']['divine'][spell.divine] = spells
|
data['spells']['divine'][spell.divine] = spells
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def create_npc(base_level, guild_id):
|
||||||
|
data = load_db_data()
|
||||||
|
if guild_id:
|
||||||
|
return name_party([generate_npc(base_level, data, guild_id=guild_id)])[0]
|
||||||
|
return name_party([generate_npc(base_level, data)])[0]
|
||||||
|
|
||||||
|
def create_party(base_level):
|
||||||
|
data = load_db_data()
|
||||||
return name_party([generate_npc(base_level, data) for x in range(0, number_encountered())])
|
return name_party([generate_npc(base_level, data) for x in range(0, number_encountered())])
|
||||||
|
|
||||||
def print_party(party):
|
def print_party(party):
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% 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">
|
<div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
|
||||||
<h1>Adventurer Conqueror King NPC Party Generator</h1>
|
<h1 class="uk-text-center"><strong>Adventurer Conqueror King</strong>NPC Party Generator</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
|
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
|
||||||
<div>
|
<div>
|
||||||
@ -238,6 +238,11 @@ div.save-block > div > div:last-child {
|
|||||||
div.acks-npc-card {
|
div.acks-npc-card {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
|
h1 strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 50%;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var party = [];
|
var party = [];
|
||||||
@ -270,7 +275,7 @@ div.acks-npc-card {
|
|||||||
|
|
||||||
// Fill in spell details
|
// Fill in spell details
|
||||||
document.querySelector('#spell-modal-title').innerText = modal_spell.name;
|
document.querySelector('#spell-modal-title').innerText = modal_spell.name;
|
||||||
document.querySelector('#spell-modal-desc').innerText = modal_spell.description;
|
document.querySelector('#spell-modal-desc').innerText = atob(modal_spell.description);
|
||||||
|
|
||||||
if(spell.range) {
|
if(spell.range) {
|
||||||
document.querySelector('#spell-modal-range').innerText = modal_spell.range;
|
document.querySelector('#spell-modal-range').innerText = modal_spell.range;
|
||||||
|
355
acks/npc/templates/generate_single_npc.html
Normal file
355
acks/npc/templates/generate_single_npc.html
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "npcsingle" %}
|
||||||
|
|
||||||
|
{% block title %}Single NPC Generation{% 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>NPC Generator</h1>
|
||||||
|
</div>
|
||||||
|
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
|
||||||
|
<div>
|
||||||
|
<label for="guild_select">Class: </label>
|
||||||
|
<select name="guild_select" id="guild_select" class="uk-select">
|
||||||
|
<option value="0">Random</option>
|
||||||
|
{% for guild in guilds %}
|
||||||
|
<option value="{{ guild.id }}"{% if guild.id == guild_id %} selected{% endif%}>{{ guild.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin-left">
|
||||||
|
<label for="base_level">Base level of npc to generate: </label>
|
||||||
|
<input type="number" name="base_level" id="base_level" class="uk-input" value="{{ base_level if base_level else 1 }}" min="0" max="14">
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin-left">
|
||||||
|
<button class="uk-button uk-button-primary" onclick="generateNPC();">Generate</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="uk-divider-icon">
|
||||||
|
|
||||||
|
{% if npc %}
|
||||||
|
<div class="uk-flex uk-flex-between uk-margin-large-top">
|
||||||
|
<h3 class="uk-display-inline-block">Generated NPC</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-flex-center" uk-grid>
|
||||||
|
<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 class="uk-text-bold">Level</div>
|
||||||
|
<div>{{ npc.level }}</div>
|
||||||
|
</div>
|
||||||
|
<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 title="{{ npc.str_mod }}">{{ npc.str }}</div>
|
||||||
|
<div title="{{ npc.str_mod }}">Str</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div title="{{ npc.int_mod }}">{{ npc.int }}</div>
|
||||||
|
<div title="{{ npc.int_mod }}">Int</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div title="{{ npc.wis_mod }}">{{ npc.wis }}</div>
|
||||||
|
<div title="{{ npc.wis_mod }}">Wis</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div title="{{ npc.dex_mod }}">{{ npc.dex }}</div>
|
||||||
|
<div title="{{ npc.dex_mod }}">Dex</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div title="{{ npc.con_mod }}">{{ npc.con }}</div>
|
||||||
|
<div title="{{ npc.con_mod }}">Con</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div title="{{ npc.chr_mod }}">{{ npc.chr }}</div>
|
||||||
|
<div title="{{ npc.chr_mod }}">Chr</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 & 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.is_divine_spellcaster and not npc.is_arcane_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 spell-table">
|
||||||
|
<thead>
|
||||||
|
<tr> <th>Name</th><th>Range</th><th>Duration</th><th>Level</th> </tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for level in npc.spell_list() %}
|
||||||
|
{% for spell in npc.spell_list()[level] %}
|
||||||
|
<tr>
|
||||||
|
<td data-spell-id="{{ spell.id }}" onclick="showSpellModal();">{{ spell.name }}</td>
|
||||||
|
<td>{{ spell.range }}</td>
|
||||||
|
<td>{{ spell.duration }}</td>
|
||||||
|
<td>{{ spell.level_for(npc) }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="spell-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"><span id="spell-modal-title">{Spell Title}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="uk-modal-body">
|
||||||
|
<ul>
|
||||||
|
<li>Range: <span id="spell-modal-range">{Spell Range}</span></li>
|
||||||
|
<li>Duration: <span id="spell-modal-duration">{Spell Duration}</span></li>
|
||||||
|
<li><span id="spell-modal-school">{Spell School}</span></li>
|
||||||
|
</ul>
|
||||||
|
<p id="spell-modal-desc">{Spell Description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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 npc %}
|
||||||
|
<label><input class="uk-checkbox" type="checkbox" name="ch[[ loop.index ]]" onchange="prepareSelectiveExport();" checked> {{ npc.name }}</label>
|
||||||
|
{% 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>
|
||||||
|
table.item-table, table.item-table th, table.spell-table, table.spell-table th {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
div.stat-block > div {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3px;
|
||||||
|
width: 26px;
|
||||||
|
}
|
||||||
|
div.stat-block > div > div:first-child {
|
||||||
|
font-weight: bold;
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
div.stat-block > div > div:last-child {
|
||||||
|
font-weight: bold;
|
||||||
|
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: 400px;
|
||||||
|
}
|
||||||
|
h1 strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 50%;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var party = [];
|
||||||
|
|
||||||
|
function generateNPC() {
|
||||||
|
// Base level
|
||||||
|
let bl = document.querySelector('#base_level').value;
|
||||||
|
if(bl < 0) { bl = 0; }
|
||||||
|
if(bl > 14) { bl = 14; }
|
||||||
|
|
||||||
|
let gl = document.querySelector('#guild_select').value;
|
||||||
|
window.location = "/npc/single/" + bl.toString() + "/" + gl.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSpellModal() {
|
||||||
|
spells = new Set();
|
||||||
|
{% if npc %}
|
||||||
|
{% for level in npc.spell_list() %}
|
||||||
|
{% for spell in npc.spell_list()[level] %}
|
||||||
|
spells.add({{ spell.roll20_format | tojson }});
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
var modal_spell, spell_id = event.target.dataset.spellId;
|
||||||
|
for(var spell of spells) {
|
||||||
|
if(spell.id == spell_id) {
|
||||||
|
modal_spell = spell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in spell details
|
||||||
|
document.querySelector('#spell-modal-title').innerText = modal_spell.name;
|
||||||
|
document.querySelector('#spell-modal-desc').innerText = atob(modal_spell.description);
|
||||||
|
|
||||||
|
if(spell.range) {
|
||||||
|
document.querySelector('#spell-modal-range').innerText = modal_spell.range;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(spell.duration) {
|
||||||
|
document.querySelector('#spell-modal-duration').innerText = modal_spell.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
var school = '';
|
||||||
|
if(modal_spell.is_arcane) {
|
||||||
|
school += 'Arcane ' + modal_spell.arcane + ' ';
|
||||||
|
}
|
||||||
|
if(modal_spell.is_divine) {
|
||||||
|
school += 'Divine ' + modal_spell.divine + ' ';
|
||||||
|
}
|
||||||
|
document.querySelector('#spell-modal-school').innerText = school;
|
||||||
|
|
||||||
|
UIkit.modal(document.querySelector('#spell-modal')).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showExportModal() {
|
||||||
|
{% if npc %}
|
||||||
|
party.push(JSON.parse('{{ npc.roll20_format | tojson }}'));
|
||||||
|
{% 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 %}
|
@ -4,15 +4,31 @@
|
|||||||
{% block title %}Spell List{% endblock %}
|
{% block title %}Spell List{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
|
<div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
|
||||||
<h1>Adventurer Conqueror King Spell List</h1>
|
<h1 class="uk-text-center"><strong>Adventurer Conqueror King</strong>Spell Reference</h1>
|
||||||
|
</div>
|
||||||
|
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-small-bottom">
|
||||||
|
<div>
|
||||||
|
<label for="spell_level">Spell Level</label>
|
||||||
|
<input type="number" name="spell_level" id="spell_level" class="uk-input" value="0">
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin-left">
|
||||||
|
<div>
|
||||||
|
<label for="divine_toggle">Divine</label>
|
||||||
|
<input type="checkbox" name="divine_toggle" id="divine_toggle" class="uk-checkbox" checked>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="arcane_toggle">Arcane</label>
|
||||||
|
<input type="checkbox" name="arcane_toggle" id="arcane_toggle" class="uk-checkbox" checked>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
|
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
|
||||||
<div>
|
<div>
|
||||||
<label for="base_level">TURN INTO FILTER AREA</label>
|
<label for="spell_query">Name Search</label>
|
||||||
<input type="number" name="base_level" id="base_level" class="uk-input" value="{{ base_level if base_level else 1 }}" >
|
<input type="text" name="spell_query" id="spell_query" class="uk-input"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-margin-left">
|
<div class="uk-margin-left">
|
||||||
<button class="uk-button uk-button-primary" onclick="generateParty();">Generate</button>
|
<button class="uk-button uk-button-primary" onclick="applyFilters();">Filter</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -21,7 +37,7 @@
|
|||||||
{% if spells %}
|
{% if spells %}
|
||||||
<div class="uk-grid-medium uk-grid-match uk-flex-center" uk-grid>
|
<div class="uk-grid-medium uk-grid-match uk-flex-center" uk-grid>
|
||||||
{% for spell in spells %}
|
{% for spell in spells %}
|
||||||
<div class="acks-npc-card">
|
<div class="acks-npc-card" id="{{ spell.dom_id }}">
|
||||||
<div class="uk-card uk-card-body uk-card-default uk-box-shadow-hover-large">
|
<div class="uk-card uk-card-body uk-card-default uk-box-shadow-hover-large">
|
||||||
<h4 class="uk-card-title uk-margin-small-top">{{ spell.name }}</h4>
|
<h4 class="uk-card-title uk-margin-small-top">{{ spell.name }}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
@ -39,6 +55,67 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var spells = [{% for spell in spells %} {{ spell.roll20_format | tojson }}, {% endfor %}];
|
||||||
|
|
||||||
|
function filterSpell(filters, spell) {
|
||||||
|
function name2id(name) {
|
||||||
|
return "#" + name.toLowerCase().replace(/[\*',]/g, "").replace(/\s/g, "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
let hide = false;
|
||||||
|
|
||||||
|
// Handle search query filtering
|
||||||
|
if(filters.spell_query) {
|
||||||
|
if(spell.name.toLowerCase().indexOf(filters.spell_query.toLowerCase()) < 0) {
|
||||||
|
hide = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle school and level filtering
|
||||||
|
if(!filters.arcane_included && spell.is_arcane) {
|
||||||
|
hide = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!filters.divine_included && spell.is_divine) {
|
||||||
|
hide = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(filters.spell_level > 0) {
|
||||||
|
if(spell.is_arcane && spell.arcane != filters.spell_level) {
|
||||||
|
hide = true;
|
||||||
|
}
|
||||||
|
if(spell.is_divine && spell.divine != filters.spell_level) {
|
||||||
|
hide = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide this spell-card if appropriate
|
||||||
|
if(hide) {
|
||||||
|
document.querySelector(name2id(spell.name)).classList.add('uk-hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFiltering() {
|
||||||
|
var hidden_spells = document.querySelectorAll("div.acks-npc-card.uk-hidden");
|
||||||
|
for(let hd of hidden_spells) {
|
||||||
|
hd.classList.remove('uk-hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilters() {
|
||||||
|
resetFiltering();
|
||||||
|
|
||||||
|
filters = {
|
||||||
|
spell_level: document.querySelector('#spell_level').value,
|
||||||
|
spell_query: document.querySelector('#spell_query').value,
|
||||||
|
arcane_included: document.querySelector('#arcane_toggle').checked,
|
||||||
|
divine_included: document.querySelector('#divine_toggle').checked,
|
||||||
|
};
|
||||||
|
|
||||||
|
spells.forEach((spell, index) => { filterSpell(filters, spell); });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<style>
|
<style>
|
||||||
table.item-table, table.item-table th {
|
table.item-table, table.item-table th {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -69,5 +146,10 @@ div.save-block > div > div:last-child {
|
|||||||
div.acks-npc-card {
|
div.acks-npc-card {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
}
|
}
|
||||||
|
h1 strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 50%;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -6,8 +6,14 @@ from flask import (
|
|||||||
Blueprint
|
Blueprint
|
||||||
)
|
)
|
||||||
|
|
||||||
from .npc_party import create_party
|
from .npc_party import (
|
||||||
from .models import Spell
|
create_party,
|
||||||
|
create_npc
|
||||||
|
)
|
||||||
|
from .models import (
|
||||||
|
Spell,
|
||||||
|
CharacterClass
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
npc_views = Blueprint(
|
npc_views = Blueprint(
|
||||||
@ -29,6 +35,20 @@ def generate_npc_party(base_level=None):
|
|||||||
return jsonify([npc.roll20_format for npc in party])
|
return jsonify([npc.roll20_format for npc in party])
|
||||||
return render_template('generate_npc_party.html', party=party, base_level=base_level)
|
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')
|
@npc_views.route('/spells')
|
||||||
def spell_list():
|
def spell_list():
|
||||||
spells = Spell.query.all()
|
spells = Spell.query.all()
|
||||||
|
BIN
acks/static/WorldMap.png
Normal file
BIN
acks/static/WorldMap.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 MiB |
@ -1,18 +1,22 @@
|
|||||||
{% set navigation_bar = [
|
{% set navigation_bar = [
|
||||||
('/', 'index', 'Home'),
|
('/', 'index', 'Home'),
|
||||||
('/handbook', 'handbook', 'Handbook'),
|
('/handbook', 'handbook', 'Handbook'),
|
||||||
('/npc/party', 'npcparty', 'NPC Party'),
|
('/worldmap', 'worldmap', 'World Map'),
|
||||||
|
('', 'generate', 'Generate'),
|
||||||
('/npc/spells', 'spells', 'Spells'),
|
('/npc/spells', 'spells', 'Spells'),
|
||||||
('/api/schema', 'api', 'API'),
|
('/api/schema', 'api', 'API'),
|
||||||
('#', 'treasure', 'Treasure'),
|
] %}
|
||||||
('#', 'henchmen', 'Henchmen'),
|
{% set generation_bar = [
|
||||||
|
('/npc/party', 'npcparty', 'NPC Party'),
|
||||||
|
('/npc/single', 'npcsingle', 'Single NPC'),
|
||||||
|
('/treasure', 'treasure', 'Treasure'),
|
||||||
] %}
|
] %}
|
||||||
{% set active_page = active_page|default('index') %}
|
{% 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 %} - Palisma 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">
|
||||||
@ -31,18 +35,52 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="uk-navbar-container" uk-navbar>
|
<nav class="uk-navbar-container" uk-navbar>
|
||||||
<div class="uk-navbar-left">
|
<div class="uk-navbar-left">
|
||||||
<a href="" class="uk-navbar-item uk-logo">Atr0phy ACKS</a>
|
<a href="" class="uk-hidden@m uk-button-default uk-margin-small-left uk-margin-small-right" uk-toggle="target: #offcanvas-nav" uk-icon="menu"></a>
|
||||||
|
<a href="" class="uk-navbar-item uk-logo">Palisma ACKS</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-center">
|
<div class="uk-navbar-center uk-visible@m">
|
||||||
<ul class="uk-navbar-nav">
|
<ul class="uk-navbar-nav">
|
||||||
{% for href, id, label in navigation_bar %}
|
{% for href, id, label in navigation_bar %}
|
||||||
<li {% if id == active_page %} class="uk-active" {% endif %}>
|
<li {% if id == active_page %} class="uk-active" {% endif %}>
|
||||||
|
{% if id == 'generate' %}
|
||||||
|
<a href="">{{ label |e }}</a>
|
||||||
|
<div class="uk-navbar-dropdown">
|
||||||
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
{% for ghref, gid, glabel in generation_bar %}
|
||||||
|
<li><a href="{{ ghref | e }}">{{ glabel|e }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
<a href="{{ href|e }}">{{ label|e }}</a>
|
<a href="{{ href|e }}">{{ label|e }}</a>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div id="offcanvas-nav" uk-offcanvas="overlay: true">
|
||||||
|
<div class="uk-offcanvas-bar uk-flex uk-flex-column">
|
||||||
|
<ul class="uk-nav uk-nav-primary uk-nav-center uk-margin-auto-vertical">
|
||||||
|
{% for href, id, label in navigation_bar %}
|
||||||
|
<li {% if id == active_page %} class="uk-active" {% endif %}>
|
||||||
|
{% if id == 'generate' %}
|
||||||
|
<a href="">{{ label |e }}</a>
|
||||||
|
<div class="uk-navbar-dropdown">
|
||||||
|
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||||
|
{% for ghref, gid, glabel in generation_bar %}
|
||||||
|
<li><a href="{{ ghref | e }}">{{ glabel|e }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ href|e }}">{{ label|e }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="uk-container">
|
<div class="uk-container">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
{% block title %}ACKS Handbook{% endblock %}
|
{% block title %}ACKS Handbook{% endblock %}
|
||||||
{% block content %}
|
{% 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>Handbook</h1>
|
||||||
|
</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"/>
|
||||||
</div>
|
</div>
|
||||||
@ -22,5 +25,10 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
h1 strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 50%;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -3,11 +3,17 @@
|
|||||||
|
|
||||||
{% block title %}ACKS Toolset Home{% endblock %}
|
{% block title %}ACKS Toolset Home{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div>
|
<div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
|
||||||
|
<h1 class="uk-text-center"><strong>Adventurer Conqueror King</strong>Home</h1>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<style>
|
<style>
|
||||||
|
h1 strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 50%;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
68
acks/templates/treasure.html
Normal file
68
acks/templates/treasure.html
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "handbook" %}
|
||||||
|
|
||||||
|
{% set treasure_letters = [
|
||||||
|
("A", "Incidental 275gp"),
|
||||||
|
("B", "Hoarder 500gp"),
|
||||||
|
("C", "Incidental 700gp"),
|
||||||
|
("D", "Hoarder 1,000gp"),
|
||||||
|
("E", "Raider 1,250gp"),
|
||||||
|
("F", "Incidental 1,500gp"),
|
||||||
|
("G", "Raider 2,000gp"),
|
||||||
|
("H", "Hoarder 2,500gp"),
|
||||||
|
("I", "Incidental 3,250gp"),
|
||||||
|
("J", "Raider 4,000gp"),
|
||||||
|
("K", "Incidental 5,000gp"),
|
||||||
|
("L", "Raider 6,000gp"),
|
||||||
|
("M", "Incidental 8,000gp"),
|
||||||
|
("N", "Hoarder 9,000gp"),
|
||||||
|
("O", "Raider 12,000gp"),
|
||||||
|
("P", "Incidental 17,000gp"),
|
||||||
|
("Q", "Hoarder 22,000gp"),
|
||||||
|
("R", "Hoarder 45,000gp"),
|
||||||
|
] %}
|
||||||
|
|
||||||
|
{% block title %}ACKS Treasure Generator{% 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>Treasure Generator</h1>
|
||||||
|
</div>
|
||||||
|
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
|
||||||
|
<div>
|
||||||
|
<label for="treasure-type" class="uk-form-label">Select Treasure Type</label>
|
||||||
|
<select id="treasure-type" class="uk-select">
|
||||||
|
<option value="">Please select one</option>
|
||||||
|
{% for v, l in treasure_letters %}
|
||||||
|
<option value="{{ v }}" {% if treasure_type == v %} selected="true" {% endif %}>{{ v }} ({{ l }})</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin-left">
|
||||||
|
<button class="uk-button uk-button-primary" onclick="generateTreasure();">Generate</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if generated_treasure %}
|
||||||
|
<div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
|
||||||
|
<div id="treasure-content">
|
||||||
|
{{ generated_treasure|safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<style>
|
||||||
|
h1 strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 50%;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function generateTreasure() {
|
||||||
|
var treasure_type = document.querySelector("select#treasure-type").value;
|
||||||
|
window.location = "/treasure/" + treasure_type.toString();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
23
acks/templates/worldmap.html
Normal file
23
acks/templates/worldmap.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% set active_page = "worldmap" %}
|
||||||
|
|
||||||
|
{% block title %}World Map{% 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>World Map</h1>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div>
|
||||||
|
<img id="worldmap-img" src="https://acks.atr0phy.net/static/WorldMap.png"/>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<style>
|
||||||
|
h1 strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 50%;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
@ -1,3 +1,4 @@
|
|||||||
|
import requests
|
||||||
from flask import current_app, Blueprint, render_template, url_for, redirect
|
from flask import current_app, Blueprint, render_template, url_for, redirect
|
||||||
|
|
||||||
|
|
||||||
@ -11,3 +12,22 @@ def index():
|
|||||||
@default_views.route('/handbook')
|
@default_views.route('/handbook')
|
||||||
def handbook():
|
def handbook():
|
||||||
return render_template('handbook.html')
|
return render_template('handbook.html')
|
||||||
|
|
||||||
|
@default_views.route('/worldmap')
|
||||||
|
def worldmap():
|
||||||
|
return render_template('worldmap.html')
|
||||||
|
|
||||||
|
@default_views.route('/treasure')
|
||||||
|
@default_views.route('/treasure/<string:treasure_type>')
|
||||||
|
def treasure(treasure_type=None):
|
||||||
|
if treasure_type is not None:
|
||||||
|
headers = {'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'}
|
||||||
|
payload = {
|
||||||
|
"treasure_type": treasure_type,
|
||||||
|
"form_id": "acks_treasure_form",
|
||||||
|
"form_build_id": "form-PqO-1VglfW4Q3x1fm7HMewhLFjst2oxY5AR_m6WOGBg"
|
||||||
|
}
|
||||||
|
response = requests.request("POST", "http://autarch.co/system/ajax", data=payload, headers=headers)
|
||||||
|
generated_treasure = response.json()[1]["data"].replace('class="form-textarea"', 'class="uk-textarea"')
|
||||||
|
return render_template('treasure.html', generated_treasure=generated_treasure, treasure_type=treasure_type)
|
||||||
|
return render_template('treasure.html')
|
||||||
|
144
roll20/base64.js
Normal file
144
roll20/base64.js
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// Github: https://github.com/shdwjk/Roll20API/blob/master/Base64/Base64.js
|
||||||
|
// By: The Aaron, Arcane Scriptomancer
|
||||||
|
// Contact: https://app.roll20.net/users/104025/the-aaron
|
||||||
|
// modified from: http://www.webtoolkit.info/
|
||||||
|
|
||||||
|
const Base64 = (() => { // eslint-disable-line no-unused-vars
|
||||||
|
const version = '0.3.2';
|
||||||
|
const lastUpdate = 1576507905;
|
||||||
|
const keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||||
|
|
||||||
|
const checkInstall = () => {
|
||||||
|
log('-=> Base64 v'+version+' <=- ['+(new Date(lastUpdate*1000))+']');
|
||||||
|
};
|
||||||
|
|
||||||
|
// private method for UTF-8 encoding
|
||||||
|
const utf8_encode = (string) => {
|
||||||
|
let utftext = '';
|
||||||
|
|
||||||
|
for (let n = 0; n < string.length; n++) {
|
||||||
|
|
||||||
|
let c1 = string.charCodeAt(n);
|
||||||
|
|
||||||
|
if (c1 < 128) {
|
||||||
|
utftext += String.fromCharCode(c1);
|
||||||
|
}
|
||||||
|
else if((c1 > 127) && (c1 < 2048)) {
|
||||||
|
utftext += String.fromCharCode((c1 >> 6) | 192);
|
||||||
|
utftext += String.fromCharCode((c1 & 63) | 128);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
utftext += String.fromCharCode((c1 >> 12) | 224);
|
||||||
|
utftext += String.fromCharCode(((c1 >> 6) & 63) | 128);
|
||||||
|
utftext += String.fromCharCode((c1 & 63) | 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return utftext;
|
||||||
|
};
|
||||||
|
|
||||||
|
// private method for UTF-8 decoding
|
||||||
|
const utf8_decode = (utftext) => {
|
||||||
|
let string = '';
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while ( i < utftext.length ) {
|
||||||
|
|
||||||
|
let c1 = utftext.charCodeAt(i);
|
||||||
|
|
||||||
|
if (c1 < 128) {
|
||||||
|
string += String.fromCharCode(c1);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if((c1 > 191) && (c1 < 224)) {
|
||||||
|
let c2 = utftext.charCodeAt(i+1);
|
||||||
|
string += String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let c2 = utftext.charCodeAt(i+1);
|
||||||
|
let c3 = utftext.charCodeAt(i+2);
|
||||||
|
string += String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const encode = (input) => {
|
||||||
|
let output = '';
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
input = utf8_encode(input);
|
||||||
|
|
||||||
|
while (i < input.length) {
|
||||||
|
|
||||||
|
let chr1 = input.charCodeAt(i++);
|
||||||
|
let chr2 = input.charCodeAt(i++);
|
||||||
|
let chr3 = input.charCodeAt(i++);
|
||||||
|
|
||||||
|
let enc1 = chr1 >> 2;
|
||||||
|
let enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||||
|
let enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||||
|
let enc4 = chr3 & 63;
|
||||||
|
|
||||||
|
if (isNaN(chr2)) {
|
||||||
|
enc3 = enc4 = 64;
|
||||||
|
} else if (isNaN(chr3)) {
|
||||||
|
enc4 = 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
output = output +
|
||||||
|
keyStr.charAt(enc1) + keyStr.charAt(enc2) +
|
||||||
|
keyStr.charAt(enc3) + keyStr.charAt(enc4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
// public method for decoding
|
||||||
|
const decode = (input) => {
|
||||||
|
let output = '';
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
input = input.replace(/[^A-Za-z0-9+/=]/g, "");
|
||||||
|
|
||||||
|
while (i < input.length) {
|
||||||
|
|
||||||
|
let enc1 = keyStr.indexOf(input.charAt(i++));
|
||||||
|
let enc2 = keyStr.indexOf(input.charAt(i++));
|
||||||
|
let enc3 = keyStr.indexOf(input.charAt(i++));
|
||||||
|
let enc4 = keyStr.indexOf(input.charAt(i++));
|
||||||
|
|
||||||
|
let chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||||
|
let chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||||
|
let chr3 = ((enc3 & 3) << 6) | enc4;
|
||||||
|
|
||||||
|
output = output + String.fromCharCode(chr1);
|
||||||
|
|
||||||
|
if (enc3 !== 64) {
|
||||||
|
output = output + String.fromCharCode(chr2);
|
||||||
|
}
|
||||||
|
if (enc4 !== 64) {
|
||||||
|
output = output + String.fromCharCode(chr3);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
output = utf8_decode(output);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
on("ready",()=>{
|
||||||
|
checkInstall();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
encode: encode,
|
||||||
|
decode: decode
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
@ -120,7 +120,7 @@ div.sheet-currency-grid {
|
|||||||
}
|
}
|
||||||
div.sheet-spells-grid {
|
div.sheet-spells-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
row-gap: 5px;
|
row-gap: 5px;
|
||||||
column-gap: 10px;
|
column-gap: 10px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
@ -207,7 +207,7 @@ input.sheet-spells-input {
|
|||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
}
|
}
|
||||||
.sheet-grid-span-4-2 {
|
.sheet-grid-span-4-2 {
|
||||||
grid-column: 4 / span 2;
|
grid-column: 4 / span 3;
|
||||||
}
|
}
|
||||||
input.sheet-input-big {
|
input.sheet-input-big {
|
||||||
width: 55px!important;
|
width: 55px!important;
|
||||||
@ -250,7 +250,7 @@ input.sheet-input-big {
|
|||||||
}
|
}
|
||||||
.sheet-spells-title {
|
.sheet-spells-title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
grid-column: 1 / span 7;
|
grid-column: 1 / span 9;
|
||||||
}
|
}
|
||||||
.sheet-exp-title {
|
.sheet-exp-title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -357,3 +357,10 @@ div.sheet-npc { display: none; }
|
|||||||
/*input.sheet-block-switch { display: none; };*/
|
/*input.sheet-block-switch { display: none; };*/
|
||||||
input.sheet-block-switch:checked ~ div.sheet-main { display: none; }
|
input.sheet-block-switch:checked ~ div.sheet-main { display: none; }
|
||||||
input.sheet-block-switch:checked ~ div.sheet-npc { display: grid; }
|
input.sheet-block-switch:checked ~ div.sheet-npc { display: grid; }
|
||||||
|
|
||||||
|
textarea.sheet-txtarea {
|
||||||
|
height: 24px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
@ -284,7 +284,7 @@
|
|||||||
<div class="sheet-spells-title">
|
<div class="sheet-spells-title">
|
||||||
<h3>Spells Per Day</h3>
|
<h3>Spells Per Day</h3>
|
||||||
</div>
|
</div>
|
||||||
<span></span><span>One</span><span>Two</span><span>Three</span><span>Four</span><span>Five</span><span>Six</span>
|
<span></span><span>One</span><span>Two</span><span>Three</span><span>Four</span><span>Five</span><span>Six</span><span></span><span></span>
|
||||||
<span>Used</span>
|
<span>Used</span>
|
||||||
<input class="spells-input" type="text" name="attr_spells_l1">
|
<input class="spells-input" type="text" name="attr_spells_l1">
|
||||||
<input class="spells-input" type="text" name="attr_spells_l2">
|
<input class="spells-input" type="text" name="attr_spells_l2">
|
||||||
@ -292,6 +292,7 @@
|
|||||||
<input class="spells-input" type="text" name="attr_spells_l4">
|
<input class="spells-input" type="text" name="attr_spells_l4">
|
||||||
<input class="spells-input" type="text" name="attr_spells_l5">
|
<input class="spells-input" type="text" name="attr_spells_l5">
|
||||||
<input class="spells-input" type="text" name="attr_spells_l6">
|
<input class="spells-input" type="text" name="attr_spells_l6">
|
||||||
|
<span></span><span></span>
|
||||||
|
|
||||||
<span>Total</span>
|
<span>Total</span>
|
||||||
<input class="spells-input" type="text" name="attr_spells_l1_max">
|
<input class="spells-input" type="text" name="attr_spells_l1_max">
|
||||||
@ -300,20 +301,23 @@
|
|||||||
<input class="spells-input" type="text" name="attr_spells_l4_max">
|
<input class="spells-input" type="text" name="attr_spells_l4_max">
|
||||||
<input class="spells-input" type="text" name="attr_spells_l5_max">
|
<input class="spells-input" type="text" name="attr_spells_l5_max">
|
||||||
<input class="spells-input" type="text" name="attr_spells_l6_max">
|
<input class="spells-input" type="text" name="attr_spells_l6_max">
|
||||||
|
<span></span><span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spells-grid">
|
<div class="spells-grid">
|
||||||
<div class="sheet-spells-title sheet-margin-top">
|
<div class="sheet-spells-title sheet-margin-top">
|
||||||
<h3>Spellbook</h3>
|
<h3>Spellbook</h3>
|
||||||
</div>
|
</div>
|
||||||
<span class="grid-span-1-2">Spell Name</span><span>Level</span><span class="grid-span-4-2">Effect</span><span>Damage</span><span></span><span></span><span></span>
|
<span class="grid-span-1-2">Spell Name</span><span>Level</span><span class="grid-span-4-2">Effect</span><span>Range</span><span>Roll</span><span></span><span></span><span></span><span></span>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="repeating_spells">
|
<fieldset class="repeating_spells">
|
||||||
<div class="spells-grid">
|
<div class="spells-grid">
|
||||||
<input class="sheet-name-input grid-span-1-2" type="text" name="attr_spell_name">
|
<input class="sheet-name-input grid-span-1-2" type="text" name="attr_spell_name">
|
||||||
<div><input class="sheet-stat-input" type="text" name="attr_spell_level"></div>
|
<div><input class="sheet-stat-input" type="text" name="attr_spell_level"></div>
|
||||||
<div class="grid-span-4-2"><input class="sheet-stat-input" type="text" name="attr_spell_effect"></div>
|
<div class="grid-span-4-2"><textarea class="sheet-stat-input sheet-txtarea" name="attr_spell_effect" ></textarea></div>
|
||||||
<div><input class="sheet-stat-input" type="text" name="attr_spell_damage"></div>
|
<div><input class="sheet-stat-input" type="text" name="attr_spell_range"></div>
|
||||||
<div><button class="sheet-roll-button" type="roll" value="&{template:acks} {{name=@{spell_name}}} {{subheader=Level @{spell_level} Arcane Spell}} {{desc=@{spell_effect}}} {{damage=[[@{spell_damage}]]}}" name="roll_Spell"></button></div>
|
<div><input class="sheet-stat-input" type="text" name="attr_spell_roll"></div>
|
||||||
|
<div><button class="sheet-roll-button" type="roll" value="&{template:acks} {{name=@{spell_name}}} {{subheader=Level @{spell_level} @{spell_school} Spell}} {{desc=@{spell_effect}}} {{roll=[[@{spell_roll}]]}} {{range=@{spell_range}}}" name="roll_Spell"></button></div>
|
||||||
|
<div class="sheet-hidden"><input type="text" name="attr_spell_school"></div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
@ -440,13 +444,16 @@
|
|||||||
<div class="sheet-acks-subheader sheet-acks-row">{{subheader}}</div>
|
<div class="sheet-acks-subheader sheet-acks-row">{{subheader}}</div>
|
||||||
{{/subheader}}
|
{{/subheader}}
|
||||||
{{#target}}
|
{{#target}}
|
||||||
<div class="sheet-acks-row">Target <span class="sheet-acks-target-value">{{target}}+</span></div>
|
<div class="sheet-acks-row"><strong>Target</strong> <span class="sheet-acks-target-value">{{target}}+</span></div>
|
||||||
{{/target}}
|
{{/target}}
|
||||||
|
{{#range}}
|
||||||
|
<div class="sheet-acks-row"><strong>Range</strong> <span class="sheet-acks-target-value">{{range}}</span></div>
|
||||||
|
{{/range}}
|
||||||
{{#roll}}
|
{{#roll}}
|
||||||
<div class="sheet-acks-row">Roll {{roll}}</div>
|
<div class="sheet-acks-row"><strong>Roll</strong> {{roll}}</div>
|
||||||
{{/roll}}
|
{{/roll}}
|
||||||
{{#damage}}
|
{{#damage}}
|
||||||
<div class="sheet-acks-row">Damage {{damage}}</div>
|
<div class="sheet-acks-row"><strong>Damage</strong> {{damage}}</div>
|
||||||
{{/damage}}
|
{{/damage}}
|
||||||
{{#desc}}
|
{{#desc}}
|
||||||
<div class="sheet-acks-row sheet-acks-desc">{{desc}}</div>
|
<div class="sheet-acks-row sheet-acks-desc">{{desc}}</div>
|
||||||
|
117
roll20/hit_dice.js
Normal file
117
roll20/hit_dice.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
var HitDice = HitDice || (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var tokenIds = [],
|
||||||
|
configure = function() {
|
||||||
|
if(!state.HitDice) {
|
||||||
|
state.HitDice = {
|
||||||
|
version: 0.1,
|
||||||
|
config: {
|
||||||
|
bar: 3,
|
||||||
|
hitDiceAttribute: 'npc_hitdice',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleInput = function(msg) {
|
||||||
|
if (msg.type === "api" && /^!mhd(\b|$)/i.test(msg.content) && playerIsGM(msg.playerid) ) {
|
||||||
|
let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
|
||||||
|
let count = 0;
|
||||||
|
// WUSSALLTHISTHEN
|
||||||
|
(msg.selected || [])
|
||||||
|
.map(o=>getObj('graphic',o._id))
|
||||||
|
.filter(g=>undefined !== g)
|
||||||
|
.forEach( o => {
|
||||||
|
++count;
|
||||||
|
tokenIds.push(o.id);
|
||||||
|
rollHitDice(o);
|
||||||
|
})
|
||||||
|
;
|
||||||
|
sendChat('',`/w "${who}" Rolling hit dice for ${count} token(s).`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
findRoll = function(txt){
|
||||||
|
return txt.match(/\d+d\d+(\+\d+)?/)[0] || 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
rollHitDice = function(obj) {
|
||||||
|
var sets = {},
|
||||||
|
bar = 'bar'+state.HitDice.config.bar,
|
||||||
|
hdAttrib,
|
||||||
|
hdExpression = 0,
|
||||||
|
bonus = 0
|
||||||
|
;
|
||||||
|
|
||||||
|
if(_.contains(tokenIds,obj.id)){
|
||||||
|
tokenIds=_.without(tokenIds,obj.id);
|
||||||
|
|
||||||
|
if('graphic' === obj.get('type') &&
|
||||||
|
'token' === obj.get('subtype') &&
|
||||||
|
'' !== obj.get('represents')
|
||||||
|
) {
|
||||||
|
if( obj && '' === obj.get(bar+'_link') ) {
|
||||||
|
hdAttrib = findObjs({
|
||||||
|
type: 'attribute',
|
||||||
|
characterid: obj.get('represents'),
|
||||||
|
name: state.HitDice.config.hitDiceAttribute
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
if( hdAttrib ) {
|
||||||
|
//sendChat('', 'HERE WE ARE');
|
||||||
|
//log(hdAttrib);
|
||||||
|
|
||||||
|
hdExpression = findRoll(hdAttrib.get('current'));
|
||||||
|
sendChat('','/r '+hdExpression+'+'+bonus,function(r){
|
||||||
|
var hp=0;
|
||||||
|
_.each(r,function(subr){
|
||||||
|
var val=JSON.parse(subr.content);
|
||||||
|
if(_.has(val,'total'))
|
||||||
|
{
|
||||||
|
hp+=val.total;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sets[bar+"_value"] = hp||1;
|
||||||
|
sets[bar+"_max"] = hp||1;
|
||||||
|
obj.set(sets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
saveTokenId = function(obj){
|
||||||
|
tokenIds.push(obj.id);
|
||||||
|
|
||||||
|
setTimeout((function(id){
|
||||||
|
return function(){
|
||||||
|
var token=getObj('graphic',id);
|
||||||
|
if(token){
|
||||||
|
rollHitDice(token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}(obj.id)),100);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
registerEventHandlers = function() {
|
||||||
|
on('chat:message', handleInput);
|
||||||
|
on('add:graphic', saveTokenId);
|
||||||
|
on('change:graphic', rollHitDice);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
configure: configure,
|
||||||
|
RegisterEventHandlers: registerEventHandlers
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
||||||
|
|
||||||
|
on('ready',function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
HitDice.configure();
|
||||||
|
HitDice.RegisterEventHandlers();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
@ -107,6 +107,25 @@ on("chat:message", function(msg) {
|
|||||||
addAttr(sheet.id, melee_name + "_attack_name", c.melee.name, null);
|
addAttr(sheet.id, melee_name + "_attack_name", c.melee.name, null);
|
||||||
addAttr(sheet.id, melee_name + "_attack_throw_mod", c.melee.throw_mod, null);
|
addAttr(sheet.id, melee_name + "_attack_throw_mod", c.melee.throw_mod, null);
|
||||||
addAttr(sheet.id, melee_name + "_attack_dmg", c.melee.damage, null);
|
addAttr(sheet.id, melee_name + "_attack_dmg", c.melee.damage, null);
|
||||||
|
|
||||||
|
if(c.spells) {
|
||||||
|
for(spell of c.spells) {
|
||||||
|
let spell_name = "repeating_spells_" + generateRowID();
|
||||||
|
addAttr(sheet.id, spell_name + "_spell_name", spell.name, null);
|
||||||
|
addAttr(sheet.id, spell_name + "_spell_effect", Base64.decode(spell.description), null);
|
||||||
|
addAttr(sheet.id, spell_name + "_spell_range", spell.range, null);
|
||||||
|
addAttr(sheet.id, spell_name + "_spell_roll", spell.roll || 0, null);
|
||||||
|
if(spell.is_arcane) {
|
||||||
|
addAttr(sheet.id, spell_name + "_spell_school", "Arcane", null);
|
||||||
|
addAttr(sheet.id, spell_name + "_spell_level", spell.arcane, null);
|
||||||
|
}
|
||||||
|
if(spell.is_divine) {
|
||||||
|
addAttr(sheet.id, spell_name + "_spell_school", "Divine", null);
|
||||||
|
addAttr(sheet.id, spell_name + "_spell_level", spell.divine, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user