Skip to content

Commit 12df383

Browse files
committed
fix: Improves behavior of additive and reductive traits (close #456, close #421)
1 parent 6d14ce3 commit 12df383

File tree

4 files changed

+108
-137
lines changed

4 files changed

+108
-137
lines changed

src/layouts/layout.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const StatblockItemTypes = [
2020
"action"
2121
] as const;
2222

23-
export const TypeNames: Array<[(typeof StatblockItemTypes)[number], string]> = [
23+
export const TypeNames: Array<[(typeof StatblockItemTypes)[number] | null, string]> = [
2424
["group", "Group"],
2525
["inline", "Inline Group"],
2626
["ifelse", "If/Else"],

src/view/statblock.ts

Lines changed: 52 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ type RendererParameters = {
4545
);
4646

4747
export default class StatBlockRenderer extends MarkdownRenderChild {
48-
topBar: HTMLDivElement;
49-
bottomBar: HTMLDivElement;
48+
topBar!: HTMLDivElement;
49+
bottomBar!: HTMLDivElement;
5050
loaded: boolean = false;
51-
statblockEl: HTMLDivElement;
52-
contentEl: HTMLDivElement;
51+
statblockEl!: HTMLDivElement;
52+
contentEl!: HTMLDivElement;
5353
container: HTMLElement;
54-
monster: Monster;
54+
monster!: Monster;
5555
plugin: StatBlockPlugin;
56-
params: Partial<StatblockParameters>;
56+
params!: Partial<StatblockParameters>;
5757
context: string;
58-
layout: Layout;
58+
layout!: Layout;
5959
constructor(
6060
public rendererParameters: RendererParameters,
6161
public icons = true
@@ -166,86 +166,57 @@ export default class StatBlockRenderer extends MarkdownRenderChild {
166166
}
167167
switch (block.type) {
168168
case "traits": {
169+
/**
170+
* Traits can be defined directly, as additive (+) or subtractive (-).
171+
*
172+
* Directly defined traits can be overidden by name up the extension tree.
173+
* Parameters > `creature` > `extends`
174+
* Directly defined parameter traits are *always shown*.
175+
*
176+
* Additive traits are *always* displayed, no matter where they originate.
177+
*
178+
* Subtractive traits are *always* removed, unless the trait is directly defined in the parameters.
179+
* Subtractive traits only work on directly defined traits.
180+
*
181+
*/
169182
const $TRAIT_MAP: Map<string, Trait> = new Map();
170-
let $ADDITIVE_TRAITS: Trait[] = [];
171-
let $DELETE_TRAITS: Set<string> = new Set();
172-
/** Add traits from the extensions group first. */
173-
for (const extension of extensions) {
174-
let traits = getTraitsList(property, extension);
175-
for (const trait of traits) {
176-
$TRAIT_MAP.set(trait.name, trait);
177-
}
183+
const $ADDITIVE_TRAITS: Trait[] = [];
178184

179-
traits = getTraitsList(
180-
`${property}+` as keyof Monster,
181-
extension
182-
);
183-
for (const trait of traits) {
184-
$ADDITIVE_TRAITS.push(trait);
185-
}
186-
traits = getTraitsList(
185+
/**
186+
* Resolve extension traits first.
187+
*/
188+
for (const creature of [...extensions]) {
189+
/**
190+
* Deleted traits. These are always removed.
191+
*/
192+
for (const trait of getTraitsList(
187193
`${property}-` as keyof Monster,
188-
extension
189-
);
190-
for (const trait of traits) {
191-
$DELETE_TRAITS.add(trait.name);
192-
}
193-
}
194-
//next, underlying monster object
195-
let traits = getTraitsList(property, built);
196-
for (const trait of traits) {
197-
if (!(property in this.params)) {
194+
creature
195+
)) {
198196
$TRAIT_MAP.delete(trait.name);
199-
$ADDITIVE_TRAITS.push(trait);
200-
} else {
197+
}
198+
/**
199+
* Directly defined traits.
200+
*
201+
* Because these can be overridden, they go into a map by name.
202+
*/
203+
for (const trait of getTraitsList(
204+
property,
205+
creature
206+
)) {
201207
$TRAIT_MAP.set(trait.name, trait);
202208
}
203-
}
204-
traits = getTraitsList(
205-
`${property}+` as keyof Monster,
206-
built
207-
);
208-
for (const trait of traits) {
209-
$ADDITIVE_TRAITS.push(trait);
210-
}
211-
traits = getTraitsList(
212-
`${property}-` as keyof Monster,
213-
built
214-
);
215-
for (const trait of traits) {
216-
$DELETE_TRAITS.add(trait.name);
217-
}
218-
219-
/** Remove these traits first, so you don't get hit by the params */
220-
traits = getTraitsList(
221-
`${property}-` as keyof Monster,
222-
this.params
223-
);
224-
for (const trait of traits) {
225-
$DELETE_TRAITS.add(trait.name);
226-
}
227-
for (const trait of $DELETE_TRAITS) {
228-
$TRAIT_MAP.delete(trait);
229-
$ADDITIVE_TRAITS = $ADDITIVE_TRAITS.filter(
230-
(t) => t.name !== trait
231-
);
232-
}
233-
//finally, the parameters should always be added
234-
traits = getTraitsList(property, this.params);
235-
for (const trait of traits) {
236-
$TRAIT_MAP.delete(trait.name);
237-
$ADDITIVE_TRAITS.push(trait);
238-
}
239-
240-
traits = getTraitsList(
241-
`${property}+` as keyof Monster,
242-
this.params
243-
);
244209

245-
for (const trait of traits) {
246-
$ADDITIVE_TRAITS.push(trait);
210+
/**
211+
* Additive traits. These traits are always shown.
212+
*/
213+
for (const trait of getTraitsList(
214+
`${property}+` as keyof Monster,
215+
creature
216+
)) {
217+
$ADDITIVE_TRAITS.push(trait);
218+
}
247219
}
248-
249220
Object.assign(built, {
250221
[property]: [
251222
...$TRAIT_MAP.values(),
@@ -398,7 +369,7 @@ export default class StatBlockRenderer extends MarkdownRenderChild {
398369
}
399370
}
400371

401-
$ui: Statblock;
372+
$ui!: Statblock;
402373
async init() {
403374
this.containerEl.empty();
404375
this.monster = (await this.build()) as Monster;
@@ -434,7 +405,7 @@ export default class StatBlockRenderer extends MarkdownRenderChild {
434405
this.$ui.$on("export", () => {
435406
this.plugin.exportAsPng(
436407
this.monster.name,
437-
this.containerEl.firstElementChild
408+
this.containerEl.firstElementChild!
438409
);
439410
});
440411

src/view/ui/ColumnContainer.svelte

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
container: target,
130130
classes: item.cls
131131
? [...(classes ?? []), item.cls]
132-
: classes ?? []
132+
: (classes ?? [])
133133
});
134134
135135
targets.push(...element);
@@ -231,7 +231,7 @@
231231
"statblock-item-inline",
232232
...(item.cls
233233
? [...(classes ?? []), item.cls]
234-
: classes ?? [])
234+
: (classes ?? []))
235235
]
236236
});
237237
for (const nested of item.nested ?? []) {
@@ -266,7 +266,7 @@
266266
type: "group",
267267
nested: layout.blocks,
268268
id: item.layout,
269-
properties: null
269+
properties: []
270270
},
271271
{
272272
classes: [
@@ -306,58 +306,55 @@
306306
item.properties[0]
307307
] as Spell[];
308308
309-
if (!Array.isArray(blocks) || !blocks.length) return;
310-
let spellBlocks: Array<SpellBlock> = blocks.reduce(
311-
(acc, current) => {
312-
if (
313-
typeof current === "string" &&
314-
(current.charAt(current.length - 1) == ":" ||
315-
!current.includes(":"))
316-
) {
317-
const newBlock: SpellBlock = {
318-
header: ensureColon(current),
319-
spells: []
320-
};
321-
acc.push(newBlock);
322-
return acc;
323-
}
324-
const lastBlock: SpellBlock = acc[acc.length - 1];
325-
let spell: Spell;
326-
if (typeof current == "string") {
309+
if (!Array.isArray(blocks) || !blocks.length) return [];
310+
let spellBlocks: Array<SpellBlock> = blocks.reduce<
311+
SpellBlock[]
312+
>((acc, current) => {
313+
if (
314+
typeof current === "string" &&
315+
(current.charAt(current.length - 1) == ":" ||
316+
!current.includes(":"))
317+
) {
318+
const newBlock: SpellBlock = {
319+
header: ensureColon(current),
320+
spells: []
321+
};
322+
acc.push(newBlock);
323+
return acc;
324+
}
325+
const lastBlock: SpellBlock = acc[acc.length - 1];
326+
let spell: Spell;
327+
if (typeof current == "string") {
328+
spell = {
329+
spells: Linkifier.linkifySpells(
330+
current,
331+
context.get("context") as string
332+
)
333+
};
334+
} else {
335+
try {
327336
spell = {
337+
level: Object.keys(current).shift(),
328338
spells: Linkifier.linkifySpells(
329-
current,
339+
stringify(Object.values(current).shift()!),
330340
context.get("context") as string
331341
)
332342
};
333-
} else {
334-
try {
335-
spell = {
336-
level: Object.keys(current).shift(),
337-
spells: Linkifier.linkifySpells(
338-
stringify(
339-
Object.values(current).shift()
340-
),
341-
context.get("context") as string
342-
)
343-
};
344-
} catch (e) {
345-
return acc;
346-
}
347-
}
348-
if (lastBlock) {
349-
lastBlock.spells.push(spell);
350-
} else {
351-
const missingHeaderBlock: SpellBlock = {
352-
header: `${monster.name} knows the following spells:`,
353-
spells: [spell]
354-
};
355-
acc.push(missingHeaderBlock);
343+
} catch (e) {
344+
return acc;
356345
}
357-
return acc;
358-
},
359-
[]
360-
);
346+
}
347+
if (lastBlock) {
348+
lastBlock.spells.push(spell);
349+
} else {
350+
const missingHeaderBlock: SpellBlock = {
351+
header: `${monster.name} knows the following spells:`,
352+
spells: [spell]
353+
};
354+
acc.push(missingHeaderBlock);
355+
}
356+
return acc;
357+
}, []);
361358
362359
for (
363360
let blockIndex = 0;
@@ -371,7 +368,7 @@
371368
props: {
372369
name:
373370
blockIndex == 0
374-
? item.heading ?? "Spellcasting"
371+
? (item.heading ?? "Spellcasting")
375372
: "",
376373
property: item.properties[0],
377374
desc: block.header,
@@ -515,10 +512,12 @@
515512
}
516513
}
517514
} catch (e) {
515+
console.error(e);
518516
return [];
519517
}
520518
break;
521519
}
520+
522521
}
523522
if ("hasRule" in item && item.hasRule) {
524523
const rule = createDiv(
@@ -535,7 +534,7 @@
535534
return targets.filter((el) => el.hasChildNodes());
536535
};
537536
$: maxHeight =
538-
!isNaN(Number(monster.columnHeight)) && monster.columnHeight > 0
537+
!isNaN(Number(monster.columnHeight)) && monster.columnHeight! > 0
539538
? monster.columnHeight
540539
: Infinity;
541540
@@ -582,7 +581,7 @@
582581
}
583582
});
584583
contentContainer.$on("built", () => {
585-
const columnEl = temp.querySelector(".column");
584+
const columnEl = temp.querySelector(".column")!;
586585
for (let target of targets) {
587586
heights.push(target.scrollHeight);
588587
}
@@ -597,7 +596,7 @@
597596
} else {
598597
split = Math.max(
599598
600,
600-
Math.min(columnEl.scrollHeight / columns, maxHeight)
599+
Math.min(columnEl.scrollHeight / columns, maxHeight!)
601600
);
602601
}
603602

src/view/ui/MarkdownHolder.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import { Linkifier } from "src/parser/linkify";
1010
import { parseForDice } from "src/parser/dice-parsing";
1111
import type { Writable } from "svelte/store";
12+
import { stringify } from "src/util/util";
1213
1314
export let property: string;
1415
@@ -38,7 +39,7 @@
3839
) {
3940
split = [{ text: monster[item.diceProperty] as string }];
4041
} else {
41-
const parsed = parseForDice(layout, property, monster);
42+
const parsed = parseForDice(layout, stringify(property), monster);
4243
if (Array.isArray(parsed)) {
4344
split = parsed;
4445
} else {
@@ -62,7 +63,7 @@
6263
new Notice(
6364
`There was an error executing the provided dice callback for [${item.properties.join(
6465
", "
65-
)}]\n\n${e.message}`
66+
)}]\n\n${(e as Error).message}`
6667
);
6768
console.error(e);
6869
}

0 commit comments

Comments
 (0)