// packs_bootstrap.js — injected into the vendored Blockbench app (same origin). // Loads the GeckoLib + Multi Actor Animator plugins and bridges model/texture // loading from the parent page via postMessage. (function () { if (window.PacksBB) return; function waitFor(fn, timeout) { return new Promise((resolve) => { const start = Date.now(); const iv = setInterval(() => { if (fn()) { clearInterval(iv); resolve(true); } else if (Date.now() - start > timeout) { clearInterval(iv); resolve(false); } }, 100); }); } function notify(type, payload) { if (window.parent && window.parent !== window) { window.parent.postMessage(Object.assign({ type: 'packs-' + type }, payload || {}), window.location.origin); } } async function loadPlugin(path, id) { if (Plugins.registered[id]) { // already registered (persisted from a previous session) — ensure active const existing = Plugins.registered[id]; if (existing.installed && !existing.disabled && typeof existing.onload === 'function' && existing.source === 'file') { return existing; } } const code = await (await fetch(path, { cache: 'no-store' })).text(); if (!code || code.length < 20) throw new Error('empty plugin: ' + path); const inst = new Plugin(); await inst.loadFromFile({ path: path, content: code }, true); return inst; } function filenameOf(resource) { const s = String(resource || '').trim(); const parts = s.split('/'); return parts[parts.length - 1]; } // Set the Multi Actor Animator per-bone texture overrides from the geo's // afw_bone_textures. Only the feature bones get special treatment; the // vanilla skin stays the model's base (just loaded, Blockbench default). function applyAfwTextureOverrides(geo) { const afw = (geo && geo['afw_bone_textures']) || {}; const overrides = {}; for (const bone in afw) { const texName = filenameOf(afw[bone]); if (texName) overrides[bone] = { name: bone, texture: texName }; } if (!Object.keys(overrides).length || !Project) return; try { Project['multiactor_bone_texture_overrides'] = JSON.stringify(overrides); } catch (e) { console.error('packs override set failed', e); } } async function openModel(msg) { await waitFor(() => typeof loadModelFile === 'function', 15000); const name = msg.name || 'model'; if (msg.modelFile) { // Standalone Blockbench model (bbmodel) with baked textures. // glTF/GLB is not sent here — those are marked "Incompatible" by the // parent page (import needs the desktop-only glTF Importer plugin). if (msg.modelFile.content) { await loadModelFile({ content: msg.modelFile.content, name: name, path: name }); } await new Promise((r) => setTimeout(r, 400)); notify('model-open', { name: name }); return; } await loadModelFile({ content: JSON.stringify(msg.geo), name: name, path: name }); // Give the format importer a beat, then set the feature-bone overrides // (before adding textures, so the plugin's add_texture listener applies // them) and load the base texture first, then the feature overlays. await new Promise((r) => setTimeout(r, 800)); applyAfwTextureOverrides(msg.geo); const afwFiles = new Set(); for (const k in ((msg.geo && msg.geo['afw_bone_textures']) || {})) { afwFiles.add(filenameOf(msg.geo['afw_bone_textures'][k])); } const baseTextures = (msg.textures || []).filter((t) => !afwFiles.has(t.name)); const featureTextures = (msg.textures || []).filter((t) => afwFiles.has(t.name)); for (const t of baseTextures.concat(featureTextures)) { try { const tex = new Texture({ name: t.name || 'texture', folder: 'entity' }); if (t.dataUrl) { await tex.fromDataURL(t.dataUrl); } else if (t.url) { await tex.fromPath(t.url); } tex.add(false); } catch (e) { console.error('packs texture load failed', t && t.name, e); } } await new Promise((r) => setTimeout(r, 400)); await hotloadPackPlugins(msg.plugins); notify('model-open', { name: name }); } // Hotload plugins bundled with the pack (served via pack_asset). A failing // plugin is reported but never blocks the model preview. async function hotloadPackPlugins(plugins) { for (const p of (plugins || [])) { if (!p || !p.url || !p.member) continue; try { const code = await (await fetch(p.url, { headers: { 'Accept': 'text/javascript' } })).text(); if (!code || code.length < 20) continue; await new Plugin().loadFromFile({ path: p.member, content: code }, true); notify('plugin-hotloaded', { name: p.member }); } catch (e) { console.error('packs pack plugin hotload failed', p.member, e); notify('plugin-error', { error: 'Failed to load plugin ' + p.member }); } } } // Open a Minecraft Skin project (Steve/Alex player model) with the skin. async function openSkin(msg) { await waitFor(() => typeof Formats !== 'undefined' && !!Formats.skin, 15000); const model = msg.model === 'alex' ? 'alex' : 'steve'; const dialog = Formats.skin.setup_dialog; dialog.show(); await dialog.setFormValues({ model: model, texture_source: 'upload_texture', texture_file: { content: msg.dataUrl, name: 'skin.png', path: 'skin.png' }, layer_template: true, }); dialog.confirm(); // Reveal every cube (incl. the outer layer) and land in Pose mode. await new Promise((r) => setTimeout(r, 700)); try { (typeof Cube !== 'undefined' && Cube.all || []).forEach((c) => { c.visibility = true; }); if (typeof Canvas !== 'undefined' && Canvas.updateAll) Canvas.updateAll(); const pose = Modes && Modes.options && Modes.options.pose; if (pose && typeof pose.select === 'function') pose.select(); } catch (e) { console.error('packs skin post-load', e); } notify('model-open', { name: msg.name || 'skin' }); } window.addEventListener('message', (e) => { if (!e.data) return; if (e.data.type === 'packs-open-skin') { openSkin(e.data).then( () => {}, (err) => { console.error('packs-open-skin failed', err); notify('model-error', { name: e.data.name || '', error: String(err) }); } ); return; } if (e.data.type !== 'packs-open-model') return; openModel(e.data).then( () => {}, (err) => { console.error('packs-open-model failed', err); notify('model-error', { name: e.data.name || '', error: String(err) }); } ); }); async function boot() { window.confirm = () => true; const ok = await waitFor(() => window.Blockbench && window.Blockbench.setup_successful && window.Plugin && typeof loadModelFile === 'function', 30000); if (!ok) { notify('error', { error: 'Blockbench did not initialize' }); return; } try { await loadPlugin('plugins/geckolib/geckolib.js', 'geckolib'); await loadPlugin('plugins/MultiactorEditor/MultiactorEditor.js', 'MultiactorEditor'); } catch (e) { console.error('packs plugin load failed', e); notify('plugin-error', { error: String(e) }); } window.PacksBB = { ready: true, plugins: ['geckolib', 'MultiactorEditor'] }; notify('bb-ready', {}); } boot(); })();