5 Commits
a683cd9007
...
d00d53f304
Author | SHA1 | Message | Date |
---|---|---|---|
Brandon Cornejo | d00d53f304 |
Treasure generator, new heading style for all pages
|
4 years ago |
Brandon Cornejo | 9017792b3c |
Single NPC gen, spell list, world map, roll20-hitdice
|
4 years ago |
Brandon Cornejo | 79f9260d13 |
Finalize spell imports
|
4 years ago |
Brandon Cornejo | 5c02d8f9fa |
Modify Roll20 sheet for spell text area
|
4 years ago |
Brandon Cornejo | b3c8ae7328 |
NPC spell generation, front end
|
4 years ago |
19 changed files with 980 additions and 35 deletions
-
1acks/npc/commands.py
-
15acks/npc/models.py
-
22acks/npc/npc_party.py
-
13acks/npc/templates/generate_npc_party.html
-
355acks/npc/templates/generate_single_npc.html
-
92acks/npc/templates/spell_list.html
-
24acks/npc/views.py
-
BINacks/static/WorldMap.png
-
50acks/templates/base.html
-
8acks/templates/handbook.html
-
8acks/templates/index.html
-
68acks/templates/treasure.html
-
23acks/templates/worldmap.html
-
20acks/views.py
-
144roll20/base64.js
-
13roll20/char_sheet.css
-
23roll20/char_sheet.html
-
117roll20/hit_dice.js
-
19roll20/npc_party_import.js
@ -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 %} |
After Width: 2560 | Height: 1440 | Size: 4.6 MiB |
@ -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 %} |
@ -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 %} |
@ -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 |
|||
}; |
|||
|
|||
})(); |
@ -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(); |
|||
}); |
|||
|
|||
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue