pf2e-sage-stats 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -96,3 +96,12 @@ sage! macro set name="perception" dice="[{{p}::out:m} {flat::{f:none}:} {d:1}d20
96
96
  ```
97
97
  sage! macro set name="lore" dice="[{{p}::out:m} {flat::{f:none}:} {d:1}d20+{{p}::lore.{0}:{a:0}} {m} {v} {{p}::name:} (mod `{m:+0}`) {{p}::lore.{0}.info:} {{p}::lore.{0}.name:} Lore Check {utils::{t:known}:vs} {{t:none}::name:} {utils::{t:known}:`{r:Spell} DC ({c:+0} DC) `} {dc} {utils::{t:known}:dc} {{t:none}::dc.{r:spells}:} {c} {...}]" cat=Skills tier=Server -y
98
98
  ```
99
+ ```
100
+ sage! macro set name="assurance" dice="[{{p}::out:m} {flat::{f:none}:} (10)d20+{0:0}-10 {v} {{p}::name:} Assurance Check {utils::{t:known}:vs} {{t:none}::name:} {utils::{t:known}:`{r:Spell} DC ({c:+0} DC) `} {dc} {utils::{t:known}:dc} {{t:none}::dc.{r:spells}:} {c} {...}]" cat=Skills tier=Server -y
101
+ ```
102
+ ```
103
+ sage! macro set name="skill" dice="[{{p}::out:m} {flat::{f:none}:} d20+{0:0} {v} {{p}::name:} Skill Check {utils::{t:known}:vs} {{t:none}::name:} {utils::{t:known}:`{r:Spell} DC ({c:+0} DC) `} {dc} {utils::{t:known}:dc} {{t:none}::dc.{r:spells}:} {c} {...}]" cat=Skills tier=Server -y
104
+ ```
105
+ ```
106
+ sage! macro set name="secret" dice="[{{p}::out:m} {flat::{f:none}:} d20+{{p}::{0}:{a:0}} {v} {{p}::name:} Secret {0} Check {utils::{t:known}:vs} {{t:none}::name:} {utils::{t:known}:`{r:Spell} DC ({c:+0} DC) `} {dc} {utils::{t:known}:dc} {{t:none}::dc.{r:spells}:} {c} {...}]" cat=Skills tier=Server -y
107
+ ```
package/dist/app.js CHANGED
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.sortFolder = exports.formatHP = exports.formatTable = exports.formatCommand = exports.formatTSV = exports.flatten = exports.parseStatblock = exports.parseJSON = exports.formatJSON = exports.fromatMap = exports.adjustmentMap = exports.prediceateMap = exports.stub = void 0;
15
+ exports.sortFolder = exports.formatTracker = exports.newtracker = exports.formatHP = exports.formatTable = exports.formatCommand = exports.formatTSV = exports.flatten = exports.parseStatblock = exports.parseTracker = exports.parseJSON = exports.formatJSON = exports.fromatMap = exports.adjustmentMap = exports.prediceateMap = exports.stub = void 0;
16
16
  const zod_1 = __importDefault(require("zod"));
17
17
  const tsv_1 = require("tsv");
18
18
  const lodash_1 = require("lodash");
@@ -63,6 +63,7 @@ const schema = zod_1.default.object({
63
63
  lores: zod_1.default.record(zod_1.default.string(), zod_1.default.object({
64
64
  mod: zod_1.default.coerce.number().default(0),
65
65
  name: zod_1.default.string(),
66
+ info: zod_1.default.array(zod_1.default.string()).default([]),
66
67
  })).default({}),
67
68
  melee: zod_1.default.record(zod_1.default.string(), zod_1.default.object({
68
69
  mod: zod_1.default.coerce.number().default(0),
@@ -178,6 +179,26 @@ exports.stub = {
178
179
  },
179
180
  skillInfo: {},
180
181
  };
182
+ const childSchema = zod_1.default.object({
183
+ name: zod_1.default.string().default(""),
184
+ alias: zod_1.default.string().default(""),
185
+ hp: zod_1.default.coerce.number().default(0),
186
+ temphp: zod_1.default.coerce.number().default(0),
187
+ maxhp: zod_1.default.coerce.number().default(10),
188
+ conditions: zod_1.default.string().default(''),
189
+ });
190
+ const charSchema = zod_1.default.object({
191
+ init: zod_1.default.coerce.number().default(0),
192
+ foe: zod_1.default.boolean().default(false),
193
+ state: zod_1.default.union([zod_1.default.literal('empty'), zod_1.default.literal('arrow'), zod_1.default.literal('check'), zod_1.default.literal('cross')]).default('empty'),
194
+ name: zod_1.default.string().default(""),
195
+ alias: zod_1.default.string().default(""),
196
+ hp: zod_1.default.coerce.number().default(0),
197
+ temphp: zod_1.default.coerce.number().default(0),
198
+ maxhp: zod_1.default.coerce.number().default(10),
199
+ conditions: zod_1.default.string().default(''),
200
+ children: zod_1.default.array(childSchema).default([]),
201
+ });
181
202
  exports.prediceateMap = Object.assign(Object.assign({}, ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20].map((v) => ([`${v}`, `d20 >= ${v} flat;`])).reduce((p, [k, v]) => { p[k] = v; return p; }, {}))), { c: 'd20 >= 5 flat;', concealed: 'd20 >= 5 flat;', h: 'd20 >= 11 flat;', hidden: 'd20 >= 11 flat;' });
182
203
  const adjustmentMap = () => ({
183
204
  'incredibly-easy': '`(Incredibly Easy)`',
@@ -197,6 +218,8 @@ const formatJSON = (stats) => JSON.stringify(stats, undefined, 2);
197
218
  exports.formatJSON = formatJSON;
198
219
  const parseJSON = (json) => schema.parse(JSON.parse(json));
199
220
  exports.parseJSON = parseJSON;
221
+ const parseTracker = (json) => zod_1.default.array(charSchema).parse(JSON.parse(json));
222
+ exports.parseTracker = parseTracker;
200
223
  const locateName = (statblock) => {
201
224
  const regex = /^([^()]+?)(\s+\(\d+\))?\s+\S+\s+(-?\d+)$/m;
202
225
  const match = statblock.match(regex);
@@ -227,7 +250,7 @@ const locateInts = (statblock, section, alias, signed = true) => {
227
250
  ]);
228
251
  }
229
252
  }
230
- return output.reduce((p, [key, name, value]) => { p[key] = { mod: value, name: name }; return p; }, {});
253
+ return output.reduce((p, [key, name, value]) => { p[key] = { mod: value, name: name, info: [] }; return p; }, {});
231
254
  };
232
255
  const locateIntsAfter = (statblock, alias, signed = true) => {
233
256
  const regex = new RegExp(`${alias}([\\s\\S]*?)(${signed ? '[+-]' : '-?'}\\d+)`, 'g');
@@ -244,7 +267,7 @@ const locateIntsAfter = (statblock, alias, signed = true) => {
244
267
  ]);
245
268
  }
