index.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. <html>
  2. <head>
  3. <style type="text/css">
  4. html, body {
  5. background-color: #886028;
  6. font-family: 'Sans Serif';
  7. }
  8. button#go, button#finalize {
  9. font-size: 1.3em;
  10. padding:5px;
  11. margin-right: 60px;
  12. }
  13. .left {
  14. float: left;
  15. }
  16. .option-list > div {
  17. margin-right: 40px;
  18. }
  19. .option-list::after {
  20. clear: both;
  21. content: "";
  22. display: table;
  23. }
  24. label, select {
  25. display: block;
  26. padding: 5 0px;
  27. }
  28. label > span {
  29. min-width: 140px;
  30. display: inline-block;
  31. font-weight: bold;
  32. }
  33. label > input {
  34. width: 70px;
  35. display: inline-block;
  36. text-align: right;
  37. padding: 3px;
  38. }
  39. .cost {
  40. text-align: right;
  41. }
  42. .avail{
  43. text-align: center;
  44. }
  45. .reroll {
  46. text-align: center;
  47. }
  48. .menu {
  49. }
  50. .item {
  51. padding: 5px;
  52. }
  53. .item-table {
  54. margin-top: 20px;
  55. border-collapse: collapse;
  56. }
  57. .item-table th {
  58. padding: 3px 10px;
  59. border: 1px solid black;
  60. }
  61. .item-table td {
  62. padding: 3px 10px;
  63. }
  64. .item-table tbody tr:nth-child(odd) {
  65. background-color: #997039;
  66. }
  67. </style>
  68. <script type="text/javascript">var module = {};</script>
  69. <script src="itemList.js"></script>
  70. <script type="text/javascript">
  71. /*
  72. art, gems, statues, etc.
  73. "mundane"
  74. attachment, ie. finger, shoulder, hat, etc.
  75. scrolls
  76. */
  77. /*
  78. for(var m of module.exports.minor_item_list) {
  79. if(!module.exports.items[m]) console.log('missing:', m);
  80. }
  81. */
  82. var prng_seed = Math.floor(Math.random() * 10000 + 10000);
  83. var wh = window.location.hash;
  84. var hash_opts = [];
  85. if(wh) {
  86. var params = wh.split('_');
  87. prng_seed = params[0];
  88. hash_opts = params[1].split('!');
  89. }
  90. function frand() {
  91. var x = Math.sin(prng_seed++) * 31337;
  92. return x - Math.floor(x);
  93. }
  94. function clone(obj) {
  95. var o = {};
  96. for(var k in obj) {
  97. var v = obj[k];
  98. if(typeof v == 'object') {
  99. if(v instanceof Array) o = [];
  100. o[k] = clone(v);
  101. }
  102. else {
  103. o[k] = v;
  104. }
  105. }
  106. return o;
  107. }
  108. var magic_ammo_rarity = {
  109. 1: 'uncommon',
  110. 2: 'rare',
  111. 3: 'very rare',
  112. }
  113. var magic_weapon_rarity = {
  114. 1: 'uncommon',
  115. 2: 'rare',
  116. 3: 'very rare',
  117. }
  118. var magic_armor_rarity = {
  119. 1: 'rare',
  120. 2: 'very rare',
  121. 3: 'legendary',
  122. }
  123. var armor_of_resistance_types = [
  124. "Acid",
  125. "Cold",
  126. "Fire",
  127. "Force",
  128. "Lightning",
  129. "Necrotic",
  130. "Poison",
  131. "Psychic",
  132. "Radiant",
  133. "Thunder",
  134. ];
  135. var scroll_level_data = {
  136. 0: {name: 'Cantrip', rarity: 'common', save_dc: 13, attack_bonus: 5},
  137. 1: {name: '1st', rarity: 'common', save_dc: 13, attack_bonus: 5},
  138. 2: {name: '2nd', rarity: 'uncommon', save_dc: 13, attack_bonus: 5},
  139. 3: {name: '3rd', rarity: 'uncommon', save_dc: 15, attack_bonus: 7},
  140. 4: {name: '4th', rarity: 'rare', save_dc: 15, attack_bonus: 7},
  141. 5: {name: '5th', rarity: 'rare', save_dc: 17, attack_bonus: 9},
  142. 6: {name: '6th', rarity: 'very rare', save_dc: 17, attack_bonus: 9},
  143. 7: {name: '7th', rarity: 'very rare', save_dc: 18, attack_bonus: 10},
  144. 8: {name: '8th', rarity: 'very rare', save_dc: 18, attack_bonus: 10},
  145. 9: {name: '9th', rarity: 'legendary', save_dc: 19, attack_bonus: 11},
  146. };
  147. // make a list of 'any' ammos
  148. var any_ammo_list = [];
  149. for(var owi in module.exports.items) {
  150. var ow = module.exports.items[owi];
  151. if(ow.type == 'ammunition' && ow.magic_item && ow.any) {
  152. any_ammo_list.push(ow);
  153. delete module.exports.items[owi];
  154. }
  155. }
  156. // fill in all the ammo
  157. for(var owi in module.exports.items) {
  158. var ow = module.exports.items[owi];
  159. if(!(ow.type == 'ammunition' && ow.rarity == 'common' && !ow.magic_item)) continue;
  160. for(var b = 1; b <= 3; b++) {
  161. var nw = clone(ow);
  162. nw.name += ', +' + b;
  163. nw.id = owi + ', +' + b;
  164. nw.magic_item = true;
  165. nw.rarity = magic_ammo_rarity[b];
  166. nw.attack_bonus = b;
  167. nw.damage_bonus = b;
  168. module.exports.items[nw.id] = nw;
  169. module.exports.minor_item_list.push(nw.id);
  170. }
  171. // "any" ammos
  172. for(var aw of any_ammo_list) {
  173. var good = 0;
  174. if(aw.any == 'ammunition') good = 1;
  175. if(good == 1) {
  176. var nw = clone(aw);
  177. nw.id = aw.id+ ', ' + ow.id;
  178. nw.name = aw.name + ', ' + ow.name;
  179. module.exports.items[nw.id] = nw;
  180. module.exports.minor_item_list.push(nw.id);
  181. }
  182. }
  183. }
  184. // make a list of 'any' weapons
  185. var any_weapon_list = [];
  186. for(var owi in module.exports.items) {
  187. var ow = module.exports.items[owi];
  188. if(ow.type == 'weapon' && ow.magic_item && ow.any) {
  189. any_weapon_list.push(ow);
  190. delete module.exports.items[owi];
  191. }
  192. }
  193. // fill in all the weapons
  194. for(var owi in module.exports.items) {
  195. var ow = module.exports.items[owi];
  196. if(!(ow.type == 'weapon' && ow.rarity == 'mundane' && !ow.magic_item)) continue;
  197. // normal +1/2/3 magic weapons
  198. for(var b = 1; b <= 3; b++) {
  199. var nw = clone(ow);
  200. nw.name += ', +' + b;
  201. nw.id = owi + ', +' + b;
  202. nw.magic_item = true;
  203. nw.rarity = magic_weapon_rarity[b];
  204. nw.attack_bonus = b;
  205. nw.damage_bonus = b;
  206. module.exports.items[nw.id] = nw;
  207. }
  208. // "any" weapons
  209. for(var aw of any_weapon_list) {
  210. var good = 0;
  211. if(aw.any instanceof Array) {
  212. for(var aa of aw.any) {
  213. if(aw.any == 'weapon') good = 1;
  214. else if(aw.any == ow.weapon_style) good = 1;
  215. else if(aw.any == 'sword, slashing' && ow.weapon_style == 'sword') {
  216. if(ow.damage.indexOf('slashing') >= 0) good = 1;
  217. }
  218. else continue;
  219. break;
  220. }
  221. }
  222. else if(aw.any == 'weapon') good = 1;
  223. else if(aw.any == ow.weapon_style) good = 1;
  224. else if(aw.any == 'sword, slashing' && ow.weapon_style == 'sword') {
  225. if(ow.damage.indexOf('slashing') >= 0) good = 1;
  226. }
  227. if(good == 1) {
  228. var nw = clone(aw);
  229. nw.id = aw.id+ ', ' + ow.id;
  230. nw.name = aw.name + ', ' + ow.name;
  231. module.exports.items[nw.id] = nw;
  232. }
  233. }
  234. }
  235. // fill in all the armor
  236. for(var oai in module.exports.items) {
  237. var oa = module.exports.items[oai];
  238. if(!(oa.type == 'armor' && oa.rarity == 'mundane' && !oa.magic_item)) continue;
  239. for(var b = 1; b <= 3; b++) {
  240. var na = clone(oa);
  241. na.name += ', +' + b;
  242. na.id = oai + ', +' + b;
  243. na.magic_item = true;
  244. na.rarity = magic_armor_rarity[b];
  245. na.ac_bonus = b;
  246. module.exports.items[na.id] = na;
  247. // armor of resistance
  248. for(var dtype of armor_of_resistance_types) {
  249. var na = clone(oa);
  250. na.name += ' of Resistance, ' + dtype;
  251. na.id = oai + ' of resistance, ' + dtype.toLowerCase();
  252. na.magic_item = true;
  253. na.rarity = 'rare';
  254. na.ac_bonus = b;
  255. module.exports.items[na.id] = na;
  256. }
  257. }
  258. if((oa.subtype == 'medium' || oa.subtype == 'heavy') && oa.id != 'hide') {
  259. // mithral
  260. var na = clone(oa);
  261. na.name = 'Mithral ' + na.name;
  262. na.id += 'mithral ' + oai;
  263. na.magic_item = true;
  264. na.rarity = 'uncommon';
  265. na.mithral = true;
  266. na.min_str = '-';
  267. delete na.stealth;
  268. module.exports.items[na.id] = na;
  269. module.exports.minor_item_list.push(na.id);
  270. // adamantium
  271. var na = clone(oa);
  272. na.name = 'Adamantine ' + na.name;
  273. na.id += 'adamantine ' + oai;
  274. na.magic_item = true;
  275. na.rarity = 'uncommon';
  276. na.adamantium = true;
  277. module.exports.items[na.id] = na;
  278. }
  279. }
  280. // fill in the scrolls
  281. for(var spi in module.exports.spell_list) {
  282. var sp = module.exports.spell_list[spi];
  283. var ld = scroll_level_data[sp.level];
  284. var sc = {
  285. id: "scroll of " + sp.id,
  286. name: "Scroll of " + sp.name,
  287. type: "Scroll",
  288. level: sp.level,
  289. school: sp.school,
  290. magic_item: true,
  291. rarity: ld.rarity,
  292. attack_bonus: ld.attack_bonus,
  293. save_dc: ld.save_dc,
  294. consumable: true,
  295. };
  296. module.exports.items[sc.id] = sc;
  297. module.exports.minor_item_list.push(sc.id);
  298. }
  299. function $all(sel, n) {
  300. return (n || document).querySelectorAll(sel);
  301. }
  302. function $(sel, n) {
  303. return (n || document).querySelector(sel);
  304. }
  305. function $ce(tag, classes, content) {
  306. var n = document.createElement(tag);
  307. if(classes) {
  308. if(typeof classes == 'string') n.className = classes;
  309. else if(classes instanceof Array) n.classList = classes;
  310. }
  311. if(content) {
  312. if(typeof content == 'string') n.innerHTML = content;
  313. else if(typeof content == 'number') n.innerHTML = content + '';
  314. else n.appendChild(content);
  315. }
  316. return n;
  317. }
  318. function roll(n, d) {
  319. var s = 0;
  320. for(var i = 0; i < n; i++) s += Math.floor((frand() * d)) + 1;
  321. return s;
  322. }
  323. function makeControl(e) {
  324. var l = $ce('label');
  325. var c = $ce('input');
  326. c.type = e.getAttribute('type');
  327. c.value = e.innerHTML;
  328. c.id = e.getAttribute('cid');
  329. if(c.type == "checkbox") {
  330. if(e.innerHTML == "1") {
  331. c.checked = true;
  332. }
  333. l.appendChild(c);
  334. l.appendChild($ce('span', '', e.getAttribute('label')));
  335. }
  336. else {
  337. l.appendChild($ce('span', '', e.getAttribute('label')));
  338. l.appendChild(c);
  339. }
  340. e.parentNode.insertBefore(l, e);
  341. e.parentNode.removeChild(e);
  342. }
  343. function is_minor(x) {
  344. return module.exports.minor_item_list.indexOf(x) >= 0;
  345. }
  346. function getPrice(item, q) {
  347. var x;
  348. switch(item.rarity) {
  349. case 'mundane': x = q + 1; break;
  350. case 'common': x = q * 10; break;
  351. case 'uncommon': x = q * 100; break;
  352. case 'rare': x = q * 1000; break;
  353. case 'very rare': x = q * 10000; break;
  354. case 'legendary': x = (roll(1, 6) + q) * 25000; break;
  355. case 'artifact': x = (roll(2, 6) + q) * 250000; break;
  356. };
  357. if(item.consumable == true) x = Math.max(1, Math.floor((x / 2) + 0.5));
  358. return x;
  359. }
  360. function nfmt(s) {
  361. return String(s).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  362. }
  363. function filterType(type, list, special) {
  364. for(var ind in list) {
  365. var i = list[ind];
  366. if(i.type == type) {
  367. special[type].push(i);
  368. list.splice(ind, 1);
  369. }
  370. }
  371. }
  372. function generateList(opts) {
  373. var cont = $('.item-list');
  374. cont.innerHTML = '';
  375. function qroll() {
  376. return roll(opts.qdice[0], opts.qdice[1]);
  377. }
  378. function filterItems(rarity, minor) {
  379. var o = [];
  380. for(var k in module.exports.items) {
  381. var i = module.exports.items[k];
  382. if(opts.exclude_cursed && i.cursed) continue;
  383. if(opts.include.PH == false && i.src == 'PH') continue;
  384. if(opts.include.DM == false && i.src == 'DM') continue;
  385. if(opts.include.XG == false && i.src == 'XG') continue;
  386. if(opts.include.EE == false && i.src == 'EE') continue;
  387. if(
  388. i.rarity == rarity
  389. && (is_minor(i.id) == minor)
  390. && i.magic_item
  391. ) {
  392. if(opts.exclude_pocket && i.pocket_dimension) continue;
  393. o.push(i);
  394. }
  395. }
  396. return o;
  397. }
  398. function addItem(item, list) {
  399. var c = $ce('tr', 'item');
  400. var qty = qroll();
  401. var price = 100 * opts.pmul;
  402. var n_ = $ce('td', 'name', item.name);
  403. var r_ = $ce('td', 'rarity', item.rarity);
  404. var q_ = $ce('td', 'avail', qroll());
  405. var p_ = $ce('td', 'cost', nfmt(getPrice(item, qty)));
  406. var rm = $ce('button', '', 'X');
  407. rm.addEventListener('click', function(e) {
  408. if(list.length == 0) {
  409. c.remove();
  410. return;
  411. };
  412. var ind = Math.floor(frand() * list.length);
  413. var it = list[ind];
  414. var q = qroll();
  415. n_.innerHTML = it.name;
  416. r_.innerHTML = it.rarity;
  417. q_.innerHTML = q;
  418. p_.innerHTML = nfmt(getPrice(it, q));
  419. list.splice(ind, 1);
  420. });
  421. c.appendChild(n_);
  422. c.appendChild(r_);
  423. c.appendChild(q_);
  424. c.appendChild(p_);
  425. c.appendChild(rm);
  426. c.appendChild($ce('td', 'reroll', rm))
  427. cont.appendChild(c);
  428. }
  429. var item_res = {};
  430. for(var sz in module.exports.standard_awards) {
  431. var z = module.exports.standard_awards[sz];
  432. for(var r in z) {
  433. var k = sz + '_' + r;
  434. var list = filterItems(r, sz == 'minor');
  435. item_res[k] = list;
  436. var amt = Math.floor((z[r][opts.CR] * opts.icmul) + .5);
  437. var chosen = [];
  438. var weapon_cnt = 0;
  439. var armor_cnt = 0;
  440. var scrolls_cnt = 0;
  441. var special = {
  442. weapon: [],
  443. armor: [],
  444. Scroll: [],
  445. };
  446. for(var n = 0; n < amt; n++) {
  447. if(list.length == 0) break;
  448. var ind = Math.floor(frand() * list.length);
  449. var it = list[ind];
  450. if(it.type == 'weapon') {
  451. if(++weapon_cnt > opts.max_weapons) {
  452. filterType('weapon', list, special);
  453. n--;
  454. continue;
  455. }
  456. }
  457. if(it.type == 'armor') {
  458. if(++armor_cnt > opts.max_armor) {
  459. filterType('armor', list, special);
  460. n--;
  461. continue;
  462. }
  463. }
  464. if(it.type == 'Scroll') {
  465. if(++scrolls_cnt > opts.max_scrolls) {
  466. filterType('Scroll', list, special);
  467. n--;
  468. continue;
  469. }
  470. }
  471. chosen.push(it);
  472. addItem(it, list, special);
  473. list.splice(ind, 1);
  474. // console.log(n, ind, list);
  475. }
  476. // console.log(sz, r, amt, list.length, chosen);
  477. }
  478. }
  479. }
  480. window.addEventListener("load", function() {
  481. if(wh) {
  482. generateList({
  483. exclude_pocket: hash_opts[0] == 1,
  484. max_weapons: hash_opts[1]|0,
  485. max_armor: hash_opts[2]|0,
  486. max_scrolls: hash_opts[3]|0,
  487. icmul: hash_opts[4]|0,
  488. pmul: hash_opts[5]|0,
  489. qdice: hash_opts[6].split('d'),
  490. CR: hash_opts[7]|0,
  491. });
  492. $('.menu').remove();
  493. $all('.reroll').forEach(function(x) { x.remove() });
  494. return;
  495. }
  496. $all('control').forEach(makeControl);
  497. $('#finalize').addEventListener('click', function(e) {
  498. $('.menu').remove();
  499. $all('.reroll').forEach(function(x) { x.remove() });
  500. });
  501. $('#go').addEventListener('click', function(e) {
  502. e.preventDefault();
  503. $('#finalize').style.display = 'inline-block';
  504. var prng_seed_orig = prng_seed;
  505. // var raw = $('#item-count').value.toLowerCase();
  506. // var [n,d] = raw.split('d');
  507. /* for checking the dice algorithm
  508. var o = {};
  509. for(var i = 0; i < n+2; i ++) o[i] = 0;
  510. for(var i = 0; i < 1000000; i ++){
  511. o[roll(n,d)]++;
  512. }
  513. console.log(o);
  514. */
  515. var CRn = $('#tier');
  516. var o = {
  517. exclude_pocket: $('#exclude-pockets').checked,
  518. exclude_cursed: $('#exclude-cursed').checked,
  519. max_weapons: parseFloat($('#max-weapons').value),
  520. max_armor: parseFloat($('#max-armor').value),
  521. max_scrolls: parseFloat($('#max-scrolls').value),
  522. icmul: parseFloat($('#item-count').value),
  523. pmul: parseFloat($('#price-mul').value),
  524. qdice: $('#avail-dice').value.split('d'),
  525. CR: CRn.selectedOptions[0].value - 1,
  526. include: {
  527. PH: $('#include-PH').checked,
  528. DM: $('#include-DM').checked,
  529. XG: $('#include-XG').checked,
  530. EE: $('#include-EE').checked,
  531. },
  532. };
  533. generateList(o);
  534. // window.location.hash = prng_seed_orig + '_'
  535. // + (o.exclude_pocket ? '1' : '0') + '!'
  536. })
  537. });
  538. </script>
  539. </head>
  540. <body>
  541. <div class="menu">
  542. <select id="tier">
  543. <option value="1">Tier 1: 1-4</option>
  544. <option value="2" selected>Tier 2: 5-10</option>
  545. <option value="3">Tier 3: 11-16</option>
  546. <option value="4">Tier 4: 17-20</option>
  547. </select>
  548. <div class="option-list">
  549. <div class="left">
  550. <control cid="item-count" label="Item Count Mul.:" type="edit">1</control>
  551. <control cid="price-mul" label="Price Multiplier:" type="edit">1</control>
  552. <control cid="avail-dice" label="Avail/Price:" type="edit">1d6</control>
  553. <!--</div>
  554. <div class="left">-->
  555. <hr/>
  556. <label><span>Per Rarity:</span></label>
  557. <control cid="max-weapons" label="Max Weapons:" type="edit">2</control>
  558. <control cid="max-armor" label="Max Armor:" type="edit">2</control>
  559. <control cid="max-scrolls" label="Max Scrolls:" type="edit">2</control>
  560. <hr/>
  561. <control cid="exclude-pockets" label="Exclude Pocket Dimension" type="checkbox">1</control>
  562. <control cid="exclude-cursed" label="Exclude Cursed Items" type="checkbox">1</control>
  563. <control cid="include-PH" label="Include Player's Handbook" type="checkbox">1</control>
  564. <control cid="include-DM" label="Include DM's Guide" type="checkbox">1</control>
  565. <control cid="include-XG" label="Include Xanathar's Guide" type="checkbox">1</control>
  566. <control cid="include-EE" label="Include Elemental Evil" type="checkbox">0</control>
  567. </div>
  568. </div>
  569. <br/>
  570. <br/>
  571. <button id="go">Generate</button>
  572. <button id="finalize" style="display:none;">Finalize</button>
  573. </div>
  574. <table class="item-table" >
  575. <thead>
  576. <tr>
  577. <th>Item Name</th>
  578. <th>Rarity</th>
  579. <th>Qty.<br/>Available</th>
  580. <th>Price</th>
  581. <th class="reroll">Reroll</th>
  582. </tr>
  583. </thead>
  584. <tbody class="item-list"></tbody>
  585. </table>
  586. </body>
  587. </html>