139 lines
5.8 KiB
JavaScript
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);
|
|
});
|