"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // external-global:skyrimPlatform var require_skyrimPlatform = __commonJS({ "external-global:skyrimPlatform"(exports2, module2) { module2.exports = globalThis.skyrimPlatform; } }); // src/index.ts var import_skyrimPlatform7 = __toESM(require_skyrimPlatform()); // src/ui-injection.ts function buildPauseMenuScript() { return ` (function () { if (window.skyduckPauseMenu) { console.log('[skyduck-pause] already injected, skip'); return; } /* * Design tokens \u2014 derivados del medall\xF3n medieval del badge de voz. * Bronce envejecido (#735238), oro tabernero (#CFAA6E / #E4B252), * pergamino (#E8DCC4), rojo \xF3xido (#D78686). * Tipograf\xEDa: Tavern \u2192 Cinzel (display Roman) \u2192 fallback serif. */ const css = \` #skyduck-pause-root { --gold: #CFAA6E; --gold-bright: #E4B252; --bronze: #735238; --bronze-deep: #2c2114; --parchment: #E8DCC4; --ink: #14100a; --rust: #D78686; --rust-deep: #7a3030; position: fixed; inset: 0; z-index: 999999; display: none; align-items: center; justify-content: center; font-family: 'Cinzel', 'Tavern', 'Trajan Pro', 'Cormorant Garamond', Georgia, serif; font-feature-settings: 'liga' 1, 'kern' 1; -webkit-font-smoothing: antialiased; background: radial-gradient(ellipse at center, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.88) 70%, rgba(0,0,0,0.95) 100%); backdrop-filter: blur(8px) saturate(0.85); -webkit-backdrop-filter: blur(8px) saturate(0.85); } /* Textura vellum sutil sobre el overlay completo */ #skyduck-pause-root::before { content: ''; position: absolute; inset: 0; pointer-events: none; opacity: 0.06; mix-blend-mode: overlay; background-image: radial-gradient(circle at 20% 30%, rgba(207,170,110,0.7) 0px, transparent 1px), radial-gradient(circle at 75% 60%, rgba(207,170,110,0.5) 0px, transparent 1px), radial-gradient(circle at 40% 85%, rgba(207,170,110,0.4) 0px, transparent 1px), radial-gradient(circle at 88% 15%, rgba(207,170,110,0.6) 0px, transparent 1px); background-size: 4px 4px, 5px 5px, 3px 3px, 6px 6px; } #skyduck-pause-root.skyduck-visible { display: flex; } /* \u2500\u2500 Card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */ .skyduck-pause-card { position: relative; width: 420px; max-width: 92vw; padding: 56px 64px 44px; background: linear-gradient(180deg, rgba(28,22,14,0.96) 0%, rgba(14,10,6,0.96) 100%); text-align: center; /* Diffusion shadow + frame de bronce */ box-shadow: 0 32px 64px -16px rgba(0,0,0,0.75), 0 0 0 1px rgba(115,82,56,0.6), inset 0 0 0 1px rgba(207,170,110,0.12); /* Esquinas suavemente cortadas tipo placa de bronce */ clip-path: polygon( 14px 0, calc(100% - 14px) 0, 100% 14px, 100% calc(100% - 14px), calc(100% - 14px) 100%, 14px 100%, 0 calc(100% - 14px), 0 14px ); transform: translateY(8px) scale(0.96); opacity: 0; transition: transform 360ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 220ms ease-out; } #skyduck-pause-root.skyduck-visible .skyduck-pause-card { transform: translateY(0) scale(1); opacity: 1; } /* Inner double border + gold seam */ .skyduck-pause-card::before { content: ''; position: absolute; inset: 10px; border: 1px solid rgba(207,170,110,0.18); pointer-events: none; } /* Animated gold shimmer band */ .skyduck-pause-card::after { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 1px; background: linear-gradient( 90deg, transparent 0%, rgba(228,178,82,0.0) 20%, rgba(228,178,82,0.8) 50%, rgba(228,178,82,0.0) 80%, transparent 100% ); animation: skyduck-pause-shimmer 5.5s ease-in-out infinite; pointer-events: none; } @keyframes skyduck-pause-shimmer { 0%, 100% { transform: translateX(-100%); opacity: 0; } 15%, 85% { opacity: 1; } 50% { transform: translateX(100%); opacity: 0; } } /* Corner ornaments \u2014 4 diamantes peque\xF1os en cada esquina del inner border */ .skyduck-pause-corner { position: absolute; width: 14px; height: 14px; pointer-events: none; color: var(--gold); opacity: 0.7; } .skyduck-pause-corner.tl { top: 4px; left: 4px; } .skyduck-pause-corner.tr { top: 4px; right: 4px; transform: scaleX(-1); } .skyduck-pause-corner.bl { bottom: 4px; left: 4px; transform: scaleY(-1); } .skyduck-pause-corner.br { bottom: 4px; right: 4px; transform: scale(-1, -1); } /* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */ .skyduck-pause-brand { color: var(--gold); font-family: 'Cinzel', 'Trajan Pro', serif; font-size: 10px; letter-spacing: 0.42em; text-indent: 0.42em; /* compensa tracking visual */ margin: 0 0 4px; text-transform: uppercase; opacity: 0.78; } .skyduck-pause-title { color: var(--parchment); font-family: 'Cinzel', 'Trajan Pro', serif; font-size: 30px; font-weight: 500; letter-spacing: 0.18em; text-indent: 0.18em; margin: 0; text-transform: uppercase; line-height: 1; } /* Divisor decorativo: l\xEDnea \u2014 diamante \u2014 l\xEDnea */ .skyduck-pause-divider { display: flex; align-items: center; justify-content: center; gap: 12px; margin: 14px 0 32px; color: var(--gold); opacity: 0.65; } .skyduck-pause-divider::before, .skyduck-pause-divider::after { content: ''; flex: 1; max-width: 90px; height: 1px; background: linear-gradient(90deg, transparent, currentColor 50%, transparent); } .skyduck-pause-divider-mark { width: 7px; height: 7px; background: currentColor; transform: rotate(45deg); } /* \u2500\u2500 Botones \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */ .skyduck-pause-btn { position: relative; display: block; width: 100%; padding: 14px 24px 14px 28px; margin: 8px 0; background: transparent; border: 0; border-top: 1px solid rgba(207,170,110,0.18); border-bottom: 1px solid rgba(207,170,110,0.18); color: var(--parchment); font-family: inherit; font-size: 13px; font-weight: 500; letter-spacing: 0.22em; text-indent: 0.22em; text-transform: uppercase; cursor: pointer; overflow: hidden; transition: color 200ms cubic-bezier(0.4, 0, 0.2, 1), letter-spacing 200ms cubic-bezier(0.4, 0, 0.2, 1), padding-left 200ms cubic-bezier(0.4, 0, 0.2, 1); } /* Indicador izquierdo: diamante dorado peque\xF1o que aparece on hover */ .skyduck-pause-btn::before { content: ''; position: absolute; left: 8px; top: 50%; width: 5px; height: 5px; background: var(--gold-bright); transform: translateY(-50%) rotate(45deg) scale(0); transition: transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1); } /* Shimmer sweep \u2014 luz que recorre el bot\xF3n on hover */ .skyduck-pause-btn::after { content: ''; position: absolute; inset: 0; background: linear-gradient( 90deg, transparent 0%, rgba(228,178,82,0.0) 30%, rgba(228,178,82,0.15) 50%, rgba(228,178,82,0.0) 70%, transparent 100% ); transform: translateX(-100%); pointer-events: none; transition: transform 600ms cubic-bezier(0.4, 0, 0.2, 1); } .skyduck-pause-btn:hover { color: var(--gold-bright); padding-left: 36px; } .skyduck-pause-btn:hover::before { transform: translateY(-50%) rotate(45deg) scale(1); } .skyduck-pause-btn:hover::after { transform: translateX(100%); } .skyduck-pause-btn:active { transform: translateY(1px); } .skyduck-pause-btn--danger { color: var(--rust); } .skyduck-pause-btn--danger::before { background: var(--rust); } .skyduck-pause-btn--danger:hover { color: var(--rust); } .skyduck-pause-btn--danger::after { background: linear-gradient( 90deg, transparent 0%, rgba(215,134,134,0.0) 30%, rgba(215,134,134,0.18) 50%, rgba(215,134,134,0.0) 70%, transparent 100% ); } /* Stagger de aparici\xF3n de botones */ .skyduck-pause-section.skyduck-active .skyduck-pause-btn { opacity: 0; animation: skyduck-pause-btn-in 380ms cubic-bezier(0.4, 0, 0.2, 1) forwards; } .skyduck-pause-section.skyduck-active .skyduck-pause-btn:nth-child(1) { animation-delay: 60ms; } .skyduck-pause-section.skyduck-active .skyduck-pause-btn:nth-child(2) { animation-delay: 140ms; } .skyduck-pause-section.skyduck-active .skyduck-pause-btn:nth-child(3) { animation-delay: 220ms; } @keyframes skyduck-pause-btn-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } .skyduck-pause-hint { margin-top: 28px; color: rgba(232,220,196,0.32); font-family: 'Geist Mono', 'JetBrains Mono', ui-monospace, monospace; font-size: 10px; letter-spacing: 0.32em; text-transform: uppercase; } \`; const styleEl = document.createElement('style'); styleEl.id = 'skyduck-pause-style'; styleEl.textContent = css; document.head.appendChild(styleEl); // Styles extra para la vista de voz const voiceStyles = document.createElement('style'); voiceStyles.textContent = \` .skyduck-pause-section { display: none; } .skyduck-pause-section.skyduck-active { display: block; } /* Header de grupo: peque\xF1a runa romana + label dorado */ .skyduck-pause-group { text-align: left; margin: 18px 0 4px; padding-bottom: 6px; position: relative; } .skyduck-pause-group:first-of-type { margin-top: 4px; } .skyduck-pause-group-label { display: inline-block; font-family: 'Cinzel', 'Trajan Pro', serif; font-size: 10px; letter-spacing: 0.36em; text-indent: 0.36em; color: var(--gold); text-transform: uppercase; opacity: 0.82; } .skyduck-pause-group-label::before { content: ''; display: inline-block; width: 4px; height: 4px; margin: 0 10px 2px 0; background: var(--gold); transform: rotate(45deg); vertical-align: middle; opacity: 0.7; } .skyduck-pause-group::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: linear-gradient(90deg, rgba(207,170,110,0.4), rgba(207,170,110,0.04)); } .skyduck-pause-row { display: grid; grid-template-columns: 110px 1fr 44px; align-items: center; gap: 16px; padding: 12px 0; border-bottom: 1px solid rgba(207,170,110,0.08); } .skyduck-pause-row:last-child { border-bottom: 0; } .skyduck-pause-row--toggle { grid-template-columns: 110px 1fr; } .skyduck-pause-row-label { color: var(--parchment); font-family: 'Cinzel', 'Trajan Pro', serif; font-size: 11px; font-weight: 400; letter-spacing: 0.18em; text-indent: 0.18em; text-transform: uppercase; text-align: left; opacity: 0.88; } /* Slider: track con groove tipo metal forjado + thumb dorado glow */ .skyduck-pause-slider { appearance: none; -webkit-appearance: none; width: 100%; height: 6px; background: linear-gradient(180deg, rgba(0,0,0,0.55) 0%, rgba(20,16,10,0.4) 100%); border: 1px solid rgba(115,82,56,0.5); outline: none; cursor: pointer; position: relative; } .skyduck-pause-slider::-webkit-slider-runnable-track { height: 4px; background: transparent; } .skyduck-pause-slider::-webkit-slider-thumb { appearance: none; -webkit-appearance: none; width: 14px; height: 14px; border-radius: 50%; margin-top: -5px; background: radial-gradient(circle at 30% 30%, #f0c878 0%, #CFAA6E 50%, #8a6a44 100%); border: 1px solid #4a3a23; box-shadow: 0 0 0 1px rgba(20,16,10,0.6), 0 0 8px rgba(228,178,82,0.45); cursor: grab; transition: box-shadow 200ms, transform 120ms; } .skyduck-pause-slider::-webkit-slider-thumb:hover { box-shadow: 0 0 0 1px rgba(20,16,10,0.6), 0 0 14px rgba(228,178,82,0.85); transform: scale(1.1); } .skyduck-pause-slider::-webkit-slider-thumb:active { cursor: grabbing; } .skyduck-pause-slider::-moz-range-thumb { width: 14px; height: 14px; border-radius: 50%; background: radial-gradient(circle at 30% 30%, #f0c878 0%, #CFAA6E 50%, #8a6a44 100%); border: 1px solid #4a3a23; cursor: grab; } /* Toggle estilo switch grabado */ .skyduck-pause-toggle { position: relative; width: 48px; height: 24px; background: linear-gradient(180deg, rgba(0,0,0,0.55) 0%, rgba(20,16,10,0.4) 100%); border: 1px solid rgba(115,82,56,0.6); cursor: pointer; transition: background 240ms cubic-bezier(0.4, 0, 0.2, 1), border-color 240ms cubic-bezier(0.4, 0, 0.2, 1); justify-self: end; margin-right: 0; } .skyduck-pause-toggle::after { content: ''; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; background: radial-gradient(circle at 30% 30%, #5a4530 0%, #3a2c1d 100%); border: 1px solid rgba(115,82,56,0.7); transition: left 280ms cubic-bezier(0.34, 1.56, 0.64, 1), background 240ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 240ms; } .skyduck-pause-toggle.skyduck-on { background: linear-gradient(180deg, rgba(207,170,110,0.18) 0%, rgba(115,82,56,0.28) 100%); border-color: rgba(207,170,110,0.85); } .skyduck-pause-toggle.skyduck-on::after { left: 26px; background: radial-gradient(circle at 30% 30%, #f0c878 0%, #CFAA6E 50%, #8a6a44 100%); box-shadow: 0 0 10px rgba(228,178,82,0.6); } .skyduck-pause-value { color: var(--gold); font-family: 'Geist Mono', 'JetBrains Mono', ui-monospace, monospace; font-size: 11px; text-align: right; opacity: 0.9; font-variant-numeric: tabular-nums; } .skyduck-pause-section[data-section="voice"] .skyduck-pause-btn { margin-top: 20px; } /* Info row (read-only) para mostrar la tecla PTT */ .skyduck-pause-row--info { grid-template-columns: 110px 1fr; } .skyduck-pause-kbd { justify-self: end; display: inline-flex; align-items: center; justify-content: center; min-width: 32px; height: 28px; padding: 0 10px; font-family: 'Geist Mono', 'JetBrains Mono', ui-monospace, monospace; font-size: 12px; font-weight: 600; color: var(--gold-bright); background: linear-gradient(180deg, rgba(40,30,18,0.9) 0%, rgba(20,16,10,0.9) 100%); border: 1px solid rgba(207,170,110,0.55); border-bottom-width: 2px; box-shadow: inset 0 1px 0 rgba(255,220,160,0.18), 0 2px 4px rgba(0,0,0,0.55); letter-spacing: 0; } /* Custom dropdown \u2014 el
100%
Nivel
\u2014
Silenciar
Audio entrante
Silenciar
Salida
Volumen
100%
Cambios aplicados al instante
\`; document.body.appendChild(root); const BRIDGE_BASE = 'http://127.0.0.1:19999'; function show() { root.classList.add('skyduck-visible'); switchSection('main'); } function hide() { root.classList.remove('skyduck-visible'); stopVu(); } function isOpen() { return root.classList.contains('skyduck-visible'); } function toggle() { if (isOpen()) hide(); else show(); } function switchSection(name) { const sections = root.querySelectorAll('[data-section]'); sections.forEach(function (s) { s.classList.toggle('skyduck-active', s.getAttribute('data-section') === name); }); if (name === 'voice') { refreshVoiceSettings(); startVu(); } else { stopVu(); // Cerrar cualquier dropdown abierto al salir. const dropdown = root.querySelector('[data-dropdown="input_device"]'); if (dropdown) dropdown.classList.remove('skyduck-open'); } } function refreshVoiceSettings() { fetch(BRIDGE_BASE + '/audio/settings', { method: 'GET' }) .then(function (r) { return r.json(); }) .then(applyVoiceSettingsToUI) .catch(function (e) { console.log('[skyduck-pause] fetch settings failed:', e); }); fetch(BRIDGE_BASE + '/audio/devices', { method: 'GET' }) .then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }) .then(populateDevices) .catch(function (e) { console.log('[skyduck-pause] fetch devices failed:', e); showDeviceError(String(e && e.message ? e.message : e)); }); // Diag para mostrar warning si playback est\xE1 roto. fetch(BRIDGE_BASE + '/audio/diag', { method: 'GET' }) .then(function (r) { return r.json(); }) .then(function (d) { const warn = root.querySelector('[data-playback-warn]'); const msg = root.querySelector('[data-playback-warn-msg]'); if (!warn || !msg) return; if (d && d.playback_ok === false) { warn.style.display = 'block'; msg.textContent = d.playback_error ? 'Salida no inicializada: ' + d.playback_error.substring(0, 120) : 'Sin salida de audio. Prob\xE1 otro dispositivo del dropdown.'; } else { warn.style.display = 'none'; } }) .catch(function () {}); } function showDeviceError(msg) { const sel = root.querySelector('[data-select="input_device"]'); if (!sel) return; sel.innerHTML = ''; const opt = document.createElement('option'); opt.textContent = 'error: ' + msg + ' \u2014 bridge viejo?'; opt.disabled = true; sel.appendChild(opt); } function populateDevices(data) { fillDropdown('input_device', (data && data.input_devices) || [], (data && data.input_current) || ''); fillDropdown('output_device', (data && data.output_devices) || [], (data && data.output_current) || ''); } function fillDropdown(key, devices, current) { const dropdown = root.querySelector('[data-dropdown="' + key + '"]'); if (!dropdown) return; const panel = dropdown.querySelector('[data-dropdown-panel]'); const currentLabel = dropdown.querySelector('[data-dropdown-current]'); if (!panel || !currentLabel) return; currentLabel.textContent = current || 'sin dispositivos'; panel.innerHTML = ''; if (devices.length === 0) { const empty = document.createElement('div'); empty.className = 'skyduck-pause-dropdown-option'; empty.style.cursor = 'default'; empty.style.opacity = '0.5'; empty.textContent = 'sin dispositivos disponibles'; panel.appendChild(empty); return; } devices.forEach(function (name) { const opt = document.createElement('button'); opt.type = 'button'; opt.className = 'skyduck-pause-dropdown-option'; if (name === current) opt.classList.add('skyduck-current'); opt.textContent = name; opt.setAttribute('data-dropdown-value', name); opt.setAttribute('data-dropdown-key', key); panel.appendChild(opt); }); } function selectDevice(key, name) { const dropdown = root.querySelector('[data-dropdown="' + key + '"]'); if (!dropdown) return; const currentLabel = dropdown.querySelector('[data-dropdown-current]'); if (currentLabel) currentLabel.textContent = name; const opts = dropdown.querySelectorAll('[data-dropdown-value]'); opts.forEach(function (o) { o.classList.toggle('skyduck-current', o.getAttribute('data-dropdown-value') === name); }); dropdown.classList.remove('skyduck-open'); const kind = key === 'input_device' ? 'input' : 'output'; fetch(BRIDGE_BASE + '/audio/device', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ kind: kind, name: name }), }) .then(function (r) { return r.json(); }) .then(function (d) { if (d && d.ok) { console.log('[skyduck-pause] ' + kind + ' device \u2192 ' + (d.selected || name)); if (d.selected && currentLabel) currentLabel.textContent = d.selected; // Refresh diag \u2014 playback_ok puede haber cambiado. if (kind === 'output') refreshVoiceSettings(); } else { console.log('[skyduck-pause] device switch failed:', d && d.error); } }) .catch(function (e) { console.log('[skyduck-pause] device switch error:', e); }); } // VU meter siempre activo cuando la secci\xF3n Voz est\xE1 visible \u2014 eso le // permite al user ver actividad de su mic sin tener que apretar nada. // Poll cada 60ms (~16 fps), suspendido cuando se cierra/cambia secci\xF3n. let vuTimer = null; function startVu() { stopVu(); const fill = root.querySelector('[data-vu-fill]'); const label = root.querySelector('[data-vu-label]'); vuTimer = window.setInterval(function () { fetch(BRIDGE_BASE + '/audio/mic_level') .then(function (r) { return r.json(); }) .then(function (d) { const pct = Math.max(0, Math.min(100, (d.level || 0) * 250)); if (fill) fill.style.width = pct.toFixed(0) + '%'; if (label) label.textContent = (pct | 0) + '%'; }) .catch(function () {}); }, 60); } function stopVu() { if (vuTimer != null) { clearInterval(vuTimer); vuTimer = null; } const fill = root.querySelector('[data-vu-fill]'); const label = root.querySelector('[data-vu-label]'); if (fill) fill.style.width = '0%'; if (label) label.textContent = '\u2014'; } function applyVoiceSettingsToUI(s) { setToggle('hard_mute', !!s.hard_mute); setToggle('mute_output', !!s.mute_output); setSlider('gain_input', s.gain_input); setSlider('gain_output', s.gain_output); } function setToggle(key, on) { const el = root.querySelector('[data-toggle="' + key + '"]'); if (el) el.classList.toggle('skyduck-on', on); } function setSlider(key, val01to2) { const v = Math.round((val01to2 || 0) * 100); const sl = root.querySelector('[data-slider="' + key + '"]'); const lb = root.querySelector('[data-value="' + key + '"]'); if (sl) sl.value = String(v); if (lb) lb.textContent = v + '%'; } function patchSettings(body) { fetch(BRIDGE_BASE + '/audio/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }).catch(function (e) { console.log('[skyduck-pause] patch settings failed:', e); }); } // Click handlers root.addEventListener('click', function (ev) { const target = ev.target; if (!(target instanceof HTMLElement)) return; // Toggles (mute_input / mute_output) const toggleKey = target.getAttribute('data-toggle'); if (toggleKey) { const on = !target.classList.contains('skyduck-on'); target.classList.toggle('skyduck-on', on); const patch = {}; patch[toggleKey] = on; patchSettings(patch); return; } // Botones de acci\xF3n const action = target.getAttribute('data-action'); if (!action) return; switch (action) { case 'continue': case 'disconnect': stopVu(); try { window.skyrimPlatform.sendMessage('skyduck:pause:' + action); } catch (e) { console.log('[skyduck-pause] sendMessage failed: ' + e); } break; case 'open-voice': switchSection('voice'); break; case 'back': switchSection('main'); break; } }); // Dropdown \u2014 toggle + selecci\xF3n. Delegamos en root para sobrevivir // a re-populate del panel. root.addEventListener('click', function (ev) { const target = ev.target; if (!(target instanceof HTMLElement)) return; // Click en una option \u2192 select. data-dropdown-key indica input/output. const valueOpt = target.closest('[data-dropdown-value]'); if (valueOpt) { const v = valueOpt.getAttribute('data-dropdown-value'); const k = valueOpt.getAttribute('data-dropdown-key') || 'input_device'; if (v) selectDevice(k, v); ev.stopPropagation(); return; } // Click en el button del dropdown \u2192 toggle abierto const btn = target.closest('.skyduck-pause-dropdown-btn'); if (btn) { const dropdown = btn.closest('[data-dropdown]'); if (dropdown) { dropdown.classList.toggle('skyduck-open'); ev.stopPropagation(); } return; } // Click afuera del dropdown \u2192 cerrar si estaba abierto const openDropdown = root.querySelector('[data-dropdown].skyduck-open'); if (openDropdown && !openDropdown.contains(target)) { openDropdown.classList.remove('skyduck-open'); } }); // Sliders \u2014 input event para feedback instant\xE1neo root.addEventListener('input', function (ev) { const target = ev.target; if (!(target instanceof HTMLInputElement)) return; const key = target.getAttribute('data-slider'); if (!key) return; const v = parseInt(target.value, 10); const lb = root.querySelector('[data-value="' + key + '"]'); if (lb) lb.textContent = v + '%'; const patch = {}; patch[key] = v / 100; patchSettings(patch); }); window.skyduckPauseMenu = { show: show, hide: hide, toggle: toggle, isOpen: isOpen }; console.log('[skyduck-pause] UI ready'); })(); `; } // src/voice-bridge.ts var import_skyrimPlatform = __toESM(require_skyrimPlatform()); var BRIDGE_URL = "http://127.0.0.1:19999"; var SEND_INTERVAL_MS = 50; var HEALTHY_THRESHOLD = 5; var STATUS_REBROADCAST_MS = 3e3; var http = new import_skyrimPlatform.HttpClient(BRIDGE_URL); var lastSendTs = 0; var healthy = false; var okStreak = 0; var lastStatusBroadcastTs = 0; function startVoiceBridge(getCharName, getCellId) { (0, import_skyrimPlatform.printConsole)("[skyduck-voice] starting bridge publisher \u2192 " + BRIDGE_URL); (0, import_skyrimPlatform.on)("update", () => { const now = Date.now(); if (now - lastSendTs < SEND_INTERVAL_MS) return; lastSendTs = now; if (now - lastStatusBroadcastTs >= STATUS_REBROADCAST_MS) { broadcastStatus(); } const p = import_skyrimPlatform.Game.getPlayer(); if (!p) return; const x = p.getPositionX(); const y = p.getPositionY(); const z = p.getPositionZ(); const yawDeg = p.getAngleZ(); if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z) || !Number.isFinite(yawDeg)) { return; } const body = JSON.stringify({ x, y, z, yaw: yawDeg * Math.PI / 180, cellId: getCellId() || "unknown", playerName: getCharName() || "Adventurer" }); http.post("/pos", { body, contentType: "application/json" }).then(() => { okStreak++; if (!healthy && okStreak >= HEALTHY_THRESHOLD) { setHealthy(true); } }).catch(() => { okStreak = 0; if (healthy) { setHealthy(false); } }); }); } function setHealthy(ok) { healthy = ok; (0, import_skyrimPlatform.printConsole)("[skyduck-voice] bridge " + (ok ? "OK" : "down")); broadcastStatus(); } function broadcastStatus() { lastStatusBroadcastTs = Date.now(); try { import_skyrimPlatform.browser.executeJavaScript( `window.dispatchEvent(new CustomEvent('message:ui:voice', { detail: { key: 'setStatus', data: ${healthy} } }));` ); } catch { } } function publishTalkingFormIds(formids) { if (!healthy) return; const body = JSON.stringify({ formids }); http.post("/talking", { body, contentType: "application/json" }).catch(() => { }); } // src/voice-talking-sync.ts var import_skyrimPlatform2 = __toESM(require_skyrimPlatform()); var ANIM_TICK_MS = 50; var STOPPING_FADE_MS = 200; var ANIM_MAX_DISTANCE = 5e3; var TALK_DOTS_MAX_DISTANCE = 3e3; var bridgeHttp = new import_skyrimPlatform2.HttpClient("http://127.0.0.1:19999"); var sharedStorage = import_skyrimPlatform2.storage; var localVoiceMode = "normal"; var localVoiceRange = 2100; function setLocalVoiceMode(mode, range) { localVoiceMode = mode; localVoiceRange = range; try { sharedStorage.skyduckLocalVoiceMode = mode; sharedStorage.skyduckLocalVoiceRange = range; } catch { } } function setLocalTalking(talking, _getLocalName, _getLocalCell) { try { sharedStorage.skyduckLocalTalking = !!talking; } catch { } } var remoteTalkers = /* @__PURE__ */ new Map(); var diagPhonemeMissing = 0; function resolveActor(fid) { try { const act = import_skyrimPlatform2.Actor.from(import_skyrimPlatform2.Game.getFormEx(fid)); if (!act) return null; const p = import_skyrimPlatform2.Game.getPlayer(); if (p && act.getFormID() === p.getFormID()) return null; return act; } catch { return null; } } var dotTextIds = /* @__PURE__ */ new Map(); var lastIconsNonEmpty = false; function pushWorldIcons(icons) { if (icons.length === 0 && !lastIconsNonEmpty) return; lastIconsNonEmpty = icons.length > 0; try { import_skyrimPlatform2.browser.executeJavaScript( `window.dispatchEvent(new CustomEvent('message:ui:voice',{detail:{key:'worldIcons',data:${JSON.stringify(icons)}}}));` ); } catch (e) { (0, import_skyrimPlatform2.printConsole)("[skyduck-talking] pushWorldIcons failed: " + String(e)); } } var ICON_HEAD_OFFSET = 135; function updateTalkDots(now) { const player = import_skyrimPlatform2.Game.getPlayer(); if (!player) { clearAllDots(); return; } const px = player.getPositionX(); const py = player.getPositionY(); const pz = player.getPositionZ(); if (![px, py, pz].every(Number.isFinite)) return; let entries = []; try { const e = sharedStorage.skyduckTalkerEntries; if (Array.isArray(e)) entries = e; } catch { } const icons = []; let skipFar = 0; let skipBehind = 0; for (const e of entries) { const wx = e.x; const wy = e.y; const wz = e.z + ICON_HEAD_OFFSET; if (![wx, wy, wz].every(Number.isFinite)) continue; const dx = px - wx; const dy = py - wy; const dz = pz - wz; if (dx * dx + dy * dy + dz * dz > TALK_DOTS_MAX_DISTANCE * TALK_DOTS_MAX_DISTANCE) { skipFar++; continue; } try { const projected = (0, import_skyrimPlatform2.worldPointToScreenPoint)([wx, wy, wz]); const screen = projected && projected[0]; if (!screen || screen.length < 3) { skipBehind++; continue; } if (screen[2] <= 0) { skipBehind++; continue; } icons.push({ id: e.id >>> 0, x: screen[0], y: 1 - screen[1] }); } catch { skipBehind++; } } diagEntries = entries.length; diagIcons = icons.length; diagSkipFar = skipFar; diagSkipBehind = skipBehind; pushWorldIcons(icons); } var diagEntries = 0; var diagIcons = 0; var diagSkipFar = 0; var diagSkipBehind = 0; function clearAllDots() { pushWorldIcons([]); if (dotTextIds.size === 0) return; for (const tid of dotTextIds.values()) { try { (0, import_skyrimPlatform2.destroyText)(tid); } catch { } } dotTextIds.clear(); } function animateTick(now) { for (const [fid, t] of remoteTalkers) { if (!t.actor) { if (!t.present) remoteTalkers.delete(fid); else t.actor = resolveActor(fid); continue; } try { const player = import_skyrimPlatform2.Game.getPlayer(); if (player) { const dx = player.getPositionX() - t.actor.getPositionX(); const dy = player.getPositionY() - t.actor.getPositionY(); const dz = player.getPositionZ() - t.actor.getPositionZ(); if (dx * dx + dy * dy + dz * dz > ANIM_MAX_DISTANCE * ANIM_MAX_DISTANCE) { if (t.stopping && now - t.stoppingSince >= STOPPING_FADE_MS) remoteTalkers.delete(fid); continue; } } } catch { continue; } if (t.stopping) { const fadeT = Math.min(1, (now - t.stoppingSince) / STOPPING_FADE_MS); applyPhonemes(t.actor, t.startTs, 1 - fadeT); if (fadeT >= 1) { resetPhonemes(t.actor); remoteTalkers.delete(fid); } } else { applyPhonemes(t.actor, t.startTs, 1); } } } function applyPhonemes(actor, startTs, gain) { const a = actor; if (typeof a.setExpressionPhoneme !== "function") { diagPhonemeMissing++; return; } const t = (Date.now() - startTs) / 1e3; const g = Math.max(0, Math.min(1, gain)); const osc = (freq, phase, base, amp) => Math.max(0, Math.min(1, base + amp * Math.sin(t * freq + phase))) * g; try { a.setExpressionPhoneme(0, osc(9, 0, 0.45, 0.45)); a.setExpressionPhoneme(1, osc(7.3, 1.2, 0.3, 0.3)); a.setExpressionPhoneme(5, osc(11.7, 2.4, 0.22, 0.22)); a.setExpressionPhoneme(7, osc(5.5, 3.6, 0.28, 0.28)); a.setExpressionPhoneme(11, osc(8.1, 4.8, 0.18, 0.18)); } catch { } } function resetPhonemes(actor) { const a = actor; if (typeof a.setExpressionPhoneme !== "function") return; try { for (let i = 0; i < 16; i++) a.setExpressionPhoneme(i, 0); } catch { } } function startTalkingSync(_getLocalName, _getLocalCell) { (0, import_skyrimPlatform2.printConsole)("[skyduck-talking] talking sync NATIVO iniciado (property skyduck_talking via gamemode)"); let lastAnimTickTs = 0; let lastDiagTs = Date.now(); (0, import_skyrimPlatform2.on)("update", () => { const now = Date.now(); if (now - lastAnimTickTs >= ANIM_TICK_MS) { lastAnimTickTs = now; animateTick(now); } }); } // src/animation-sync.ts var import_skyrimPlatform3 = __toESM(require_skyrimPlatform()); // src/animationCatalog.ts var STOP_EVENT = "IdleStop"; var VALID_EVENTS = /* @__PURE__ */ new Set([ // Saludos "IdleSilentBow", "IdleWave", "IdleDialogueWelcomeHandGesture", "IdleCO2Ceremony1Welcome", "IdleComeThisWay", "IdleGetAttention", "Idlegive", "IdleTake", // Celebrar "IdleApplaud2", "IdleApplaud3", "IdleApplaud4", "IdleApplaud5", "IdleCivilWarCheer", "IdleMQ201ToastStart", "IdleLaugh", // Bailar "IdleCiceroDance1", "IdleCiceroDance2", "IdleCiceroDance3", "IdleCiceroHappy", "IdleBarDrinkingStart", // Música "IdleDrumStart", "IdlefluteStart", "IdleLuteStart", // Militar "IdleCombatShieldStart", "IdleSalute", "IdleSnaptoAttention", "IdleCleanSword", "IdleCombatStretchingStart", "IdleBlowHornImperial", "IdleSurrender", "IdleCowering", // Erudito "IdleGreyBeardWordTeach", "IdleStudy", "IdleNoteRead", "IdleDialogueHandOnChinGesture", "IdleExamine", "IdlePray", "IdleGreybeardMeditateEnter", "IdleRitualStart", "IdlePointClose", "IdleLookFar", // Descanso "IdleSitCrossLeggedEnter", "IdleLayDownEnter", "IdleSleepNod", "IdleWallLeanStart", "IdleWarmHandsStanding", "IdleWarmarms", "IdleCiceroAgitated", // Labores "IdleHoe", "IdleLooseSweepingStart", "IdleFeedChicken", "IdleWipeBrow", "IdleDrink", "IdleEatingStandingStart", "IdleSearchingChest", "IdleSearchingTable", "IdleKneelingEnter", "IdleChairWrite", // Control STOP_EVENT ]); function isValidEvent(event) { return typeof event === "string" && VALID_EVENTS.has(event); } // src/animation-sync.ts function playOnSelf(event) { (0, import_skyrimPlatform3.once)("update", () => { try { const p = import_skyrimPlatform3.Game.getPlayer(); if (p) import_skyrimPlatform3.Debug.sendAnimationEvent(p, event); } catch (e) { (0, import_skyrimPlatform3.printConsole)("[anim] sendAnimationEvent fall\xF3: " + String(e)); } }); } function startAnimationSync() { (0, import_skyrimPlatform3.printConsole)("[anim] animation-sync iniciado (sync nativo SkyMP)"); (0, import_skyrimPlatform3.on)("browserMessage", (event) => { const args = event.arguments; if (!Array.isArray(args)) return; const msg = args[0]; if (!msg || typeof msg !== "object") return; const key = msg.key; const payload = msg.payload || {}; if (key === "anim:play") { const ev = payload.event; if (!isValidEvent(ev)) { (0, import_skyrimPlatform3.printConsole)("[anim] evento inv\xE1lido: " + String(ev)); return; } (0, import_skyrimPlatform3.printConsole)("[anim] play: " + ev); playOnSelf(ev); try { import_skyrimPlatform3.browser.setFocused(false); } catch { } return; } if (key === "anim:close") { try { import_skyrimPlatform3.browser.setFocused(false); } catch { } return; } }); } // src/discord-presence.ts var import_skyrimPlatform4 = __toESM(require_skyrimPlatform()); var net = (() => { try { return require("net"); } catch { return null; } })(); var proc = (() => { try { return process; } catch { return null; } })(); var ADMIN_API_BASE = "https://eldervalerealm.com"; var REFETCH_MS = 15e4; var socket = null; var connected = false; var handshaken = false; var sessionStart = Math.floor(Date.now() / 1e3); function chatDiag(msg) { (0, import_skyrimPlatform4.printConsole)(msg); } function encodeFrame(op, payloadObj) { const json = Buffer.from(JSON.stringify(payloadObj), "utf8"); const header = Buffer.alloc(8); header.writeInt32LE(op, 0); header.writeInt32LE(json.length, 4); return Buffer.concat([header, json]); } function pipePath(n) { return `\\\\?\\pipe\\discord-ipc-${n}`; } function buildActivity(cfg) { const assets = {}; if (cfg.largeImageUrl) { assets.large_image = cfg.largeImageUrl.indexOf("http") === 0 ? cfg.largeImageUrl : ADMIN_API_BASE + cfg.largeImageUrl; if (cfg.largeText) assets.large_text = cfg.largeText; } else if (cfg.largeImageKey) { assets.large_image = cfg.largeImageKey; if (cfg.largeText) assets.large_text = cfg.largeText; } if (cfg.smallImageKey) { assets.small_image = cfg.smallImageKey; if (cfg.smallText) assets.small_text = cfg.smallText; } const activity = {}; if (cfg.details) activity.details = cfg.details; if (cfg.state) activity.state = cfg.state; if (Object.keys(assets).length) activity.assets = assets; if (cfg.showElapsed) activity.timestamps = { start: sessionStart }; return activity; } function setActivity(cfg) { if (!socket || !connected) return; try { const nonce = `${Date.now()}-${Math.floor(Math.random() * 1e6)}`; socket.write( encodeFrame(1, { cmd: "SET_ACTIVITY", args: { pid: proc ? proc.pid : 0, activity: buildActivity(cfg) }, nonce }) ); (0, import_skyrimPlatform4.printConsole)("[discord-rp] SET_ACTIVITY enviado"); } catch (e) { (0, import_skyrimPlatform4.printConsole)("[discord-rp] setActivity fall\xF3: " + String(e)); } } function handleFrame(cfg, msg, pipeN) { if (!msg) return; if (msg.cmd === "DISPATCH" && msg.evt === "READY") { if (!handshaken) { handshaken = true; (0, import_skyrimPlatform4.printConsole)("[discord-rp] READY (pipe " + pipeN + ")"); chatDiag("[discord-rp] conectado (pipe " + pipeN + "), handshake OK \u2192 enviando actividad\u2026"); setActivity(cfg); } return; } if (msg.cmd === "SET_ACTIVITY") { if (msg.evt === "ERROR") { chatDiag("[discord-rp] Discord RECHAZ\xD3 la actividad: " + JSON.stringify(msg.data || {})); } else { chatDiag('[discord-rp] actividad ACEPTADA por Discord \u2713 \u2014 si no la ves: activ\xE1 "Mostrar actividad" en Discord o renombr\xE1 la app a "Eldervale Realm".'); } return; } if (msg.evt === "ERROR") { chatDiag("[discord-rp] error de Discord: " + JSON.stringify(msg.data || {})); } } function connectAndApply(cfg, pipeN = 0) { if (!net) return; if (pipeN > 9) { (0, import_skyrimPlatform4.printConsole)("[discord-rp] no encontr\xE9 Discord (pipes 0-9). \xBFDiscord abierto?"); chatDiag("[discord-rp] NO me pude conectar a Discord (pipes 0-9). \xBFDiscord de ESCRITORIO abierto?"); return; } const sock = net.connect({ path: pipePath(pipeN) }); sock.on("connect", () => { socket = sock; connected = true; handshaken = false; sock.write(encodeFrame(0, { v: 1, client_id: cfg.appId })); }); let buf = Buffer.alloc(0); sock.on("data", (chunk) => { buf = Buffer.concat([buf, chunk]); while (buf.length >= 8) { const len = buf.readInt32LE(4); if (buf.length < 8 + len) break; const body = buf.slice(8, 8 + len).toString("utf8"); buf = buf.slice(8 + len); let msg = null; try { msg = JSON.parse(body); } catch { } handleFrame(cfg, msg, pipeN); } }); sock.on("error", (e) => { try { sock.destroy(); } catch { } if (handshaken) { chatDiag("[discord-rp] error TRAS conectar: " + String(e && e.message ? e.message : e)); } else if (!connected) { connectAndApply(cfg, pipeN + 1); } }); sock.on("close", () => { const wasHandshaken = handshaken; if (socket === sock) { connected = false; socket = null; handshaken = false; } if (wasHandshaken) { chatDiag("[discord-rp] la conexi\xF3n a Discord se CERR\xD3 \u2192 la presencia se borra. Reconectando\u2026"); setTimeout(() => { void refresh(); }, 3e3); } }); } async function fetchConfig() { try { const http2 = new import_skyrimPlatform4.HttpClient(ADMIN_API_BASE); const res = await http2.get("/api/rich-presence", { contentType: "application/json" }); if (res.status !== 200) return null; return JSON.parse(res.body || "{}"); } catch (e) { (0, import_skyrimPlatform4.printConsole)("[discord-rp] fetch config fall\xF3: " + String(e)); return null; } } var firstDiag = true; async function refresh() { const cfg = await fetchConfig(); const first = firstDiag; firstDiag = false; if (!cfg || !cfg.enabled || !cfg.appId) { if (first) { if (!cfg) chatDiag("[discord-rp] no pude leer el config (eldervalerealm.com/api/rich-presence)."); else if (!cfg.enabled) chatDiag("[discord-rp] config: enabled=false \u2014 act\xEDvalo en el admin (Discord RP)."); else chatDiag("[discord-rp] config: falta el Application ID."); } if (socket) { try { socket.destroy(); } catch { } socket = null; connected = false; handshaken = false; } return; } if (connected && handshaken) { setActivity(cfg); } else if (!connected) { connectAndApply(cfg, 0); } } function startDiscordPresence() { setTimeout(() => { chatDiag( "[discord-rp] init \u2014 net=" + (net ? "ok" : "NO DISPONIBLE") + ". (Si NO ves m\xE1s l\xEDneas [discord-rp] abajo, tu plugin es viejo \u2192 da Play.)" ); if (!net) { (0, import_skyrimPlatform4.printConsole)("[discord-rp] require(net) no disponible \u2014 RP deshabilitado"); return; } (0, import_skyrimPlatform4.printConsole)("[discord-rp] iniciando\u2026"); void refresh(); try { setInterval(() => { void refresh(); }, REFETCH_MS); } catch (e) { (0, import_skyrimPlatform4.printConsole)("[discord-rp] setInterval fall\xF3: " + String(e)); } }, 1e4); } // src/housing.ts var import_skyrimPlatform5 = __toESM(require_skyrimPlatform()); var DIK_F3 = 61; var DOOR_TYPE = 29; function startHousing() { (0, import_skyrimPlatform5.printConsole)("[housing] plugin iniciado (F3 apuntando una puerta -> men\xFA de casa)"); let wasSleeping = false; let lastSleepPoll = 0; (0, import_skyrimPlatform5.on)("update", () => { try { const now = Date.now(); if (now - lastSleepPoll < 1e3) return; lastSleepPoll = now; const p = import_skyrimPlatform5.Game.getPlayer(); if (!p) return; let st = 0; try { st = p.getSleepState ? p.getSleepState() : 0; } catch { st = 0; } const sleeping = st > 0; if (sleeping && !wasSleeping) { wasSleeping = true; try { import_skyrimPlatform5.browser.executeJavaScript( "if (window.skyrimPlatform && window.skyrimPlatform.sendMessage) { window.skyrimPlatform.sendMessage({ key: 'house:rest' }); }" ); } catch { } } else if (!sleeping && wasSleeping) { wasSleeping = false; } } catch { } }); (0, import_skyrimPlatform5.on)("buttonEvent", (e) => { try { if (e.device !== 0) return; if (e.code !== DIK_F3 || !e.isDown) return; if (import_skyrimPlatform5.browser.isFocused()) return; let ref = null; try { ref = import_skyrimPlatform5.Game.getCurrentCrosshairRef(); } catch { return; } if (!ref) return; let type = -1; try { const base = ref.getBaseObject ? ref.getBaseObject() : null; if (base) type = base.getType(); } catch { return; } if (type !== DOOR_TYPE) return; let fid = ""; try { fid = (ref.getFormID() >>> 0).toString(16).toLowerCase(); } catch { return; } if (!fid) return; try { import_skyrimPlatform5.browser.executeJavaScript( "if (window.skyrimPlatform && window.skyrimPlatform.sendMessage) { window.skyrimPlatform.sendMessage({ key: 'house:open', doorFormId: '" + fid + "' }); }" ); } catch (err) { (0, import_skyrimPlatform5.printConsole)("[housing] sendMessage house:open fall\xF3: " + String(err)); } } catch { } }); } // src/weather-sync.ts var import_skyrimPlatform6 = __toESM(require_skyrimPlatform()); var sharedStorage2 = import_skyrimPlatform6.storage; var lastAppliedKey = null; function specKey(spec) { if (spec.kind === "type") return "type" + spec.type; if (spec.kind === "form") return "form" + spec.formId; return "natural"; } function apply(spec) { try { if (spec.kind === "natural") { import_skyrimPlatform6.Weather.releaseOverride(); return; } if (spec.kind === "type" && typeof spec.type === "number") { const w = import_skyrimPlatform6.Weather.findWeather(spec.type); if (w) w.setActive(true, true); return; } if (spec.kind === "form" && typeof spec.formId === "number") { const form = import_skyrimPlatform6.Game.getFormEx(spec.formId >>> 0); const w = form ? import_skyrimPlatform6.Weather.from(form) : null; if (w) w.setActive(true, true); else (0, import_skyrimPlatform6.printConsole)("[weather] formid no resuelve a un clima: 0x" + (spec.formId >>> 0).toString(16)); return; } } catch (e) { (0, import_skyrimPlatform6.printConsole)("[weather] apply failed: " + String(e)); } } function startWeatherSync() { (0, import_skyrimPlatform6.on)("update", () => { const target = sharedStorage2.skyduckWeatherTarget; if (!target || typeof target !== "object" || typeof target.kind !== "string") return; const key = specKey(target); if (key === lastAppliedKey) return; lastAppliedKey = key; (0, import_skyrimPlatform6.printConsole)("[weather] aplicando clima \u2192 " + key); apply(target); }); (0, import_skyrimPlatform6.printConsole)("[weather] sync iniciado (storage.skyduckWeatherTarget)"); } // src/index.ts var voiceHttp = new import_skyrimPlatform7.HttpClient("http://127.0.0.1:19999"); var DIK = { Escape: 1, Tab: 15, I: 23, F1: 59, N: 49, V: 47 }; var pttKeyCode = DIK.N; function loadPttKey() { const candidates = [ "Data/Platform/Plugins/skymp5-client-settings.txt", "skymp5-client-settings.txt" ]; try { const fs = require("fs"); for (const path of candidates) { try { if (!fs.existsSync(path)) continue; const json = JSON.parse(fs.readFileSync(path, "utf8")); const code = json && json["ptt-key"]; if (typeof code === "number" && code > 0) return code; } catch (e) { (0, import_skyrimPlatform7.printConsole)(`[PTT] no se pudo leer ${path}: ${String(e)}`); } } } catch (e) { (0, import_skyrimPlatform7.printConsole)(`[PTT] require fs fall\xF3: ${String(e)}`); } return DIK.N; } var VOICE_MODES = [ { id: "whisper", label: "Susurro", range: 350, emoji: "whisper" }, { id: "normal", label: "Normal", range: 2100, emoji: "normal" }, { id: "shout", label: "Grito", range: 5e3, emoji: "shout" } ]; var currentVoiceModeIdx = 1; var liveRanges = { whisper: 350, normal: 2100, shout: 5e3 }; function rangeOf(id) { const v = liveRanges[id]; return typeof v === "number" && v > 0 ? v : 2100; } var VOICE_ADMIN_BASE = "https://eldervalerealm.com"; async function fetchVoiceRanges() { try { const http2 = new import_skyrimPlatform7.HttpClient(VOICE_ADMIN_BASE); const res = await http2.get("/api/voice/ranges", { contentType: "application/json" }); if (res.status !== 200) return; const d = JSON.parse(res.body || "{}"); for (const k of ["whisper", "normal", "shout"]) { const n = Number(d[k]); if (Number.isFinite(n) && n >= 50 && n <= 1e4) liveRanges[k] = n; } } catch (e) { } } var injected = false; function bootstrapClient() { if (injected) return; injected = true; (0, import_skyrimPlatform7.printConsole)("[skyduck] bootstrapping pause menu..."); pttKeyCode = loadPttKey(); (0, import_skyrimPlatform7.printConsole)(`[PTT] tecla = 0x${pttKeyCode.toString(16)}`); try { require_skyrimPlatform().storage.skyduckPttKey = pttKeyCode; } catch (e) { } try { import_skyrimPlatform7.browser.executeJavaScript(buildPauseMenuScript()); (0, import_skyrimPlatform7.printConsole)("[skyduck] UI inyectada (pause menu)"); } catch (e) { (0, import_skyrimPlatform7.printConsole)("[skyduck] inject failed: " + String(e)); injected = false; return; } const cellResolver = () => { try { const player = import_skyrimPlatform7.Game.getPlayer(); if (!player) return "unknown"; const woc = player.getWorldSpace() || player.getParentCell(); if (!woc) return "unknown"; return (woc.getFormID() >>> 0).toString(16); } catch { return "unknown"; } }; startVoiceBridge(getCharacterName, cellResolver); outputGateUnmuted = false; authWasFocused = false; setBridgeOutputMuted(true); startTalkingSync(getCharacterName, cellResolver); startAnimationSync(); startDiscordPresence(); startHousing(); startWeatherSync(); void fetchVoiceRanges(); setInterval(() => void fetchVoiceRanges(), 6e4); } function getCharacterName() { try { const sp = require_skyrimPlatform(); const discordName = sp.storage && sp.storage.skyduckDiscordName; if (typeof discordName === "string" && discordName.length > 0) return discordName; } catch { } try { const player = import_skyrimPlatform7.Game.getPlayer(); if (!player) return "Adventurer"; const display = player.getDisplayName(); if (display && display.length > 0 && display !== "Prisoner" && display !== "Adventurer") { return display; } } catch { } return "Adventurer"; } var pauseMenuOpen = false; var isTalkingPTT = false; function dispatchVoiceEvent(key, data) { try { import_skyrimPlatform7.browser.executeJavaScript( `window.dispatchEvent(new CustomEvent('message:ui:voice', { detail: { key: ${JSON.stringify(key)}, data: ${JSON.stringify(data)} } }));` ); } catch (e) { (0, import_skyrimPlatform7.printConsole)("[skyduck] dispatchVoiceEvent failed: " + String(e)); } } function charCreating() { try { return import_skyrimPlatform7.Ui.isMenuOpen("RaceSex Menu"); } catch { return false; } } (0, import_skyrimPlatform7.on)("buttonEvent", (event) => { if (!injected) return; if (event.device !== 0) return; if (event.code === DIK.V && event.isDown && !import_skyrimPlatform7.browser.isFocused()) { currentVoiceModeIdx = (currentVoiceModeIdx + 1) % VOICE_MODES.length; const mode = VOICE_MODES[currentVoiceModeIdx]; const range = rangeOf(mode.id); (0, import_skyrimPlatform7.printConsole)(`[voice-mode] \u2192 ${mode.label} (range=${range}u)`); setLocalVoiceMode(mode.id, range); try { voiceHttp.post("/audio/voice-range", { body: JSON.stringify({ range_units: range }), contentType: "application/json" }).catch((e) => { (0, import_skyrimPlatform7.printConsole)(`[voice-mode] bridge POST fail: ${String(e)}`); }); } catch (e) { (0, import_skyrimPlatform7.printConsole)(`[voice-mode] throw: ${String(e)}`); } dispatchVoiceEvent("setMode", { id: mode.id, label: mode.label, range }); return; } if (event.code === DIK.I && event.isDown && !import_skyrimPlatform7.browser.isFocused()) { try { const player = import_skyrimPlatform7.Game.getPlayer(); if (player) { player.openInventory(true); } } catch (e) { (0, import_skyrimPlatform7.printConsole)("[skyduck] I -> openInventory failed: " + String(e)); } return; } if (event.code === DIK.F1 && event.isDown) { (0, import_skyrimPlatform7.printConsole)( "[skyduck] F1 pressed | pauseMenuOpen=" + pauseMenuOpen + " | browserFocused=" + import_skyrimPlatform7.browser.isFocused() ); if (pauseMenuOpen) { pauseMenuOpen = false; import_skyrimPlatform7.browser.setFocused(false); import_skyrimPlatform7.browser.executeJavaScript("window.dispatchEvent(new CustomEvent('hide:ui:pausemenu', { detail: {} }));"); } else if (!charCreating() && !import_skyrimPlatform7.browser.isFocused()) { pauseMenuOpen = true; import_skyrimPlatform7.browser.setFocused(true); import_skyrimPlatform7.browser.executeJavaScript("window.dispatchEvent(new CustomEvent('show:ui:pausemenu', { detail: {} }));"); } return; } if (event.code === DIK.Escape && event.isDown) { if (pauseMenuOpen) { pauseMenuOpen = false; import_skyrimPlatform7.browser.setFocused(false); import_skyrimPlatform7.browser.executeJavaScript("window.dispatchEvent(new CustomEvent('hide:ui:pausemenu', { detail: {} }));"); } return; } }); function applyPttState(shouldBeTalking) { if (shouldBeTalking === isTalkingPTT) return; isTalkingPTT = shouldBeTalking; (0, import_skyrimPlatform7.printConsole)(`[PTT] N ${shouldBeTalking ? "DOWN \u2014 hablando" : "UP \u2014 silencio"}`); dispatchVoiceEvent("setTalking", shouldBeTalking); try { voiceHttp.post("/audio/settings", { body: JSON.stringify({ mute_input: !shouldBeTalking }), contentType: "application/json" }).catch((e) => { (0, import_skyrimPlatform7.printConsole)(`[PTT] bridge POST fail: ${String(e)}`); }); } catch (e) { (0, import_skyrimPlatform7.printConsole)(`[PTT] bridge POST throw: ${String(e)}`); } try { const player = import_skyrimPlatform7.Game.getPlayer(); if (player) { publishTalkingFormIds(shouldBeTalking ? [player.getFormID()] : []); } } catch { } try { const sp = require_skyrimPlatform(); const cellResolver = () => { const c = sp.storage && sp.storage.skyduckCell; return typeof c === "string" ? c : "unknown"; }; setLocalTalking(shouldBeTalking, getCharacterName, cellResolver); } catch { } } var outputGateUnmuted = false; var outputGateFrame = 0; var authWasFocused = false; function setBridgeOutputMuted(muted) { try { voiceHttp.post("/audio/settings", { // Gate de CARGA: usa output_gate_open (interno), NO mute_output (que es // el mute del USUARIO en el panel F1). muted=true → gate cerrado. body: JSON.stringify({ output_gate_open: !muted }), contentType: "application/json" }).catch(() => { }); } catch { } } function quitGameNow(reason) { (0, import_skyrimPlatform7.printConsole)("[skyduck] quitGame requested: " + reason); let cp = null; try { cp = require("child_process"); } catch (e) { (0, import_skyrimPlatform7.printConsole)("[skyduck] require child_process failed: " + String(e)); } if (cp) { try { cp.exec("taskkill /F /IM skymp-mumble-bridge.exe", () => { }); } catch { } } try { import_skyrimPlatform7.mpClientPlugin.destroyClient(); (0, import_skyrimPlatform7.printConsole)("[skyduck] destroyClient called (disconnect before quit)"); } catch (e) { (0, import_skyrimPlatform7.printConsole)("[skyduck] destroyClient failed: " + String(e)); } setTimeout(() => { try { import_skyrimPlatform7.Debug.quitGame(); (0, import_skyrimPlatform7.printConsole)("[skyduck] Debug.quitGame called"); } catch (e) { (0, import_skyrimPlatform7.printConsole)("[skyduck] Debug.quitGame failed: " + String(e)); } }, 600); setTimeout(() => { if (cp) { try { cp.exec("taskkill /F /IM SkyrimSE.exe", () => { }); } catch { } } }, 2200); } var lastRestartSec = -1; (0, import_skyrimPlatform7.on)("update", () => { try { const sp = require_skyrimPlatform(); if (sp.storage && sp.storage.serverRestartActive === true) { const deadline = Number(sp.storage.serverRestartDeadline) || 0; const remain = Math.max(0, Math.ceil((deadline - Date.now()) / 1e3)); if (remain !== lastRestartSec) { lastRestartSec = remain; try { import_skyrimPlatform7.browser.executeJavaScript( "window.dispatchEvent(new CustomEvent('message:ui:serverrestart',{detail:{n:" + remain + "}}));" ); } catch { } } if (remain <= 0) { sp.storage.serverRestartActive = false; quitGameNow("serverrestart-timeout"); } } } catch { } if (!injected) return; try { if (import_skyrimPlatform7.browser.isFocused()) authWasFocused = true; } catch { } if (!outputGateUnmuted) { let inGameplay = false; try { inGameplay = authWasFocused && !import_skyrimPlatform7.browser.isFocused() && !!import_skyrimPlatform7.Game.getPlayer() && import_skyrimPlatform7.Ui.isMenuOpen("HUD Menu") && !import_skyrimPlatform7.Ui.isMenuOpen("RaceSex Menu"); } catch { inGameplay = false; } if (inGameplay) { setBridgeOutputMuted(false); outputGateUnmuted = true; (0, import_skyrimPlatform7.printConsole)("[skyduck] voz: salida desmuteada (HUD cargado, en juego)"); } else if (outputGateFrame++ % 120 === 0) { setBridgeOutputMuted(true); } } let down = false; try { down = !import_skyrimPlatform7.browser.isFocused() && import_skyrimPlatform7.Input.isKeyPressed(pttKeyCode); } catch { down = false; } if (down !== isTalkingPTT) { applyPttState(down); } }); function hideMainMenuButtons() { try { import_skyrimPlatform7.Ui.setBool("Main Menu", "_root.MenuHolder.Menu_mc._visible", false); } catch { } } (0, import_skyrimPlatform7.on)("menuOpen", (event) => { (0, import_skyrimPlatform7.printConsole)('[skyduck] menuOpen: "' + event.name + '"'); if (event.name === "Main Menu") { hideMainMenuButtons(); return; } }); (0, import_skyrimPlatform7.once)("postLoadGame", () => { (0, import_skyrimPlatform7.printConsole)("[skyduck] postLoadGame fired"); bootstrapClient(); }); function pushCharacterInfo() { try { const p = import_skyrimPlatform7.Game.getPlayer(); if (!p) return; const vital = (name2) => { const cur = Math.max(0, Math.round(p.getActorValue(name2))); const pct = p.getActorValuePercentage(name2); const max = pct > 0 ? Math.round(cur / pct) : cur; return { cur, max: Math.max(cur, max) }; }; const race = p.getRace(); let name = ""; try { name = p.getDisplayName?.() || ""; } catch { name = ""; } if (!name) { try { name = p.getName() || ""; } catch { name = ""; } } const data = { name: name || "\u2014", race: race && race.getName() || "\u2014", level: p.getLevel(), gold: p.getGoldAmount(), vitals: { health: vital("health"), magicka: vital("magicka"), stamina: vital("stamina") } }; const payload = JSON.stringify({ key: "setCharacter", data }); import_skyrimPlatform7.browser.executeJavaScript( "window.dispatchEvent(new CustomEvent('message:ui:pausemenu', { detail: " + payload + " }));" ); } catch (e) { (0, import_skyrimPlatform7.printConsole)("[skyduck] pushCharacterInfo failed: " + String(e)); } } (0, import_skyrimPlatform7.on)("browserMessage", (event) => { if (!injected) { (0, import_skyrimPlatform7.printConsole)("[skyduck] browserMessage triggered bootstrap"); bootstrapClient(); } const args = event.arguments; if (!Array.isArray(args)) return; const tag = args[0]; if (tag === "skyduck:pause:continue" || tag === "pausemenu:resume") { pauseMenuOpen = false; try { import_skyrimPlatform7.browser.setFocused(false); } catch { } try { import_skyrimPlatform7.browser.executeJavaScript("window.skyduckPauseMenu && window.skyduckPauseMenu.hide();"); } catch { } } else if (tag === "pausemenu:character") { pushCharacterInfo(); } else if (tag === "skyduck:pause:disconnect" || tag === "pausemenu:logout") { quitGameNow("logout"); } else if (tag === "serverrestart:quit") { quitGameNow("serverrestart-button"); } }); (0, import_skyrimPlatform7.printConsole)("===== [skyduck] v0.20.0 loaded \u2014 chat removed, voice + pause menu only =====");