Files
npc-portrait-pack/scripts/npc-portrait-pack.mjs
T
2026-03-31 17:00:15 +01:00

139 lines
5.8 KiB
JavaScript

// npc-portrait-pack.mjs — Main entry point
// Foundry VTT module: NPC Portrait Pack + Generator (SLA Industries edition)
import { PortraitManager } from './portrait-manager.mjs';
import { registerSettings, MODULE_ID } from './settings.mjs';
import { PortraitPickerDialog } from './portrait-dialog.mjs';
import { NpcGeneratorDialog } from './npc-generator.mjs';
// ---------------------------------------------------------------------------
// init — register settings before anything else
// ---------------------------------------------------------------------------
Hooks.once('init', () => {
registerSettings();
console.log('NPC Portrait Pack | Initialised');
});
// ---------------------------------------------------------------------------
// ready — load portrait index, expose public API
// ---------------------------------------------------------------------------
Hooks.once('ready', async () => {
const manager = new PortraitManager();
await manager.loadIndex();
game.npcPortraitPack = {
getRandomPortrait: (opts = {}) => manager.getRandomPortrait(opts),
assignPortraitToActor: (actor, opts = {}) => manager.assignPortraitToActor(actor, opts),
filterPortraits: (opts = {}) => manager.filterPortraits(opts),
portraitOptsForActor: (actor) => manager.portraitOptsForActor(actor),
openPickerDialog: (actor) => {
new PortraitPickerDialog(actor, manager).render(true);
},
openGeneratorDialog: () => {
new NpcGeneratorDialog(manager).render(true);
},
manager,
};
Hooks.callAll('npcPortraitPack.ready', game.npcPortraitPack);
console.log('NPC Portrait Pack | Ready → game.npcPortraitPack');
});
// ---------------------------------------------------------------------------
// createActor hook — auto-assign portrait to new NPC actors
// ---------------------------------------------------------------------------
Hooks.on('createActor', async (actor, _options, userId) => {
if (game.userId !== userId) return;
if (!game.settings.get(MODULE_ID, 'autoAssign')) return;
if (actor.hasPlayerOwner || actor.type === 'ship') return;
if (!game.npcPortraitPack) {
await new Promise(resolve => Hooks.once('npcPortraitPack.ready', resolve));
}
await game.npcPortraitPack.assignPortraitToActor(actor);
});
// ---------------------------------------------------------------------------
// Sheet header buttons — inject "Portrait" and "Generate NPC" buttons
//
// In Foundry v13, _getHeaderButtons() calls:
// Hooks.callAll(`get${this.constructor.name}HeaderButtons`, sheet, buttons)
// so `getActorSheetHeaderButtons` does NOT fire for subclasses.
//
// Instead we use renderActorSheet which fires for ALL ActorSheet subclasses,
// and inject our buttons directly into the DOM.
// ---------------------------------------------------------------------------
Hooks.on('renderActorSheet', (sheet, html, _data) => {
if (!game.user?.isGM) return;
const actor = sheet.actor ?? sheet.object;
if (!actor || actor.type === 'ship') return;
// Resolve jQuery or plain DOM root
const root = html instanceof jQuery ? html[0] : html;
// Find the header controls area — works for both appv1 and appv2 layouts
const headerBtns = root.querySelector('.window-header .header-button')?.parentElement
?? root.querySelector('.header-controls')
?? root.querySelector('.window-header');
if (!headerBtns) return;
// Avoid duplicate injection on re-renders
if (root.querySelector('.npp-portrait-btn')) return;
// ── Portrait picker button ──────────────────────────────────────
const portraitBtn = document.createElement('a');
portraitBtn.className = 'header-button control npp-portrait-btn';
portraitBtn.title = 'Assign Portrait';
portraitBtn.innerHTML = '<i class="fas fa-portrait"></i><label>Portrait</label>';
portraitBtn.addEventListener('click', (ev) => {
ev.preventDefault();
if (!game.npcPortraitPack) return ui.notifications.warn('NPC Portrait Pack not ready.');
game.npcPortraitPack.openPickerDialog(actor);
});
// ── Generator button (GM toolbar, not actor-specific) ──────────
// Only show on non-player-owned actors, or always on the toolbar
const genBtn = document.createElement('a');
genBtn.className = 'header-button control npp-generate-btn';
genBtn.title = 'Open NPC Generator';
genBtn.innerHTML = '<i class="fas fa-user-plus"></i><label>Generate NPC</label>';
genBtn.addEventListener('click', (ev) => {
ev.preventDefault();
if (!game.npcPortraitPack) return ui.notifications.warn('NPC Portrait Pack not ready.');
game.npcPortraitPack.openGeneratorDialog();
});
// Prepend — so they appear before the default close/configure buttons
headerBtns.prepend(genBtn);
headerBtns.prepend(portraitBtn);
});
// ---------------------------------------------------------------------------
// Register a "Generate NPC" button in the Actors sidebar tab header
// ---------------------------------------------------------------------------
Hooks.on('renderActorDirectory', (_app, html) => {
if (!game.user?.isGM) return;
const root = html instanceof jQuery ? html[0] : html;
if (root.querySelector('.npp-generate-sidebar-btn')) return;
const header = root.querySelector('.directory-header .action-buttons')
?? root.querySelector('.header-actions');
if (!header) return;
const btn = document.createElement('button');
btn.className = 'npp-generate-sidebar-btn';
btn.title = 'Generate SLA NPC';
btn.innerHTML = '<i class="fas fa-user-plus"></i> Generate NPC';
btn.addEventListener('click', () => {
if (!game.npcPortraitPack) return ui.notifications.warn('NPC Portrait Pack not ready.');
game.npcPortraitPack.openGeneratorDialog();
});
header.appendChild(btn);
});