let current = dv.current();let title = current.name || current.file.name;// --- CONFIGURATION ---// Set to your silhouette image path or filename (e.g., "npc-images/silhouette.webp" or "silhouette.webp").// If the file doesn't exist or is set to "", it will gracefully display the text message below.let fallbackImage = "npc-images/silhouette.webp"; // ---------------------// 1. Gather all potential names (Title, quoted nicknames, and Obsidian Aliases)let rawNames = [title];// Extract quoted nickname if present in title (e.g., Wulfric "The Bear" -> The Bear)let matchNick = title.match(/"([^"]+)"/);if (matchNick && matchNick) { rawNames.push(matchNick);}// Safely pull aliases from Obsidian Frontmatter/Propertieslet rawAliases = current.file?.aliases || current.aliases || [];if (typeof rawAliases === "string") { rawNames.push(rawAliases);} else if (rawAliases && typeof rawAliases[Symbol.iterator] === "function") { for (let a of rawAliases) { if (a && typeof a === "string") rawNames.push(a); }}// 2. Build sanitized search terms (ensures terms are >= 2 chars to avoid matching "_" or empty strings)let searchTerms = new Set();for (let name of rawNames) { if (!name || typeof name !== "string") continue; let lower = name.toLowerCase().trim().replace(/["']/g, ""); let slug = lower.replace(/[\s-]+/g, "_").replace(/^_+|_+\$/g, ""); if (lower.length >= 2) searchTerms.add(lower); if (slug.length >= 2) searchTerms.add(slug);}// 3. Search npc-images for matching character fileslet matches = app.vault.getFiles().filter(f => { let pathLower = f.path.toLowerCase(); if (!pathLower.includes("npc-images")) return false; // Exclude the fallback image from character search if (fallbackImage && pathLower.includes(fallbackImage.toLowerCase())) return false; let imgName = f.name.toLowerCase(); return Array.from(searchTerms).some(term => term && imgName.includes(term));});// 4. Prioritize files with "avatar" in the name, otherwise take first matchlet match = matches.find(f => f.name.toLowerCase().includes("avatar")) || (matches.length > 0 ? matches : null);// 5. Render artwork, fallback image, or clean text messageif (match) { dv.paragraph(`![[${match.path}|cover]]`);} else { // Flexible fallback image lookup (works with filename, full path, or partial match) let fallbackFile = null; if (fallbackImage && fallbackImage.trim() !== "") { let cleanFB = fallbackImage.toLowerCase().trim(); fallbackFile = app.vault.getFiles().find(f => f.path.toLowerCase() === cleanFB || f.path.toLowerCase().includes(cleanFB) || f.name.toLowerCase().includes(cleanFB) ); } if (fallbackFile) { dv.paragraph(`![[${fallbackFile.path}|cover]]`); } else { dv.paragraph(`*No artwork generated yet for ${title}*`); }}
// 5. Render artwork or fallback
if (match) {
dv.paragraph(![[${match.path}|cover]]);
} else {
let fallbackFile = fallbackImage ? app.vault.getAbstractFileByPath(fallbackImage) : null;
if (fallbackFile) {
dv.paragraph(![[${fallbackFile.path}|cover]]);
} else {
dv.paragraph(*No artwork generated yet for ${title}*);
}
}
### Character Bio
A seneschal for [[Great House Korunda]]. He paid the party 500gp to find his missing sister, [[Jaslin Farlight|Jaslin]], for himself whilst also hiring them to counter the [[Illithid]] and [[Dread Ethel|Dread Ethel]] for his master.
Appearance & Personality
Visuals & Mannerisms
let current = dv.current();let title = current.name || current.file.name;// --- CONFIGURATION ---// Set to your silhouette image path or filename (e.g., "npc-images/silhouette.webp" or "silhouette.webp").// If the file doesn't exist or is set to "", it will gracefully display the text message below.let fallbackImage = "npc-images/silhouette.webp"; // ---------------------// 1. Gather all potential names (Title, quoted nicknames, and Obsidian Aliases)let rawNames = [title];// Extract quoted nickname if present in title (e.g., Wulfric "The Bear" -> The Bear)let matchNick = title.match(/"([^"]+)"/);if (matchNick && matchNick) { rawNames.push(matchNick);}// Safely pull aliases from Obsidian Frontmatter/Propertieslet rawAliases = current.file?.aliases || current.aliases || [];if (typeof rawAliases === "string") { rawNames.push(rawAliases);} else if (rawAliases && typeof rawAliases[Symbol.iterator] === "function") { for (let a of rawAliases) { if (a && typeof a === "string") rawNames.push(a); }}// 2. Build sanitized search terms (ensures terms are >= 2 chars to avoid matching "_" or empty strings)let searchTerms = new Set();for (let name of rawNames) { if (!name || typeof name !== "string") continue; let lower = name.toLowerCase().trim().replace(/["']/g, ""); let slug = lower.replace(/[\s-]+/g, "_").replace(/^_+|_+\$/g, ""); if (lower.length >= 2) searchTerms.add(lower); if (slug.length >= 2) searchTerms.add(slug);}// 3. Search npc-images for matching character fileslet matches = app.vault.getFiles().filter(f => { let pathLower = f.path.toLowerCase(); if (!pathLower.includes("npc-images")) return false; // Exclude the fallback image from character search if (fallbackImage && pathLower.includes(fallbackImage.toLowerCase())) return false; let imgName = f.name.toLowerCase(); return Array.from(searchTerms).some(term => term && imgName.includes(term));});// 4. Prioritize files with "avatar" in the name, otherwise take first matchlet match = matches.find(f => f.name.toLowerCase().includes("avatar")) || (matches.length > 0 ? matches : null);// 5. Render artwork, fallback image, or clean text messageif (match) { dv.paragraph(`![[${match.path}|cover]]`);} else { // Flexible fallback image lookup (works with filename, full path, or partial match) let fallbackFile = null; if (fallbackImage && fallbackImage.trim() !== "") { let cleanFB = fallbackImage.toLowerCase().trim(); fallbackFile = app.vault.getFiles().find(f => f.path.toLowerCase() === cleanFB || f.path.toLowerCase().includes(cleanFB) || f.name.toLowerCase().includes(cleanFB) ); } if (fallbackFile) { dv.paragraph(`![[${fallbackFile.path}|cover]]`); } else { dv.paragraph(`*No artwork generated yet for ${title}*`); }}
// 5. Render artwork or fallback
if (match) {
dv.paragraph(![[${match.path}|cover]]);
} else {
let fallbackFile = fallbackImage ? app.vault.getAbstractFileByPath(fallbackImage) : null;
if (fallbackFile) {
dv.paragraph(![[${fallbackFile.path}|cover]]);
} else {
dv.paragraph(*No artwork generated yet for ${title}*);
}
}
- **Mannerisms:**
Recent Activity
This section displays every time this entity is mentioned in session summaries.
The Party then travelled to Chevid, capital of Korunda Prefecture. Whilst there they spoke with Fain Farlight, seneschal for Great House Korunda. The party passed on their news whilst also receiving word from Fain. Fain confirmed the rumours that the city had been having regular nightmares that were debilitating and suspected to be supernatural in origin. Fain had been authorised to reward anyone who assisted in resolving this. Fain advised that Nira Crakehall was a researcher associated with Great House Lyran who was visiting to assist. Crakehall had the service of a sarcastic quickling familiar named Flit. On a more personal note Fain contracted the party to investigate the disappearance of his sister, Jaslin Farlight. Jaslin worked in the library provided to the people by Korunda. After visiting the library and speaking with Crakehall the party left to go to Jaslin’s home.
The party reported to Fain with Jaslin in tow. Upon mention of the Illithid involvement Fain immediately triggered a message to others outside of his office. Fain stated that whilst he loved his sister there were protocols to follow when there was any suggestion of Illithid involvement, fearing any potential new Desolation.
Fain brought the party to the Lord of Great House Korunda, Rurisk Korunda. Lord Rurisk stated that should the party deal with this, preferably destroying the Illithid, then they would be rewarded greatly. Lord Rurisk stated that he would be sending his personal bodyguards to investigate and deal with this also.
Fain Farlight
Medium Humanoid (Elf), Neutral
Armor Class 15
Hit Points 56 (16d6)
Speed 30 ft.
Initiative +2 (12)
STR
DEX
CON
INT
WIS
CHA
11 (+0)
15 (+2)
10 (+0)
16 (+3)
12 (+1)
11 (+0)
Saving Throws Wis +3
Skills Arcana +5, Sleight of Hand +4
Senses Darkvision 60 ft., Passive Perception 11
Languages Common, Dwarvish, Elvish, Sylvan, Primordial
Challenge 3 (XP 700; PB +2)
Proficiency Bonus +2
Words of Reason. Allies in a 10-foot Emanation originating from Fain Farlight have Advantage on saving throws to avoid or end the Charmed or Frightened condition. This trait doesn’t function if Fain Farlight has the Incapacitated condition.
Actions
Multiattack. Fain Farlight makes two Bursting Sigil attacks.
Bursting Sigil.Melee or Ranged Attack Roll: +5, reach 5 ft. or range 60 ft. Hit: 13 (3d6 + 3) Radiant damage.
Spellcasting. Fain Farlight casts one of the following spells, requiring no Material components and using Intelligence as the spellcasting ability (DC 13):
At will: Comprehend Languages, Mage Armor (included in AC), Message
1/day: Sending
Reactions
Word of Stasis (Recharge 5–6).Constitution Saving Throw: DC 13, each creature in a 20-foot-radius Sphere centered on a point Fain Farlight can see within 60 feet. Failure: The target has the Incapacitated condition until the start of Fain Farlight’s next turn. While the target is Incapacitated, its Speed is 0 and can’t increase.