246
269
  }
247
- return output.reduce((p, [key, name, value]) => { p[key] = { mod: value, name: name }; return p; }, {});
270
+ return output.reduce((p, [key, name, value]) => { p[key] = { mod: value, name: name, info: [] }; return p; }, {});
248
271
  };
249
272
  const locateStrikes = (statblock, alias) => {
250
273
  const regex = new RegExp(`${alias}(\\s+\\[.+\\])?\\s+(.+?)\\s+(\\(x\\d+\\)\\s+)?([+-]\\d+)\\s*([\\S\\s]*?),\\s+(Damage|Effect)\\s+(\\S+\\s+\\S+.*?)(?=$|\\s+Melee|\\s+Ranged)`, 'gm');
@@ -340,6 +363,7 @@ const dc2DC = (secret, bump) => ([key, value]) => [`dc.${key}`, secret ? `||${va
340
363
  const mod2DC = (secret, bump) => ([key, value]) => [`dc.${key}`, secret ? `||${10 + value + bump}||` : `${10 + value + bump}`];
341
364
  const toLoreMod = ([key, value]) => [`lore.${key}`, value.mod];
342
365
  const toLoreName = ([key, value]) => [`lore.${key}.name`, value.name];
366
+ const toLoreInfo = ([key, value]) => [`lore.${key}.info`, `\`(${value.info.join(', ')})\``];
343
367
  const toMelee = ([key, value]) => [
344
368
  [`melee.${key}`, value.mod],
345
369
  [`melee.${key}.desc`, value.desc],
@@ -364,6 +388,11 @@ const flatten = (stats, secretDC = false, defaultSkills = false, recallDC = fals
364
388
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
365
389
  const untrained = stats.untrained;
366
390
  const skills = defaultSkills ? Object.assign({ acrobatics: untrained + stats.attributes.dexterity, arcana: untrained + stats.attributes.intelligence, athletics: untrained + stats.attributes.strength, crafting: untrained + stats.attributes.intelligence, deception: untrained + stats.attributes.charisma, diplomacy: untrained + stats.attributes.charisma, intimidation: untrained + stats.attributes.charisma, medicine: untrained + stats.attributes.wisdom, nature: untrained + stats.attributes.wisdom, occultism: untrained + stats.attributes.intelligence, performance: untrained + stats.attributes.charisma, religion: untrained + stats.attributes.wisdom, society: untrained + stats.attributes.intelligence, stealth: untrained + stats.attributes.dexterity, survival: untrained + stats.attributes.wisdom, thievery: untrained + stats.attributes.dexterity }, stats.skills) : stats.skills;
391
+ const lores = defaultSkills ? Object.assign(Object.assign({}, stats.lores), { other: {
392
+ mod: untrained + stats.attributes.intelligence,
393
+ name: "Other",
394
+ info: ['untrained']
395
+ } }) : stats.lores;
367
396
  let melee = (0, lodash_1.entries)(stats.melee).map(([k, v]) => {
368
397
  var _a;
369
398
  return [k, {
@@ -413,8 +442,9 @@ const flatten = (stats, secretDC = false, defaultSkills = false, recallDC = fals
413
442
  ['ref', stats.saves.reflex + ((_d = stats.bump.saves) !== null && _d !== void 0 ? _d : stats.bump.default)],
414
443
  ['perception', stats.perception + ((_e = stats.bump.perception) !== null && _e !== void 0 ? _e : stats.bump.default)],
415
444
  ...(0, lodash_1.entries)(skills.performance ? Object.assign(Object.assign({}, skills), { perfomance: skills.performance }) : skills).map(([k, v]) => { var _a; return [k, v + ((_a = stats.bump.skills) !== null && _a !== void 0 ? _a : stats.bump.default)]; }),
416
- ...(0, lodash_1.entries)(stats.lores).map(toLoreMod).map(([k, v]) => { var _a; return [k, v + ((_a = stats.bump.skills) !== null && _a !== void 0 ? _a : stats.bump.default)]; }),
417
- ...(0, lodash_1.entries)(stats.lores).map(toLoreName),
445
+ ...(0, lodash_1.entries)(lores).map(toLoreMod).map(([k, v]) => { var _a; return [k, v + ((_a = stats.bump.skills) !== null && _a !== void 0 ? _a : stats.bump.default)]; }),
446
+ ...(0, lodash_1.entries)(lores).map(toLoreName),
447
+ ...(0, lodash_1.entries)(lores).filter(([, v]) => v.info.length > 0).map(toLoreInfo),
418
448
  ...melee.flatMap(toMelee),
419
449
  ...ranged.flatMap(toRanged),
420
450
  ...(0, lodash_1.entries)(stats.extra),
@@ -485,6 +515,85 @@ const formatHP = (arg) => {
485
515
  return _formatHP(negative ? maxhp - hp : hp, maxhp);
486
516
  };
487
517
  exports.formatHP = formatHP;
518
+ const newtracker = (status) => {
519
+ var _a, _b;
520
+ const regex = /((.+)\s+\((\d+)\/(\d+)\s+HP\):(.*)(\n>(.+)\s+\((\d+)\/(\d+)\s+HP\):(.*))*)/gm;
521
+ const match = status.match(regex);
522
+ const tracker = zod_1.default.array(charSchema).parse([]);
523
+ for (const m of match !== null && match !== void 0 ? match : []) {
524
+ const mmatch = m.match(/([^>]+)\s+\((\d+)\/(\d+)\s+HP\):(.*)/);
525
+ const cmatch = m.match(/>\s+(\S+)\s+\((\d+)\/(\d+)\s+HP\):(.*)/g);
526
+ const char = charSchema.parse({
527
+ name: mmatch === null || mmatch === void 0 ? void 0 : mmatch[1],
528
+ hp: mmatch === null || mmatch === void 0 ? void 0 : mmatch[2],
529
+ maxhp: mmatch === null || mmatch === void 0 ? void 0 : mmatch[3],
530
+ conditions: (_a = mmatch === null || mmatch === void 0 ? void 0 : mmatch[4]) === null || _a === void 0 ? void 0 : _a.trim(),
531
+ });
532
+ for (const c of cmatch !== null && cmatch !== void 0 ? cmatch : []) {
533
+ const fmatch = c.match(/>\s+(\S+)\s+\((\d+)\/(\d+)\s+HP\):(.*)/);
534
+ const child = childSchema.parse({
535
+ name: fmatch === null || fmatch === void 0 ? void 0 : fmatch[1],
536
+ hp: fmatch === null || fmatch === void 0 ? void 0 : fmatch[2],
537
+ maxhp: fmatch === null || fmatch === void 0 ? void 0 : fmatch[3],
538
+ conditions: (_b = fmatch === null || fmatch === void 0 ? void 0 : fmatch[4]) === null || _b === void 0 ? void 0 : _b.trim(),
539
+ });
540
+ char.children.push(child);
541
+ }
542
+ tracker.push(char);
543
+ }
544
+ return tracker;
545
+ };
546
+ exports.newtracker = newtracker;
547
+ const formatTracker = (tracker) => {
548
+ const nameLength = tracker.reduce((p, c) => {
549
+ const length = c.children.reduce((pp, cc) => pp > cc.name.length + 1 ? pp : cc.name.length + 1, c.name.length);
550
+ return p > length ? p : length;
551
+ }, 0);
552
+ const hpLength = tracker.reduce((p, c) => {
553
+ const hpLength = c.maxhp.toString().length + 1 + c.hp.toString().length + (c.temphp > 1 ? c.temphp.toString().length + 1 : 0);
554
+ const length = c.children.reduce((pp, cc) => {
555
+ const hhpLength = cc.maxhp.toString().length + 1 + cc.hp.toString().length + (cc.temphp > 1 ? cc.temphp.toString().length + 1 : 0);
556
+ return pp > hhpLength ? pp : hhpLength;
557
+ }, hpLength);
558
+ return p > length ? p : length;
559
+ }, 0);
560
+ const stateMap = {
561
+ empty: ':emptynode:',
562
+ arrow: ':arrownode:',
563
+ check: ':checknode:',
564
+ cross: ':crossnode:',
565
+ };
566
+ const hpMap = [
567
+ ':beaten:',
568
+ ':bruised:',
569
+ ':wounded:',
570
+ ':limping:',
571
+ ':dying:',
572
+ ].reverse();
573
+ const lines = tracker.sort((a, b) => (a.init === b.init) ? (b.foe ? 1 : 0) - (a.foe ? 1 : 0) : b.init - a.init).flatMap((char, index) => {
574
+ const ls = [];
575
+ if (tracker[index - 1] && tracker[index - 1].foe !== char.foe) {
576
+ ls.push('');
577
+ }
578
+ const hp = char.foe ? (`-${char.maxhp - char.hp}${char.temphp > 0 ? `+${char.temphp}` : ''}`.padStart(hpLength)) : (`${char.hp}${char.temphp > 0 ? `+${char.temphp}` : ''}/${char.maxhp}`.padStart(hpLength));
579
+ const hpBar = (value, max) => (value <= 0 ? (':crossed:') : value >= max ? (':healthy:') : (hpMap[Math.round((hpMap.length - 1) * value / max)]));
580
+ ls.push((`${stateMap[char.state]}**\` ${char.name.padEnd(nameLength)} ▏${char.alias.padEnd(3)} ▏${hp} \`** ${hpBar(char.hp, char.maxhp)} `));
581
+ if (char.conditions.length > 0) {
582
+ ls.push(`-# ${char.conditions}`);
583
+ }
584
+ for (const child of char.children) {
585
+ const hhp = char.foe ? (`-${child.maxhp - child.hp}${child.temphp > 0 ? `+${child.temphp}` : ''}`.padStart(hpLength)) : (`${child.hp}${child.temphp > 0 ? `+${child.temphp}` : ''}/${child.maxhp}`.padStart(hpLength));
586
+ ls.push((`:smallnode:**\`- ${child.name.padEnd(nameLength - 1)} ▏${child.alias.padEnd(3)} ▏${hhp} \`** ${hpBar(child.hp, child.maxhp)} `));
587
+ if (child.conditions.length > 0) {
588
+ ls.push(`-# ${child.conditions}`);
589
+ }
590
+ }
591
+ return ls;
592
+ });
593
+ lines.push(':spacer:');
594
+ return lines.join('\n');
595
+ };
596
+ exports.formatTracker = formatTracker;
488
597
  const sortFolder = () => __awaiter(void 0, void 0, void 0, function* () {
489
598
  const dir = yield (0, promises_1.readdir)('.');
490
599
  const summary = yield Promise.all(dir.map((file) => __awaiter(void 0, void 0, void 0, function* () {
package/dist/index.js CHANGED
@@ -9,6 +9,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  step((generator = generator.apply(thisArg, _arguments || [])).next());
10
10
  });
11
11
  };
12
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
13
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
14
+ var m = o[Symbol.asyncIterator], i;
15
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
16
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
17
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
18
+ };
12
19
  var __importDefault = (this && this.__importDefault) || function (mod) {
13
20
  return (mod && mod.__esModule) ? mod : { "default": mod };
14
21
  };
@@ -16,6 +23,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
16
23
  const commander_1 = require("commander");
17
24
  const promises_1 = __importDefault(require("fs/promises"));
18
25
  const path_1 = __importDefault(require("path"));
26
+ const readline_1 = __importDefault(require("readline"));
19
27
  const package_json_1 = __importDefault(require("../package.json"));
20
28
  const app_1 = require("./app");
21
29
  const program = new commander_1.Command(package_json_1.default.name);
@@ -144,4 +152,122 @@ program.command('command')
144
152
  const output = option.output ? option.output : path_1.default.join(path_1.default.parse(argument).dir, path_1.default.parse(argument).name + '-command.txt');
145
153
  yield promises_1.default.writeFile(output, (0, app_1.formatCommand)(stats, option.secretDC, option.defaultSkills, option.recallDC), { encoding: 'utf-8' });
146
154
  }));
155
+ program.command('newtracker')
156
+ .argument('<file>', 'input text file')
157
+ .option('-o, --output <file>', 'output file name')
158
+ .action((argument, option) => __awaiter(void 0, void 0, void 0, function* () {
159
+ const file = yield promises_1.default.readFile(argument, { encoding: 'utf-8' });
160
+ const tracker = (0, app_1.newtracker)(file);
161
+ if (option.output) {
162
+ yield promises_1.default.writeFile(option.output, JSON.stringify(tracker, undefined, 2), { encoding: 'utf-8' });
163
+ }
164
+ else {
165
+ console.log(JSON.stringify(tracker, undefined, 2));
166
+ }
167
+ }));
168
+ program.command('tracker')
169
+ .argument('<file>', 'input text file')
170
+ .option('-o, --output <file>', 'output file name')
171
+ .action((argument, option) => __awaiter(void 0, void 0, void 0, function* () {
172
+ const file = yield promises_1.default.readFile(argument, { encoding: 'utf-8' });
173
+ const tracker = (0, app_1.parseTracker)(file);
174
+ if (option.output) {
175
+ yield promises_1.default.writeFile(option.output, (0, app_1.formatTracker)(tracker), { encoding: 'utf-8' });
176
+ }
177
+ else {
178
+ console.log((0, app_1.formatTracker)(tracker));
179
+ }
180
+ }));
181
+ program.command('track')
182
+ .argument('<file>', 'input text file')
183
+ .option('-o, --output <file>', 'output file name')
184
+ .action((argument, option) => __awaiter(void 0, void 0, void 0, function* () {
185
+ const iterator = promises_1.default.watch(argument);
186
+ const invoke = (filename) => __awaiter(void 0, void 0, void 0, function* () {
187
+ const file = yield promises_1.default.readFile(filename, { encoding: 'utf-8' });
188
+ const tracker = (0, app_1.parseTracker)(file);
189
+ if (option.output) {
190
+ yield promises_1.default.writeFile(option.output, (0, app_1.formatTracker)(tracker), { encoding: 'utf-8' });
191
+ }
192
+ else {
193
+ console.clear();
194
+ console.log((0, app_1.formatTracker)(tracker));
195
+ }
196
+ });
197
+ yield invoke(argument);
198
+ yield Promise.all([
199
+ (() => __awaiter(void 0, void 0, void 0, function* () {
200
+ var _a, e_1, _b, _c;
201
+ var _d;
202
+ try {
203
+ for (var _e = true, iterator_1 = __asyncValues(iterator), iterator_1_1; iterator_1_1 = yield iterator_1.next(), _a = iterator_1_1.done, !_a; _e = true) {
204
+ _c = iterator_1_1.value;
205
+ _e = false;
206
+ const value = _c;
207
+ yield invoke((_d = value.filename) !== null && _d !== void 0 ? _d : argument);
208
+ }
209
+ }
210
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
211
+ finally {
212
+ try {
213
+ if (!_e && !_a && (_b = iterator_1.return)) yield _b.call(iterator_1);
214
+ }
215
+ finally { if (e_1) throw e_1.error; }
216
+ }
217
+ }))(),
218
+ (() => __awaiter(void 0, void 0, void 0, function* () {
219
+ const rl = readline_1.default.createInterface({
220
+ input: process.stdin,
221
+ output: process.stdout
222
+ });
223
+ while (true) {
224
+ yield new Promise((resolve) => setTimeout(resolve, 500));
225
+ const input = yield new Promise((resolve) => (rl.question('>', resolve)));
226
+ const hpmod = input.match(/([^\d+-]+)\s*([+-]?\d+)/);
227
+ if (hpmod) {
228
+ const file = yield promises_1.default.readFile(argument, { encoding: 'utf-8' });
229
+ const tracker = (0, app_1.parseTracker)(file);
230
+ const index = tracker.findIndex((c) => c.alias === hpmod[1]);
231
+ if (index >= 0) {
232
+ let hp = tracker[index].hp;
233
+ let thp = tracker[index].temphp;
234
+ if (hpmod[2].match(/^[+-]/)) {
235
+ const delta = parseInt(hpmod[2]);
236
+ if (delta < 0) {
237
+ thp += delta;
238
+ hp += Math.min(thp, 0);
239
+ }
240
+ else {
241
+ hp += delta;
242
+ }
243
+ }
244
+ else {
245
+ hp = parseInt(hpmod[2]);
246
+ }
247
+ tracker[index].temphp = Math.max(0, thp);
248
+ tracker[index].hp = Math.min(tracker[index].maxhp, Math.max(0, hp));
249
+ yield promises_1.default.writeFile(argument, JSON.stringify(tracker, undefined, 2), { encoding: 'utf-8' });
250
+ }
251
+ }
252
+ const thpmod = input.match(/(\S+)\s+[tT]\s*([+-]?\d+)/);
253
+ if (thpmod) {
254
+ const file = yield promises_1.default.readFile(argument, { encoding: 'utf-8' });
255
+ const tracker = (0, app_1.parseTracker)(file);
256
+ const index = tracker.findIndex((c) => c.alias === thpmod[1]);
257
+ if (index >= 0) {
258
+ let thp = tracker[index].temphp;
259
+ if (thpmod[2].match(/^[+-]/)) {
260
+ thp += parseInt(thpmod[2]);
261
+ }
262
+ else {
263
+ thp = parseInt(thpmod[2]);
264
+ }
265
+ tracker[index].temphp = Math.max(0, thp);
266
+ yield promises_1.default.writeFile(argument, JSON.stringify(tracker, undefined, 2), { encoding: 'utf-8' });
267
+ }
268
+ }
269
+ }
270
+ }))(),
271
+ ]);
272
+ }));
147
273
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pf2e-sage-stats",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "An RPG Sage's .tsv stat generation tool",
5
5
  "main": "dist/index.js",
6
6
  "author": "ikariott@gmail.com",
package/src/app.ts CHANGED
@@ -49,6 +49,7 @@ const schema = zod.object({
49
49
  lores: zod.record(zod.string(), zod.object({
50
50
  mod: zod.coerce.number().default(0),
51
51
  name: zod.string(),
52
+ info: zod.array(zod.string()).default([]),
52
53
  })).default({}),
53
54
  melee: zod.record(zod.string(), zod.object({
54
55
  mod: zod.coerce.number().default(0),
@@ -168,6 +169,32 @@ export const stub: Required<Schema> = {
168
169
  skillInfo: {},
169
170
  };
170
171
 
172
+ const childSchema = zod.object({
173
+ name: zod.string().default(""),
174
+ alias: zod.string().default(""),
175
+ hp: zod.coerce.number().default(0),
176
+ temphp: zod.coerce.number().default(0),
177
+ maxhp: zod.coerce.number().default(10),
178
+ conditions: zod.string().default(''),
179
+ });
180
+
181
+ export type ChildSchema = zod.infer<typeof childSchema>;
182
+
183
+ const charSchema = zod.object({
184
+ init: zod.coerce.number().default(0),
185
+ foe: zod.boolean().default(false),
186
+ state: zod.union([zod.literal('empty'), zod.literal('arrow'), zod.literal('check'), zod.literal('cross')]).default('empty'),
187
+ name: zod.string().default(""),
188
+ alias: zod.string().default(""),
189
+ hp: zod.coerce.number().default(0),
190
+ temphp: zod.coerce.number().default(0),
191
+ maxhp: zod.coerce.number().default(10),
192
+ conditions: zod.string().default(''),
193
+ children: zod.array(childSchema).default([]),
194
+ });
195
+
196
+ export type CharSchema = zod.infer<typeof charSchema>;
197
+
171
198
  export const prediceateMap: Record<string, string> = {
172
199
  ...([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20].map((v): [string, string] => (
173
200
  [`${v}`, `d20 >= ${v} flat;`]
@@ -199,6 +226,8 @@ const dcByLevel = [13, 14, 15, 16, 18, 19, 20, 22, 23, 24, 26, 27, 28, 30, 31, 3
199
226
  export const formatJSON = (stats: Schema): string => JSON.stringify(stats, undefined, 2);
200
227
  export const parseJSON = (json: string): Schema => schema.parse(JSON.parse(json));
201
228
 
229
+ export const parseTracker = (json: string): CharSchema[] => zod.array(charSchema).parse(JSON.parse(json));
230
+
202
231
  const locateName = (statblock: string): { name: string; level: number } | null => {
203
232
  const regex = /^([^()]+?)(\s+\(\d+\))?\s+\S+\s+(-?\d+)$/m;
204
233
 
@@ -221,7 +250,7 @@ const locateInt = <T extends string>(name: T, statblock: string, section: string
221
250
  return null;
222
251
  }
223
252
 
224
- const locateInts = (statblock: string, section: string, alias: string, signed: boolean = true): Record<string, { mod: number; name: string; }> => {
253
+ const locateInts = (statblock: string, section: string, alias: string, signed: boolean = true): Record<string, { mod: number; name: string; info: string[] }> => {
225
254
  const regex = new RegExp(`(${section}|,)([^,]*?)${alias}\\s+(${signed ? '[+-]' : '-?'}\\d+)`, 'g');
226
255
 
227
256
  let matches: RegExpExecArray | null = null;
@@ -239,10 +268,10 @@ const locateInts = (statblock: string, section: string, alias: string, signed: b
239
268
  }
240
269
  }
241
270
 
242
- return output.reduce<Record<string, { mod: number; name: string; }>>((p, [key, name, value]) => { p[key] = { mod: value, name: name }; return p; }, {});
271
+ return output.reduce<Record<string, { mod: number; name: string; info: string[] }>>((p, [key, name, value]) => { p[key] = { mod: value, name: name, info: [] }; return p; }, {});
243
272
  }
244
273
 
245
- const locateIntsAfter = (statblock: string, alias: string, signed: boolean = true): Record<string, { mod: number; name: string; }> => {
274
+ const locateIntsAfter = (statblock: string, alias: string, signed: boolean = true): Record<string, { mod: number; name: string; info: string[] }> => {
246
275
  const regex = new RegExp(`${alias}([\\s\\S]*?)(${signed ? '[+-]' : '-?'}\\d+)`, 'g');
247
276
 
248
277
  let matches: RegExpExecArray | null = null;
@@ -260,7 +289,7 @@ const locateIntsAfter = (statblock: string, alias: string, signed: boolean = tru
260
289
  }
261
290
  }
262
291
 
263
- return output.reduce<Record<string, { mod: number; name: string; }>>((p, [key, name, value]) => { p[key] = { mod: value, name: name }; return p; }, {});
292
+ return output.reduce<Record<string, { mod: number; name: string; info: string[] }>>((p, [key, name, value]) => { p[key] = { mod: value, name: name, info: [] }; return p; }, {});
264
293
  }
265
294
 
266
295
  const locateStrikes = (statblock: string, alias: string): Record<string, Schema['melee'][string]> => {
@@ -437,6 +466,7 @@ const dc2DC = (secret: boolean, bump: number) => ([key, value]: [string | number
437
466
  const mod2DC = (secret: boolean, bump: number) => ([key, value]: [string | number, number]): [string, string] => [`dc.${key}`, secret ? `||${10 + value + bump}||` : `${10 + value + bump}`];
438
467
  const toLoreMod = ([key, value]: [string | number, { mod: number; name: string; }]): [string, number] => [`lore.${key}`, value.mod];
439
468
  const toLoreName = ([key, value]: [string | number, { mod: number; name: string; }]): [string, string] => [`lore.${key}.name`, value.name];
469
+ const toLoreInfo = ([key, value]: [string | number, { mod: number; name: string; info: string[] }]): [string, string] => [`lore.${key}.info`, `\`(${value.info.join(', ')})\``];
440
470
  const toMelee = ([key, value]: [string, Schema['melee'][string]]): [string, number | string][] => [
441
471
  [`melee.${key}`, value.mod],
442
472
  [`melee.${key}.desc`, value.desc],
@@ -482,6 +512,15 @@ export const flatten = (stats: Schema, secretDC: boolean = false, defaultSkills:
482
512
  ...stats.skills,
483
513
  } : stats.skills;
484
514
 
515
+ const lores = defaultSkills ? {
516
+ ...stats.lores,
517
+ other: {
518
+ mod: untrained + stats.attributes.intelligence,
519
+ name: "Other",
520
+ info: ['untrained']
521
+ }
522
+ } : stats.lores
523
+
485
524
  let melee = entries(stats.melee).map(([k, v]): [string, typeof v] => [k, {
486
525
  mod: v.mod + (stats.bump.attack ?? stats.bump.default),
487
526
  desc: v.desc,
@@ -534,8 +573,9 @@ export const flatten = (stats: Schema, secretDC: boolean = false, defaultSkills:
534
573
  ['ref', stats.saves.reflex + (stats.bump.saves ?? stats.bump.default)],
535
574
  ['perception', stats.perception + (stats.bump.perception ?? stats.bump.default)],
536
575
  ...entries(skills.performance ? { ...skills, perfomance: skills.performance } : skills).map(([k, v]) => [k, v + (stats.bump.skills ?? stats.bump.default)]),
537
- ...entries(stats.lores).map(toLoreMod).map(([k, v]) => [k, v + (stats.bump.skills ?? stats.bump.default)]),
538
- ...entries(stats.lores).map(toLoreName),
576
+ ...entries(lores).map(toLoreMod).map(([k, v]) => [k, v + (stats.bump.skills ?? stats.bump.default)]),
577
+ ...entries(lores).map(toLoreName),
578
+ ...entries(lores).filter(([, v]) => v.info.length > 0).map(toLoreInfo),
539
579
  ...melee.flatMap(toMelee),
540
580
  ...ranged.flatMap(toRanged),
541
581
  ...entries(stats.extra),
@@ -619,6 +659,132 @@ export const formatHP = (arg: string) => {
619
659
  return _formatHP(negative ? maxhp - hp : hp, maxhp);
620
660
  };
621
661
 
662
+ export const newtracker = (status: string) => {
663
+ const regex = /((.+)\s+\((\d+)\/(\d+)\s+HP\):(.*)(\n>(.+)\s+\((\d+)\/(\d+)\s+HP\):(.*))*)/gm;
664
+
665
+ const match = status.match(regex);
666
+
667
+ const tracker = zod.array(charSchema).parse([]);
668
+
669
+ for (const m of match ?? []) {
670
+ const mmatch = m.match(/([^>]+)\s+\((\d+)\/(\d+)\s+HP\):(.*)/)
671
+ const cmatch = m.match(/>\s+(\S+)\s+\((\d+)\/(\d+)\s+HP\):(.*)/g)
672
+
673
+ const char = charSchema.parse({
674
+ name: mmatch?.[1],
675
+ hp: mmatch?.[2],
676
+ maxhp: mmatch?.[3],
677
+ conditions: mmatch?.[4]?.trim(),
678
+ });
679
+
680
+ for (const c of cmatch ?? []) {
681
+ const fmatch = c.match(/>\s+(\S+)\s+\((\d+)\/(\d+)\s+HP\):(.*)/)
682
+
683
+ const child = childSchema.parse({
684
+ name: fmatch?.[1],
685
+ hp: fmatch?.[2],
686
+ maxhp: fmatch?.[3],
687
+ conditions: fmatch?.[4]?.trim(),
688
+ });
689
+
690
+ char.children.push(child);
691
+ }
692
+
693
+ tracker.push(char);
694
+ }
695
+
696
+ return tracker;
697
+ };
698
+
699
+ export const formatTracker = (tracker: CharSchema[]) => {
700
+ const nameLength = tracker.reduce((p, c) => {
701
+ const length = c.children.reduce((pp, cc) => pp > cc.name.length + 1 ? pp : cc.name.length + 1, c.name.length);
702
+
703
+ return p > length ? p : length;
704
+ }, 0);
705
+
706
+ const hpLength = tracker.reduce((p, c) => {
707
+ const hpLength = c.maxhp.toString().length + 1 + c.hp.toString().length + (c.temphp > 1 ? c.temphp.toString().length + 1 : 0);
708
+
709
+ const length = c.children.reduce((pp, cc) => {
710
+ const hhpLength = cc.maxhp.toString().length + 1 + cc.hp.toString().length + (cc.temphp > 1 ? cc.temphp.toString().length + 1 : 0);
711
+
712
+ return pp > hhpLength ? pp : hhpLength;
713
+ }, hpLength);
714
+
715
+ return p > length ? p : length;
716
+ }, 0);
717
+
718
+ const stateMap: Record<CharSchema['state'], string> = {
719
+ empty: ':emptynode:',
720
+ arrow: ':arrownode:',
721
+ check: ':checknode:',
722
+ cross: ':crossnode:',
723
+ }
724
+
725
+ const hpMap = [
726
+ ':beaten:',
727
+ ':bruised:',
728
+ ':wounded:',
729
+ ':limping:',
730
+ ':dying:',
731
+ ].reverse();
732
+
733
+ const lines = tracker.sort((a, b) => (a.init === b.init) ? (b.foe ? 1 : 0) - (a.foe ? 1 : 0) : b.init - a.init).flatMap((char, index) => {
734
+ const ls: string[] = [];
735
+
736
+ if (tracker[index - 1] && tracker[index - 1].foe !== char.foe) {
737
+ ls.push('')
738
+ }
739
+
740
+ const hp = char.foe ? (
741
+ `-${char.maxhp - char.hp}${char.temphp > 0 ? `+${char.temphp}` : ''}`.padStart(hpLength)
742
+ ) : (
743
+ `${char.hp}${char.temphp > 0 ? `+${char.temphp}` : ''}/${char.maxhp}`.padStart(hpLength)
744
+ );
745
+
746
+ const hpBar = (value: number, max: number) => (
747
+ value <= 0 ? (
748
+ ':crossed:'
749
+ ) : value >= max ? (
750
+ ':healthy:'
751
+ ) : (
752
+ hpMap[Math.round((hpMap.length - 1) * value / max)]
753
+ )
754
+ );
755
+
756
+ ls.push((
757
+ `${stateMap[char.state]}**\` ${char.name.padEnd(nameLength)} ▏${char.alias.padEnd(3)} ▏${hp} \`** ${hpBar(char.hp, char.maxhp)} `
758
+ ))
759
+
760
+ if (char.conditions.length > 0) {
761
+ ls.push(`-# ${char.conditions}`)
762
+ }
763
+
764
+ for (const child of char.children) {
765
+ const hhp = char.foe ? (
766
+ `-${child.maxhp - child.hp}${child.temphp > 0 ? `+${child.temphp}` : ''}`.padStart(hpLength)
767
+ ) : (
768
+ `${child.hp}${child.temphp > 0 ? `+${child.temphp}` : ''}/${child.maxhp}`.padStart(hpLength)
769
+ );
770
+
771
+ ls.push((
772
+ `:smallnode:**\`- ${child.name.padEnd(nameLength - 1)} ▏${child.alias.padEnd(3)} ▏${hhp} \`** ${hpBar(child.hp, child.maxhp)} `
773
+ ))
774
+
775
+ if (child.conditions.length > 0) {
776
+ ls.push(`-# ${child.conditions}`)
777
+ }
778
+ }
779
+
780
+ return ls;
781
+ });
782
+
783
+ lines.push(':spacer:')
784
+
785
+ return lines.join('\n');
786
+ }
787
+
622
788
  export const sortFolder = async () => {
623
789
  const dir = await readdir('.');
624
790
 
package/src/index.ts CHANGED
@@ -3,10 +3,11 @@
3
3
  import { Command } from 'commander';
4
4
  import fs from 'fs/promises';
5
5
  import path from 'path';
6
+ import readline from 'readline';
6
7
 
7
8
  import pack from '../package.json';
8
9
 
9
- import { sortFolder, formatCommand, formatJSON, formatTSV, fromatMap, parseJSON, parseStatblock, stub, adjustmentMap, prediceateMap, formatTable, formatHP } from './app';
10
+ import { sortFolder, formatCommand, formatJSON, formatTSV, fromatMap, parseJSON, parseStatblock, stub, adjustmentMap, prediceateMap, formatTable, formatHP, newtracker, parseTracker, formatTracker } from './app';
10
11
 
11
12
  const program = new Command(pack.name);
12
13
 
@@ -147,5 +148,136 @@ program.command('command')
147
148
  await fs.writeFile(output, formatCommand(stats, option.secretDC, option.defaultSkills, option.recallDC), { encoding: 'utf-8' });
148
149
  });
149
150
 
151
+ program.command('newtracker')
152
+ .argument('<file>', 'input text file')
153
+ .option('-o, --output <file>', 'output file name')
154
+ .action(async (argument, option) => {
155
+ const file = await fs.readFile(argument, { encoding: 'utf-8' });
156
+
157
+ const tracker = newtracker(file);
158
+
159
+ if (option.output) {
160
+ await fs.writeFile(option.output, JSON.stringify(tracker, undefined, 2), { encoding: 'utf-8' })
161
+ } else {
162
+ console.log(JSON.stringify(tracker, undefined, 2));
163
+ }
164
+ });
165
+
166
+ program.command('tracker')
167
+ .argument('<file>', 'input text file')
168
+ .option('-o, --output <file>', 'output file name')
169
+ .action(async (argument, option) => {
170
+ const file = await fs.readFile(argument, { encoding: 'utf-8' });
171
+
172
+ const tracker = parseTracker(file);
173
+
174
+ if (option.output) {
175
+ await fs.writeFile(option.output, formatTracker(tracker), { encoding: 'utf-8' })
176
+ } else {
177
+ console.log(formatTracker(tracker));
178
+ }
179
+ });
180
+
181
+ program.command('track')
182
+ .argument('<file>', 'input text file')
183
+ .option('-o, --output <file>', 'output file name')
184
+ .action(async (argument, option) => {
185
+ const iterator = fs.watch(argument);
186
+
187
+ const invoke = async (filename: string) => {
188
+ const file = await fs.readFile(filename, { encoding: 'utf-8' });
189
+
190
+ const tracker = parseTracker(file);
191
+
192
+ if (option.output) {
193
+ await fs.writeFile(option.output, formatTracker(tracker), { encoding: 'utf-8' })
194
+ } else {
195
+ console.clear();
196
+ console.log(formatTracker(tracker));
197
+ }
198
+ }
199
+
200
+ await invoke(argument);
201
+
202
+ await Promise.all([
203
+ (async () => {
204
+ for await (const value of iterator) {
205
+ await invoke(value.filename ?? argument);
206
+ }
207
+ })(),
208
+ (async () => {
209
+ const rl = readline.createInterface({
210
+ input: process.stdin,
211
+ output: process.stdout
212
+ });
213
+
214
+ while (true) {
215
+ await new Promise((resolve) => setTimeout(resolve, 500));
216
+
217
+ const input = await new Promise<string>((resolve) => (
218
+ rl.question('>', resolve)
219
+ ));
220
+
221
+ const hpmod = input.match(/([^\d+-]+)\s*([+-]?\d+)/);
222
+
223
+ if (hpmod) {
224
+ const file = await fs.readFile(argument, { encoding: 'utf-8' });
225
+
226
+ const tracker = parseTracker(file);
227
+
228
+ const index = tracker.findIndex((c) => c.alias === hpmod[1])
229
+
230
+ if (index >= 0) {
231
+ let hp = tracker[index].hp;
232
+ let thp = tracker[index].temphp;
233
+
234
+ if (hpmod[2].match(/^[+-]/)) {
235
+ const delta = parseInt(hpmod[2])
236
+
237
+ if (delta < 0) {
238
+ thp += delta;
239
+ hp += Math.min(thp, 0)
240
+ } else {
241
+ hp += delta;
242
+ }
243
+ } else {
244
+ hp = parseInt(hpmod[2]);
245
+ }
246
+
247
+ tracker[index].temphp = Math.max(0, thp)
248
+ tracker[index].hp = Math.min(tracker[index].maxhp, Math.max(0, hp))
249
+
250
+ await fs.writeFile(argument, JSON.stringify(tracker, undefined, 2), { encoding: 'utf-8' })
251
+ }
252
+ }
253
+
254
+ const thpmod = input.match(/(\S+)\s+[tT]\s*([+-]?\d+)/);
255
+
256
+ if (thpmod) {
257
+ const file = await fs.readFile(argument, { encoding: 'utf-8' });
258
+
259
+ const tracker = parseTracker(file);
260
+
261
+ const index = tracker.findIndex((c) => c.alias === thpmod[1])
262
+
263
+ if (index >= 0) {
264
+ let thp = tracker[index].temphp;
265
+
266
+ if (thpmod[2].match(/^[+-]/)) {
267
+ thp += parseInt(thpmod[2]);
268
+ } else {
269
+ thp = parseInt(thpmod[2]);
270
+ }
271
+
272
+ tracker[index].temphp = Math.max(0, thp)
273
+
274
+ await fs.writeFile(argument, JSON.stringify(tracker, undefined, 2), { encoding: 'utf-8' })
275
+ }
276
+ }
277
+ }
278
+ })(),
279
+ ])
280
+ });
281
+
150
282
 
151
283
  program.parse();
package/status.txt ADDED
@@ -0,0 +1,5 @@
1
+ ## Status: Player Characters
2
+ Асотил (64/64 HP):
3
+ Брон (24/24 HP):
4
+ Нтаанди (20/20 HP):
5
+ Перитон (22/22 HP):
package/tracker.json ADDED
@@ -0,0 +1,62 @@
1
+ [
2
+ {
3
+ "init": 19,
4
+ "foe": false,
5
+ "state": "empty",
6
+ "name": "Перитон",
7
+ "alias": "pe",
8
+ "hp": 22,
9
+ "temphp": 0,
10
+ "maxhp": 22,
11
+ "conditions": "",
12
+ "children": []
13
+ },
14
+ {
15
+ "init": 21,
16
+ "foe": false,
17
+ "state": "empty",
18
+ "name": "Асотил",
19
+ "alias": "as",
20
+ "hp": 64,
21
+ "temphp": 0,
22
+ "maxhp": 64,
23
+ "conditions": "",
24
+ "children": []
25
+ },
26
+ {
27
+ "init": 19,
28
+ "foe": true,
29
+ "state": "empty",
30
+ "name": "Псина",
31
+ "alias": "hnd",
32
+ "hp": 70,
33
+ "temphp": 0,
34
+ "maxhp": 80,
35
+ "conditions": "-1 to AC and saves",
36
+ "children": []
37
+ },
38
+ {
39
+ "init": 18,
40
+ "foe": false,
41
+ "state": "empty",
42
+ "name": "Брон",
43
+ "alias": "br",
44
+ "hp": 24,
45
+ "temphp": 0,
46
+ "maxhp": 24,
47
+ "conditions": "hidden",
48
+ "children": []
49
+ },
50
+ {
51
+ "init": 25,
52
+ "foe": false,
53
+ "state": "arrow",
54
+ "name": "Нтаанди",
55
+ "alias": "nt",
56
+ "hp": 13,
57
+ "temphp": 0,
58
+ "maxhp": 20,
59
+ "conditions": "",
60
+ "children": []
61
+ }
62
+ ]