Skip to content

Commit

Permalink
feat: adds extends parameter (close #111)
Browse files Browse the repository at this point in the history
  • Loading branch information
valentine195 committed Nov 23, 2022
1 parent 7452dac commit 8e8c63e
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 41 deletions.
8 changes: 6 additions & 2 deletions @types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ export interface Monster {
legendary_actions?: Trait[];
reactions?: Trait[];
lair_actions?: Trait[];
monster?: string;
creature?: string;
source?: string | string[];
spellsNotes?: string;

Expand Down Expand Up @@ -84,6 +82,12 @@ export interface Monster {

note?: string;
mtime?: number;

/* Extensions */

monster?: string;
creature?: string;
extends?: string | string[];
}

export interface StatblockParameters
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,30 @@ name: Paarthurnax

<img src="https://raw.githubusercontent.com/valentine195/obsidian-5e-statblocks/beta/images/override.PNG">

## Extending

The `extends` key can be used to extend *extend* an existing creature, similar to the `monster` key shown in Overriding Fields. The difference is the resulting monster is not combined; it maintains a link to the base creature. This allows you to, for example, create a named version of a Goblin; any changes to the base Goblin will propagate to the extension.

This key supports a single creature name or an array of creatures. Fields from creatures specified later will take precedence, with any fields directly defined in the statblock taking final precedence.

This field is also fully recursive; extending a creature that extends another creature will cascade these extensions all the way through.

````
```statblock
name: Paarthurnax
extends: Ancient Black Dragon
```
````

````
```statblock
name: Extended Paarthurnax
extends:
- Paarthurnax
- Goblin
```
````

## Traits

Traits, as well as Actions, Reactions and Legendary Actions, should be added by specifying a name and description (desc):
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type SpellsProps = {
};
type SubHeadingProps = {
type: "subheading";
separator: string;
separator?: string;
};
type TableProps = {
type: "table";
Expand Down
62 changes: 28 additions & 34 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
SAVE_ICON,
SAVE_SYMBOL
} from "./data/constants";
import type { Monster, StatblockParameters } from "@types";
import type { Monster, StatblockParameters, Trait } from "@types";
import StatblockSettingTab from "./settings/settings";
import fastCopy from "fast-copy";

Expand Down Expand Up @@ -477,35 +477,24 @@ export default class StatBlockPlugin extends Plugin {
this.bestiary.get(params.monster) ??
this.bestiary.get(params.creature)
);
//TODO: The traits are breaking because it expects { name, desc }, not array.
if (monster) {

let layout =
this.settings.layouts.find(
(layout) =>
layout.name == (params.layout ?? monster.layout) ||
layout.name == (params.statblock ?? monster.statblock)
) ?? this.defaultLayout;

let TraitBlocks = layout.blocks
.filter((b) => b.type == "traits")
.flatMap((p) => p.properties);
for (const trait of TraitBlocks) {
let traits = transformTraits(
monster.traits ?? [],
params.traits ?? []
(monster[trait] as Trait[]) ?? [],
(params[trait] as Trait[]) ?? []
);
let actions = transformTraits(
monster.actions ?? [],
params.actions ?? []
);
let bonus_actions = transformTraits(
monster.bonus_actions ?? [],
params.bonus_actions ?? []
);
let legendary_actions = transformTraits(
monster.legendary_actions ?? [],
params.legendary_actions ?? []
);
let reactions = transformTraits(
monster.reactions ?? [],
params.reactions ?? []
);

Object.assign(params, {
traits,
actions,
bonus_actions,
reactions,
legendary_actions
[trait]: traits
});
}

Expand Down Expand Up @@ -533,19 +522,24 @@ export default class StatBlockPlugin extends Plugin {
Object.fromEntries([a])
);
}

//combine extensions

if (
params.extends &&
params.extends.length &&
monster.extends &&
monster.extends.length
) {
params.extends = [monster.extends, params.extends].flat();
}

const toBuild: Monster = Object.assign(
{},
monster ?? {},
params ?? {}
);

let layout =
this.settings.layouts.find(
(layout) =>
layout.name == toBuild?.layout ||
layout.name == toBuild?.statblock
) ?? this.defaultLayout;

el.addClass("statblock-plugin-container");
el.parentElement?.addClass("statblock-plugin-parent");
const toBuildWithLinksReplaced = JSON.parse(
Expand Down
2 changes: 2 additions & 0 deletions src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ export function transformTraits(
paramsTraits: { desc: string; name: string }[] | [string, string][] = []
) {
if (!monsterTraits) monsterTraits = [];
if (!Array.isArray(monsterTraits)) monsterTraits = [monsterTraits];
if (!paramsTraits) paramsTraits = [];
if (!Array.isArray(paramsTraits)) paramsTraits = [paramsTraits];
for (const trait of paramsTraits ?? []) {
if (!trait) continue;
if (Array.isArray(trait)) {
Expand Down
55 changes: 51 additions & 4 deletions src/view/statblock.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { App, ButtonComponent, Modal } from "obsidian";
import { Layout5e } from "src/layouts/basic5e";
import { MarkdownRenderChild } from "obsidian";
import type { Monster } from "@types";
import type { Monster, Trait } from "@types";

import Statblock from "./Statblock.svelte";
import type StatBlockPlugin from "src/main";

import fastCopy from "fast-copy";
import type { Layout } from "src/layouts/types";
import { transformTraits } from "src/util/util";

export default class StatBlockRenderer extends MarkdownRenderChild {
topBar: HTMLDivElement;
Expand All @@ -18,18 +19,18 @@ export default class StatBlockRenderer extends MarkdownRenderChild {
constructor(
container: HTMLElement,
monster: Monster,
plugin: StatBlockPlugin,
public plugin: StatBlockPlugin,
canSave: boolean,
context: string,
layout: Layout = Layout5e
public layout: Layout = Layout5e
) {
super(container);

const statblock = new Statblock({
target: this.containerEl,
props: {
context,
monster,
monster: this.transform(monster),
statblock: layout.blocks,
layout: layout.name,
plugin,
Expand Down Expand Up @@ -60,6 +61,52 @@ export default class StatBlockRenderer extends MarkdownRenderChild {
);
});
}
transform(monster: Monster): Monster {
if (
!("extends" in monster) ||
!(
Array.isArray(monster.extends) ||
typeof monster.extends == "string"
) ||
!monster.extends.length
) {
return monster;
}

const extensions = this.getExtensions(monster);

let TraitBlocks = this.layout.blocks
.filter((b) => b.type == "traits")
.flatMap((p) => p.properties);
const traitsHolder = {};
for (const trait of TraitBlocks) {
let traitArray: Trait[] = [];
for (const m of [...extensions]) {
traitArray = transformTraits(
traitArray,
(m[trait] as Trait[]) ?? []
);
}
Object.assign(traitsHolder, {
[trait]: traitArray
});
}
const ret = Object.assign({}, ...extensions, monster, traitsHolder);

return ret;
}
getExtensions(monster: Monster): Monster[] {
let extensions: Monster[] = [fastCopy(monster)];
if (monster.extends && monster.extends.length) {
for (const extension of [monster.extends].flat()) {
const extensionMonster = this.plugin.bestiary.get(extension);
if (!extensionMonster) continue;
extensions.push(...this.getExtensions(extensionMonster));
}
}

return extensions;
}
}

export async function confirmWithModal(
Expand Down
1 change: 1 addition & 0 deletions src/view/ui/Content.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
}
case "spells": {
const blocks: Trait[] = monster[item.properties[0]] as Trait[];
if (!Array.isArray(blocks) || !blocks.length) return;
new Spells({
Expand Down

0 comments on commit 8e8c63e

Please sign in to comment.