You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
14 KiB

  1. {% extends "base.html" %}
  2. {% set active_page = "npcparty" %}
  3. {% block title %}NPC Party Generation{% endblock %}
  4. {% block content %}
  5. <div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
  6. <h1 class="uk-text-center"><strong>Legends of Palisma</strong>NPC Party Generator</h1>
  7. </div>
  8. <div class="uk-flex uk-flex-bottom uk-flex-center uk-margin-large-bottom">
  9. <div>
  10. <label for="base_level">Base level of party to generate: </label>
  11. <input type="number" name="base_level" id="base_level" class="uk-input" value="{{ base_level if base_level else 1 }}" min="0" max="14">
  12. </div>
  13. <div class="uk-margin-left">
  14. <button class="uk-button uk-button-primary" onclick="generateParty();">Generate</button>
  15. </div>
  16. </div>
  17. <hr class="uk-divider-icon">
  18. {% if party %}
  19. <div class="uk-flex uk-flex-between uk-margin-large-top">
  20. <h3 class="uk-display-inline-block">NPC Party of Size {{ party | length }} </h3>
  21. <div class="uk-text-right uk-display-inline-block">
  22. <a class="uk-button uk-button-small uk-button-secondary uk-border-rounded" onclick="showExportModal();">Roll20 Export</a>
  23. </div>
  24. </div>
  25. <div class="uk-grid-medium uk-grid-match uk-flex-center" uk-grid>
  26. {% for npc in party %}
  27. <div class="acks-npc-card">
  28. <div class="uk-card uk-card-body uk-card-default uk-box-shadow-hover-large">
  29. <h4 class="uk-card-title uk-margin-small-top">{{ npc.name }}</h4>
  30. <div class="uk-card-badge uk-label uk-label-primary">{{ npc.guild.name }}</div>
  31. <div class="uk-flex uk-flex-around uk-text-center uk-margin-bottom">
  32. <div>
  33. <div class="uk-text-bold">Level</div>
  34. <div>{{ npc.level }}</div>
  35. </div>
  36. <div>
  37. <div class="uk-text-bold">HP</div>
  38. <div>{{ npc.hp }}</div>
  39. </div>
  40. <div>
  41. <div class="uk-text-bold">AC</div>
  42. <div>{{ npc.armour.ac_mod }}</div>
  43. </div>
  44. </div>
  45. <div class="uk-flex uk-flex-around stat-block">
  46. <div>
  47. <div title="{{ npc.str_mod }}">{{ npc.str }}</div>
  48. <div title="{{ npc.str_mod }}">Str</div>
  49. </div>
  50. <div>
  51. <div title="{{ npc.int_mod }}">{{ npc.int }}</div>
  52. <div title="{{ npc.int_mod }}">Int</div>
  53. </div>
  54. <div>
  55. <div title="{{ npc.wis_mod }}">{{ npc.wis }}</div>
  56. <div title="{{ npc.wis_mod }}">Wis</div>
  57. </div>
  58. <div>
  59. <div title="{{ npc.dex_mod }}">{{ npc.dex }}</div>
  60. <div title="{{ npc.dex_mod }}">Dex</div>
  61. </div>
  62. <div>
  63. <div title="{{ npc.con_mod }}">{{ npc.con }}</div>
  64. <div title="{{ npc.con_mod }}">Con</div>
  65. </div>
  66. <div>
  67. <div title="{{ npc.chr_mod }}">{{ npc.chr }}</div>
  68. <div title="{{ npc.chr_mod }}">Chr</div>
  69. </div>
  70. </div>
  71. <hr class="uk-divider-small uk-text-center">
  72. <div class="uk-flex uk-flex-around uk-text-center uk-margin-bottom save-block uk-margin-top">
  73. <div>
  74. <div title="Petrification & Paralysis">{{ npc.save_pp }}</div>
  75. <div title="Petrification & Paralysis">P &amp; P</div>
  76. </div>
  77. <div>
  78. <div title="Poison & Death">{{ npc.save_pd }}</div>
  79. <div title="Poison & Death">P &amp; D</div>
  80. </div>
  81. <div>
  82. <div title="Blast & Breath">{{ npc.save_bb }}</div>
  83. <div title="Blast & Breath">B &amp; B</div>
  84. </div>
  85. <div>
  86. <div title="Staffs & Wands">{{ npc.save_sw }}</div>
  87. <div title="Staffs & Wands">S &amp; W</div>
  88. </div>
  89. <div>
  90. <div title="Spells">{{ npc.save_sp }}</div>
  91. <div title="Spells">Spells</div>
  92. </div>
  93. </div>
  94. <ul uk-tab>
  95. <li class="uk-active"><a href="">Equipment</a></li>
  96. <li {% if not npc.is_divine_spellcaster and not npc.is_arcane_spellcaster %}class="uk-disabled"{% endif %}><a href="">Spells</a></li>
  97. </ul>
  98. <ul class="uk-switcher uk-margin">
  99. <li>
  100. <table class="uk-table uk-table-hover uk-table-small item-table">
  101. <thead>
  102. <tr> <th>Name</th><th>Worth</th><th>Thr</th><th>Dmg</th> </tr>
  103. </thead>
  104. <tbody>
  105. <tr>
  106. <td>{{ npc.melee.name }}</td>
  107. <td>{{ npc.melee.gp_value }}gp</td>
  108. <td>{{ npc.attack_throw }}</td>
  109. <td>{{ npc.melee.damage_die }}</td>
  110. </tr>
  111. {% if npc.ranged %}
  112. <tr>
  113. <td>{{ npc.ranged.name }}</td>
  114. <td>{{ npc.ranged.gp_value }}gp</td>
  115. <td>{{ npc.attack_throw }}</td>
  116. <td>{{ npc.ranged.damage_die }}</td>
  117. </tr>
  118. {% endif %}
  119. <tr>
  120. <td>{{ npc.armour.name }}</td>
  121. <td>{{ npc.armour.gp_value }}gp</td>
  122. <td></td>
  123. <td></td>
  124. </tr>
  125. </tbody>
  126. </table>
  127. </li>
  128. <li>
  129. <table class="uk-table uk-table-hover uk-table-small spell-table">
  130. <thead>
  131. <tr> <th>Name</th><th>Range</th><th>Duration</th><th>Level</th> </tr>
  132. </thead>
  133. <tbody>
  134. {% for level in npc.spell_list() %}
  135. {% for spell in npc.spell_list()[level] %}
  136. <tr>
  137. <td data-spell-id="{{ spell.id }}" onclick="showSpellModal();">{{ spell.name }}</td>
  138. <td>{{ spell.range }}</td>
  139. <td>{{ spell.duration }}</td>
  140. <td>{{ spell.level_for(npc) }}</td>
  141. </tr>
  142. {% endfor %}
  143. {% endfor %}
  144. </tbody>
  145. </table>
  146. </li>
  147. </ul>
  148. </div>
  149. </div>
  150. {% endfor %}
  151. </div>
  152. {% else %}
  153. <div class="uk-grid-medium uk-grid-match uk-flex-center" uk-grid>
  154. </div>
  155. {% endif %}
  156. <div id="spell-modal" uk-modal>
  157. <div class="uk-modal-dialog uk-margin-auto-vertical">
  158. <button class="uk-modal-close-default" type="button" uk-close></button>
  159. <div class="uk-modal-header">
  160. <h2 class="uk-modal-title"><span id="spell-modal-title">{Spell Title}</h2>
  161. </div>
  162. <div class="uk-modal-body">
  163. <ul>
  164. <li>Range: <span id="spell-modal-range">{Spell Range}</span></li>
  165. <li>Duration: <span id="spell-modal-duration">{Spell Duration}</span></li>
  166. <li><span id="spell-modal-school">{Spell School}</span></li>
  167. </ul>
  168. <p id="spell-modal-desc">{Spell Description}</p>
  169. </div>
  170. </div>
  171. </div>
  172. <div id="party-export-modal" uk-modal>
  173. <div class="uk-modal-dialog uk-margin-auto-vertical">
  174. <button class="uk-modal-close-default" type="button" uk-close></button>
  175. <div class="uk-modal-header">
  176. <h2 class="uk-modal-title">Roll20 Export</h2>
  177. </div>
  178. <div class="uk-modal-body">
  179. <div class="uk-margin">
  180. <h5>Characters to Export:</h5>
  181. <div id="pe_selects" class="uk-grid-small uk-grid uk-child-width-auto">
  182. {% if party %}
  183. {% for npc in party %}
  184. <label><input class="uk-checkbox" type="checkbox" name="ch{{ loop.index }}" onchange="prepareSelectiveExport();" checked> {{ npc.name }}</label>
  185. {% endfor %}
  186. {% endif %}
  187. </div>
  188. <div class="uk-margin uk-align-right">
  189. <div class="uk-button-group">
  190. <button id="pe_none" class="uk-button uk-button-small uk-button-default" onclick="partyExportToggle();">None</button>
  191. <button id="pe_all" class="uk-button uk-button-small uk-button-secondary" onclick="partyExportToggle();">All</button>
  192. </div>
  193. </div>
  194. </div>
  195. <textarea id="party-export-show" class="uk-textarea uk-text-small" rows="10"></textarea>
  196. <textarea id="party-export-data" class="uk-textarea uk-hidden"></textarea>
  197. </div>
  198. <div class="uk-modal-footer uk-text-right">
  199. <button class="uk-button uk-button-default uk-modal-close" type="button">Cancel</button>
  200. <button class="uk-button uk-button-primary" type="button" onclick="exportParty();">Copy</button>
  201. </div>
  202. </div>
  203. </div>
  204. <br>
  205. <style>
  206. table.item-table, table.item-table th, table.spell-table, table.spell-table th {
  207. font-size: 12px;
  208. }
  209. div.stat-block > div {
  210. text-align: center;
  211. padding: 3px;
  212. width: 26px;
  213. }
  214. div.stat-block > div > div:first-child {
  215. font-weight: bold;
  216. color: green;
  217. }
  218. div.stat-block > div > div:last-child {
  219. font-weight: bold;
  220. font-size: 12px;
  221. color: #888;
  222. }
  223. div.save-block > div > div:first-child {
  224. font-weight: bold;
  225. color: purple;
  226. }
  227. div.save-block > div > div:last-child {
  228. font-weight: bold;
  229. font-size: 12px;
  230. color: #888;
  231. }
  232. div.acks-npc-card {
  233. width: 400px;
  234. }
  235. h1 strong {
  236. display: block;
  237. font-size: 50%;
  238. opacity: 0.65;
  239. }
  240. </style>
  241. <script type="text/javascript">
  242. var party = [];
  243. function generateParty() {
  244. let bl = document.querySelector('#base_level').value;
  245. if(bl < 0) { bl = 0; }
  246. if(bl > 14) { bl = 14; }
  247. window.location = "/npc/party/" + bl.toString();
  248. }
  249. function showSpellModal() {
  250. spells = new Set();
  251. {% if party %}
  252. {% for npc in party %}
  253. {% for level in npc.spell_list() %}
  254. {% for spell in npc.spell_list()[level] %}
  255. spells.add({{ spell.roll20_format | tojson }});
  256. {% endfor %}
  257. {% endfor %}
  258. {% endfor %}
  259. {% endif %}
  260. var modal_spell, spell_id = event.target.dataset.spellId;
  261. for(var spell of spells) {
  262. if(spell.id == spell_id) {
  263. modal_spell = spell
  264. }
  265. }
  266. // Fill in spell details
  267. document.querySelector('#spell-modal-title').innerText = modal_spell.name;
  268. document.querySelector('#spell-modal-desc').innerText = atob(modal_spell.description);
  269. if(spell.range) {
  270. document.querySelector('#spell-modal-range').innerText = modal_spell.range;
  271. }
  272. if(spell.duration) {
  273. document.querySelector('#spell-modal-duration').innerText = modal_spell.duration;
  274. }
  275. var school = '';
  276. if(modal_spell.is_arcane) {
  277. school += 'Arcane ' + modal_spell.arcane + ' ';
  278. }
  279. if(modal_spell.is_divine) {
  280. school += 'Divine ' + modal_spell.divine + ' ';
  281. }
  282. document.querySelector('#spell-modal-school').innerText = school;
  283. UIkit.modal(document.querySelector('#spell-modal')).show();
  284. }
  285. function showExportModal() {
  286. {% if party %}
  287. {% for npc in party %}
  288. party.push(JSON.parse('{{ npc.roll20_format | tojson }}'));
  289. {% endfor %}
  290. {% endif %}
  291. for(let cb of document.querySelectorAll('#pe_selects > label > input')) {
  292. cb.checked = true;
  293. }
  294. prepareSelectiveExport();
  295. UIkit.modal(document.querySelector('#party-export-modal')).show();
  296. }
  297. function prepareSelectiveExport() {
  298. var selected_party = [];
  299. var checkboxes = document.querySelectorAll('#pe_selects > label > input');
  300. for(let [i, cb] of checkboxes.entries()) {
  301. if(cb.checked) {
  302. selected_party.push(party[i]);
  303. }
  304. }
  305. document.querySelector('#party-export-data').value = "!acksimport " + JSON.stringify(selected_party);
  306. document.querySelector('#party-export-show').value = JSON.stringify(selected_party, undefined, 2);
  307. }
  308. function exportParty() {
  309. let party_data = document.querySelector('#party-export-data')
  310. party_data.classList.remove('uk-hidden');
  311. party_data.select();
  312. document.execCommand("copy");
  313. party_data.classList.add('uk-hidden');
  314. UIkit.modal(document.querySelector('#party-export-modal')).hide();
  315. }
  316. function partyExportToggle() {
  317. var checkboxes = document.querySelectorAll('#pe_selects > label > input');
  318. for(let cb of checkboxes) {
  319. if(event.target.id === "pe_none") {
  320. cb.checked = false;
  321. }
  322. if(event.target.id === "pe_all") {
  323. cb.checked = true;
  324. }
  325. }
  326. prepareSelectiveExport();
  327. }
  328. </script>
  329. {% endblock %}