Skip to content

Instantly share code, notes, and snippets.

@Restioson
Last active May 4, 2024 20:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Restioson/e5d6f9a981ec77ce7f8634197fdc8f4e to your computer and use it in GitHub Desktop.
Save Restioson/e5d6f9a981ec77ce7f8634197fdc8f4e to your computer and use it in GitHub Desktop.
[Obsidian] Search through your Fantasty Statblocks bestiary by CR/type

Monster Search

Search through your Fantasty Statblocks bestiary by monster CR and type, using Dataview!

image

How to add

  1. Make sure that Meta-Bind, Fantasy Statblocks, Dataview and is installed
  2. Make sure Dataview JS queries are turned on
  3. Paste the code from monster_search.js inside a dataviewjs code block (triple `s)
  4. Add the following properties to the note's frontmatter. They shouldn't need any values to be given.
    • searching_cr
    • searching_type
    • searching_open_bestiary
  5. Happy searching!

Options and usage

The creatures are returned in a table with their name, HP, AC, CR, and source, in no particular order. If there is a note with the same name as a creature, the name column will link to that note. If there isn't one, upon clicking it, a Fantasy Statblocks bestiary panel will be opened and it will be shown there. If there was already one open, it will just show it there.

The filters described below work like an 'AND' - if you search for CR 4 and Undead, you will only get CR4 undead creatures.

Filtering by CR

Type the CR of the creature in the 'cr' search box, e.g 1/8, 1/4, 1/2, or a whole number like 5. This will filter the results to creatures of that CR only. If no creatures match that CR, it will find the closest CR to the given number (either up or down, whichever is closer) which do contain monsters, and return those.

Filtering by type

Select the type of the creature in the 'type' select. Only creatures of this type will be returned.

Selecting a random creature

By default, one creature from the results is selected at random, to let you quickly choose a random creature from the list for inspiration. Its name will be listed in bold, and appear as > Name < (between ><). If you want to get a new creature from the same list, you can click the 'Select random' button to get a new one. If you don't care about the random selection, you can just ignore it.

Opening bestiary panel

If 'open bestiary panel' is toggled on, whenever a search is made, the creature randomly selected will be shown in a Fantasy Statblocks bestiary panel. One will be opened if there isn't one open already, and the right bar will always be shown if it was hidden. If toggled off, this won't happen.

// made with <3 by Restioson, licensed Apache 2.0 (cred to obsidianttrpg.com for inspiration)
const statblocks = app.plugins.getPlugin("obsidian-5e-statblocks");
const bestiary = statblocks.bestiary;
const cr = dv.current().searching_cr;
const type = dv.current().searching_type;
const open_bestiary = dv.current().searching_open_bestiary;
function parseCR(crStr) {
switch (crStr) {
case '1/8':
return 0.125;
case '1/4':
return 0.25;
case '1/2':
return 0.5;
default:
return parseInt(crStr);
}
}
var beasts = Array.from(bestiary.values())
.filter((i) => {
var include = true;
if (cr) {
include &= parseCR(i.cr) === parseCR(cr);
}
if (type !== "Any") {
const i_typ = i.type || ""; // handle null
include &= i_typ.toLowerCase() ===
type.toLowerCase();
}
return include;
})
if (beasts.length === 0) {
dv.paragraph(`No beasts found with CR '${cr}' and type ${type}. Showing beast of closest CR.`)
var targetCR = parseCR(cr);
var closestCR = null;
for (const beast of bestiary.values()) {
const beast_type = beast.type || ""; // handle null
if (type !== "Any" &&
beast_type.toLowerCase() !== type.toLowerCase()
) {
continue;
}
const iCR = parseCR(beast.cr);
if (closestCR == null || closestCR === iCR) {
closestCR = iCR;
beasts.push(beast);
} else if (Math.abs(targetCR - iCR) < Math.abs(targetCR - closestCR)) {
closestCR = iCR;
beasts = [beast];
}
}
}
beasts = dv.array(beasts);
function reroll(dataview) {
dataview.component.render();
}
async function showBeast(beast, force_open) {
if (!open_bestiary && !force_open) {
return;
}
if (!statblocks.creature_view) {
await app.commands.executeCommandById("obsidian-5e-statblocks:open-creature-view")
setTimeout(() => statblocks.creature_view.render(beast), 250)
} else {
statblocks.creature_view.render(beast)
if (open_bestiary || force_open) {
app.workspace.revealLeaf(statblocks.creature_view.leaf)
}
}
}
const button = dv.el("button", "Select random");
button.addEventListener("click", () => reroll(this));
button.style = "margin-right: 10px; font-size: 0.9em;";
dv.span("CR: ")
dv.span("`INPUT[text:searching_cr]`")
.querySelector("input")
.addEventListener("change", () => {
app.workspace.trigger("dataview:refresh-views");
});
const monster_types = [
"Any",
"Aberration",
"Beast",
"Celestial",
"Construct",
"Dragon",
"Elemental",
"Fey",
"Fiend",
"Giant",
"Humanoid",
"Monstrosity",
"Ooze",
"Plant",
"Swarm of tiny beasts",
"Undead"
];
const opts = monster_types
.map((type) => `option(${type})`)
.join(',');
dv.span("Type: ")
dv.span(`\`INPUT[inlineSelect(${opts}):searching_type]\``)
.querySelector("select")
.addEventListener("change", () => {
setTimeout(() => {
app.workspace.trigger("dataview:refresh-views")
}, 250);
});
dv.span("Open bestiary panel `INPUT[toggle:searching_open_bestiary]`\n")
const chosen = beasts[Math.floor(Math.random() * beasts.length)];
await showBeast(chosen, false);
dv.table(["Name", "HP", "AC", "CR", "Source"], beasts.map((monster) => {
var link;
if (!dv.page(monster.name)) {
link = document.createElement("a");
if (chosen.name === monster.name) {
const b = document.createElement("strong");
b.innerText = `> ${monster.name} <`;
link.appendChild(b);
} else {
link.innerText = monster.name;
}
link.addEventListener("click", () => showBeast(monster, true));
} else {
link = monster.name === chosen.name ? `**[[${monster.name}|> ${monster.name} <]]**` : dv.fileLink(monster.name);
}
return [link, monster.hp, monster.ac, monster.cr, monster.source];
}));
@ASingleMind
Copy link

Hello,

this code can't find the bestiary under the Fantasy Statblock plugin, which stored on github and accessed through the main.js in the statblock plugin folder.

statblock search

@Restioson
Copy link
Author

Restioson commented May 4, 2024

@ASingleMind hey! I'm not sure exactly how your setup works. Here are the lines (lines 1-2 of the source code) which get the bestiary:

const statblocks = app.plugins.getPlugin("obsidian-5e-statblocks");
const bestiary = statblocks.bestiary;

If you need to get it some other way, these are the lines that you'd want to change.

Can you explain more about your setup? I'm not sure how you've installed the Fantasty Statblocks plugin?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment