diff --git a/addons/yt_world/data/codex_talent.xml b/addons/yt_world/data/codex_talent.xml index a3dd19f..df69e35 100644 --- a/addons/yt_world/data/codex_talent.xml +++ b/addons/yt_world/data/codex_talent.xml @@ -101,7 +101,7 @@ talent_harvest_blessing 丰收祝福 - 🌾 + assets/icons/talents/harvest_blessing.png uncommon 被动 农作物产量+15%;生长速度+10% @@ -115,7 +115,7 @@ talent_soul_drain 汲魂者之触 - 💀 + assets/icons/talents/soul_drain.png epic 被动 造成伤害的8%转化为生命回复;对亡灵单位翻倍 @@ -129,7 +129,7 @@ talent_elemental_affinity 元素亲和 - 🔥 + assets/icons/talents/elemental_affinity.png legendary 被动 元素伤害+20%;受到元素伤害-15% @@ -143,7 +143,7 @@ talent_swift_stride 迅捷步伐 - 💨 + assets/icons/talents/swift_stride.png common 被动 基础移速+15%;脱离战斗后额外+10% @@ -157,7 +157,7 @@ talent_precise_strike 精准打击 - 🎯 + assets/icons/talents/precise_strike.png uncommon 被动 暴击率+8%;暴击伤害+15% @@ -171,7 +171,7 @@ talent_tough_body 坚韧体魄 - 🛡️ + assets/icons/talents/tough_body.png common 被动 最大生命+12%;受到物理伤害-5% @@ -185,7 +185,7 @@ talent_nature_whisper 自然低语 - 🍃 + assets/icons/talents/nature_whisper.png rare 被动 采集速度+20%;稀有材料发现率+5% @@ -199,7 +199,7 @@ talent_shadow_stealth 暗影潜行 - 🌑 + assets/icons/talents/shadow_stealth.png epic 被动 夜间隐蔽+30%;从隐蔽状态发起的首击伤害+25% diff --git a/assets/game-api.js b/assets/game-api.js index 9d1898d..fb83046 100644 --- a/assets/game-api.js +++ b/assets/game-api.js @@ -17,16 +17,15 @@ const _isDevPort = (port) => (port === 80 && (location.port === '' || location.port === '80')); const API_CONFIG = { - // 本地开发一律跨域直连 Odoo 8069(python http.server 无代理能力); - // 仅 8080 dev-server(自带反向代理到 Odoo)才走同源空 base。 base: _isLocalDev() - ? (_isDevPort(8080) ? '' : `http://${location.hostname}:8069`) - : '', - db: _isLocalDev() ? 'game_a' : 'game', + ? ((_isDevPort(8080) || _isDevPort(8888)) ? '' : `http://${location.hostname}:8080`) + : '', + db: _isLocalDev() ? 'study' : 'game', }; -// 世界(yt_world)所在库:与 API_CONFIG.db 保持一致(本地均为 game_a),避免跨库导致 auth 失败。 -const WORLD_DB = API_CONFIG.db; +// 世界(yt_world)所在库:本地 Odoo 钉 yt_game;线上游戏库名为 game。按域名自适应,git 同步不冲突。 +const WORLD_DB = _isLocalDev() ? 'yt_game' : 'game'; + // Odoo 后端基础 URL(世界 API 专用,兼容本地跨域 / 生产同域) const WORLD_BASE = API_CONFIG.base || ''; diff --git a/index.html b/index.html index bf2d4c5..bdb1818 100644 --- a/index.html +++ b/index.html @@ -369,83 +369,30 @@ function playIntroSound() { const now = ctx.currentTime; const master = ctx.createGain(); master.gain.setValueAtTime(0.0001, now); - master.gain.linearRampToValueAtTime(0.55, now + 0.06); + master.gain.linearRampToValueAtTime(0.5, now + 0.05); master.connect(ctx.destination); - // 1. 深空底噪:50Hz + 100Hz 正弦,贯穿全程 - [50, 100].forEach(f => { - const o = ctx.createOscillator(); o.type = 'sine'; o.frequency.value = f; - const g = ctx.createGain(); - g.gain.setValueAtTime(0.0001, now); - g.gain.linearRampToValueAtTime(0.05, now + 0.8); - g.gain.setValueAtTime(0.05, now + 4.2); - g.gain.linearRampToValueAtTime(0.0001, now + 5.2); - o.connect(g).connect(master); o.start(now); o.stop(now + 5.3); - }); + // 干净的「铃」音:正弦主音 + 高八度泛音增亮,平滑指数衰减 + function bell(freq, start, dur, vol) { + const o1 = ctx.createOscillator(); o1.type = 'sine'; o1.frequency.value = freq; + const o2 = ctx.createOscillator(); o2.type = 'sine'; o2.frequency.value = freq * 2; + const g1 = ctx.createGain(); + g1.gain.setValueAtTime(0.0001, start); + g1.gain.exponentialRampToValueAtTime(vol, start + 0.012); + g1.gain.exponentialRampToValueAtTime(0.0001, start + dur); + const g2 = ctx.createGain(); + g2.gain.setValueAtTime(0.0001, start); + g2.gain.exponentialRampToValueAtTime(vol * 0.3, start + 0.012); + g2.gain.exponentialRampToValueAtTime(0.0001, start + dur * 0.55); + o1.connect(g1).connect(master); o1.start(start); o1.stop(start + dur + 0.05); + o2.connect(g2).connect(master); o2.start(start); o2.stop(start + dur + 0.05); + } - // 2. 低频滤波噪声扫过(0.2s - 1.5s) - const len = Math.floor(ctx.sampleRate * 1.4); - const buf = ctx.createBuffer(1, len, ctx.sampleRate); - const data = buf.getChannelData(0); - for (let i = 0; i < len; i++) data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / len, 1.4); - const noise = ctx.createBufferSource(); noise.buffer = buf; - const nf = ctx.createBiquadFilter(); nf.type = 'lowpass'; - nf.frequency.setValueAtTime(800, now + 0.2); nf.frequency.linearRampToValueAtTime(2200, now + 0.9); nf.frequency.linearRampToValueAtTime(600, now + 1.5); - const ng = ctx.createGain(); - ng.gain.setValueAtTime(0.0001, now + 0.2); ng.gain.linearRampToValueAtTime(0.16, now + 0.7); ng.gain.linearRampToValueAtTime(0.0001, now + 1.5); - noise.connect(nf).connect(ng).connect(master); noise.start(now + 0.2); noise.stop(now + 1.6); - - // 3. 数字 glitch 短鸣(0.5 / 0.9 / 1.3s) - [0.5, 0.9, 1.3].forEach((t, i) => { - const o = ctx.createOscillator(); o.type = 'square'; - o.frequency.setValueAtTime(1200 + i * 600, now + t); o.frequency.exponentialRampToValueAtTime(1800 + i * 400, now + t + 0.04); - const g = ctx.createGain(); - g.gain.setValueAtTime(0.0001, now + t); g.gain.exponentialRampToValueAtTime(0.05, now + t + 0.01); g.gain.exponentialRampToValueAtTime(0.0001, now + t + 0.08); - o.connect(g).connect(master); o.start(now + t); o.stop(now + t + 0.1); - }); - - // 4. 上扬合成器扫频(1.2s - 2.7s) - const sweep = ctx.createOscillator(); sweep.type = 'sawtooth'; - sweep.frequency.setValueAtTime(180, now + 1.2); sweep.frequency.exponentialRampToValueAtTime(1100, now + 2.5); - const sg = ctx.createGain(); - sg.gain.setValueAtTime(0.0001, now + 1.2); sg.gain.linearRampToValueAtTime(0.11, now + 2.0); sg.gain.linearRampToValueAtTime(0.0001, now + 2.7); - const sf = ctx.createBiquadFilter(); sf.type = 'lowpass'; - sf.frequency.setValueAtTime(600, now + 1.2); sf.frequency.linearRampToValueAtTime(3200, now + 2.5); - sweep.connect(sf).connect(sg).connect(master); sweep.start(now + 1.2); sweep.stop(now + 2.8); - - // 5. 冲击点(2.4s):噪声爆发 + 低频重击 - const impactLen = Math.floor(ctx.sampleRate * 0.35); - const impactBuf = ctx.createBuffer(1, impactLen, ctx.sampleRate); - const id = impactBuf.getChannelData(0); - for (let i = 0; i < impactLen; i++) id[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / impactLen, 1.2); - const impactNoise = ctx.createBufferSource(); impactNoise.buffer = impactBuf; - const impactF = ctx.createBiquadFilter(); impactF.type = 'bandpass'; impactF.frequency.value = 450; impactF.Q.value = 1.2; - const impactG = ctx.createGain(); - impactG.gain.setValueAtTime(0.0001, now + 2.4); impactG.gain.exponentialRampToValueAtTime(0.24, now + 2.42); impactG.gain.exponentialRampToValueAtTime(0.0001, now + 2.85); - impactNoise.connect(impactF).connect(impactG).connect(master); impactNoise.start(now + 2.4); impactNoise.stop(now + 2.9); - const thump = ctx.createOscillator(); thump.type = 'sine'; - thump.frequency.setValueAtTime(90, now + 2.4); thump.frequency.exponentialRampToValueAtTime(35, now + 2.8); - const thumpG = ctx.createGain(); - thumpG.gain.setValueAtTime(0.0001, now + 2.4); thumpG.gain.exponentialRampToValueAtTime(0.28, now + 2.43); thumpG.gain.exponentialRampToValueAtTime(0.0001, now + 2.9); - thump.connect(thumpG).connect(master); thump.start(now + 2.4); thump.stop(now + 2.95); - - // 6. 科技琶音(2.9s - 4.3s) - [523.25, 659.25, 783.99, 1046.50, 783.99, 659.25].forEach((f, i) => { - const t = now + 2.9 + i * 0.24; - const o = ctx.createOscillator(); o.type = 'triangle'; o.frequency.value = f; - const g = ctx.createGain(); - g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(0.08, t + 0.02); g.gain.exponentialRampToValueAtTime(0.0001, t + 0.55); - o.connect(g).connect(master); o.start(t); o.stop(t + 0.6); - }); - - // 7. 最终揭示和弦(4.4s - 5.2s) - [392.00, 523.25, 659.25, 783.99].forEach((f, i) => { - const t = now + 4.4 + i * 0.04; - const o = ctx.createOscillator(); o.type = 'sine'; o.frequency.value = f; - const g = ctx.createGain(); - g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(0.12, t + 0.05); g.gain.exponentialRampToValueAtTime(0.0001, t + 1.0); - o.connect(g).connect(master); o.start(t); o.stop(t + 1.1); - }); + // 开场:极轻星光一闪(光点感) + bell(1046.50, now + 0.2, 0.45, 0.06); + // 揭示「叮咚」门铃:高音 ding -> 低音 dong + bell(1567.98, now + 3.6, 0.55, 0.22); // G6 叮 + bell(1046.50, now + 4.25, 0.95, 0.22); // C6 咚 return master; } diff --git a/world.html b/world.html index 7d85868..8c49899 100644 --- a/world.html +++ b/world.html @@ -7436,83 +7436,30 @@ function playIntroSound() { const now = ctx.currentTime; const master = ctx.createGain(); master.gain.setValueAtTime(0.0001, now); - master.gain.linearRampToValueAtTime(0.55, now + 0.06); + master.gain.linearRampToValueAtTime(0.5, now + 0.05); master.connect(ctx.destination); - // 1. 深空底噪:50Hz + 100Hz 正弦,贯穿全程 - [50, 100].forEach(f => { - const o = ctx.createOscillator(); o.type = 'sine'; o.frequency.value = f; - const g = ctx.createGain(); - g.gain.setValueAtTime(0.0001, now); - g.gain.linearRampToValueAtTime(0.05, now + 0.8); - g.gain.setValueAtTime(0.05, now + 4.2); - g.gain.linearRampToValueAtTime(0.0001, now + 5.2); - o.connect(g).connect(master); o.start(now); o.stop(now + 5.3); - }); + // 干净的「铃」音:正弦主音 + 高八度泛音增亮,平滑指数衰减 + function bell(freq, start, dur, vol) { + const o1 = ctx.createOscillator(); o1.type = 'sine'; o1.frequency.value = freq; + const o2 = ctx.createOscillator(); o2.type = 'sine'; o2.frequency.value = freq * 2; + const g1 = ctx.createGain(); + g1.gain.setValueAtTime(0.0001, start); + g1.gain.exponentialRampToValueAtTime(vol, start + 0.012); + g1.gain.exponentialRampToValueAtTime(0.0001, start + dur); + const g2 = ctx.createGain(); + g2.gain.setValueAtTime(0.0001, start); + g2.gain.exponentialRampToValueAtTime(vol * 0.3, start + 0.012); + g2.gain.exponentialRampToValueAtTime(0.0001, start + dur * 0.55); + o1.connect(g1).connect(master); o1.start(start); o1.stop(start + dur + 0.05); + o2.connect(g2).connect(master); o2.start(start); o2.stop(start + dur + 0.05); + } - // 2. 低频滤波噪声扫过(0.2s - 1.5s) - const len = Math.floor(ctx.sampleRate * 1.4); - const buf = ctx.createBuffer(1, len, ctx.sampleRate); - const data = buf.getChannelData(0); - for (let i = 0; i < len; i++) data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / len, 1.4); - const noise = ctx.createBufferSource(); noise.buffer = buf; - const nf = ctx.createBiquadFilter(); nf.type = 'lowpass'; - nf.frequency.setValueAtTime(800, now + 0.2); nf.frequency.linearRampToValueAtTime(2200, now + 0.9); nf.frequency.linearRampToValueAtTime(600, now + 1.5); - const ng = ctx.createGain(); - ng.gain.setValueAtTime(0.0001, now + 0.2); ng.gain.linearRampToValueAtTime(0.16, now + 0.7); ng.gain.linearRampToValueAtTime(0.0001, now + 1.5); - noise.connect(nf).connect(ng).connect(master); noise.start(now + 0.2); noise.stop(now + 1.6); - - // 3. 数字 glitch 短鸣(0.5 / 0.9 / 1.3s) - [0.5, 0.9, 1.3].forEach((t, i) => { - const o = ctx.createOscillator(); o.type = 'square'; - o.frequency.setValueAtTime(1200 + i * 600, now + t); o.frequency.exponentialRampToValueAtTime(1800 + i * 400, now + t + 0.04); - const g = ctx.createGain(); - g.gain.setValueAtTime(0.0001, now + t); g.gain.exponentialRampToValueAtTime(0.05, now + t + 0.01); g.gain.exponentialRampToValueAtTime(0.0001, now + t + 0.08); - o.connect(g).connect(master); o.start(now + t); o.stop(now + t + 0.1); - }); - - // 4. 上扬合成器扫频(1.2s - 2.7s) - const sweep = ctx.createOscillator(); sweep.type = 'sawtooth'; - sweep.frequency.setValueAtTime(180, now + 1.2); sweep.frequency.exponentialRampToValueAtTime(1100, now + 2.5); - const sg = ctx.createGain(); - sg.gain.setValueAtTime(0.0001, now + 1.2); sg.gain.linearRampToValueAtTime(0.11, now + 2.0); sg.gain.linearRampToValueAtTime(0.0001, now + 2.7); - const sf = ctx.createBiquadFilter(); sf.type = 'lowpass'; - sf.frequency.setValueAtTime(600, now + 1.2); sf.frequency.linearRampToValueAtTime(3200, now + 2.5); - sweep.connect(sf).connect(sg).connect(master); sweep.start(now + 1.2); sweep.stop(now + 2.8); - - // 5. 冲击点(2.4s):噪声爆发 + 低频重击 - const impactLen = Math.floor(ctx.sampleRate * 0.35); - const impactBuf = ctx.createBuffer(1, impactLen, ctx.sampleRate); - const id = impactBuf.getChannelData(0); - for (let i = 0; i < impactLen; i++) id[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / impactLen, 1.2); - const impactNoise = ctx.createBufferSource(); impactNoise.buffer = impactBuf; - const impactF = ctx.createBiquadFilter(); impactF.type = 'bandpass'; impactF.frequency.value = 450; impactF.Q.value = 1.2; - const impactG = ctx.createGain(); - impactG.gain.setValueAtTime(0.0001, now + 2.4); impactG.gain.exponentialRampToValueAtTime(0.24, now + 2.42); impactG.gain.exponentialRampToValueAtTime(0.0001, now + 2.85); - impactNoise.connect(impactF).connect(impactG).connect(master); impactNoise.start(now + 2.4); impactNoise.stop(now + 2.9); - const thump = ctx.createOscillator(); thump.type = 'sine'; - thump.frequency.setValueAtTime(90, now + 2.4); thump.frequency.exponentialRampToValueAtTime(35, now + 2.8); - const thumpG = ctx.createGain(); - thumpG.gain.setValueAtTime(0.0001, now + 2.4); thumpG.gain.exponentialRampToValueAtTime(0.28, now + 2.43); thumpG.gain.exponentialRampToValueAtTime(0.0001, now + 2.9); - thump.connect(thumpG).connect(master); thump.start(now + 2.4); thump.stop(now + 2.95); - - // 6. 科技琶音(2.9s - 4.3s) - [523.25, 659.25, 783.99, 1046.50, 783.99, 659.25].forEach((f, i) => { - const t = now + 2.9 + i * 0.24; - const o = ctx.createOscillator(); o.type = 'triangle'; o.frequency.value = f; - const g = ctx.createGain(); - g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(0.08, t + 0.02); g.gain.exponentialRampToValueAtTime(0.0001, t + 0.55); - o.connect(g).connect(master); o.start(t); o.stop(t + 0.6); - }); - - // 7. 最终揭示和弦(4.4s - 5.2s) - [392.00, 523.25, 659.25, 783.99].forEach((f, i) => { - const t = now + 4.4 + i * 0.04; - const o = ctx.createOscillator(); o.type = 'sine'; o.frequency.value = f; - const g = ctx.createGain(); - g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(0.12, t + 0.05); g.gain.exponentialRampToValueAtTime(0.0001, t + 1.0); - o.connect(g).connect(master); o.start(t); o.stop(t + 1.1); - }); + // 开场:极轻星光一闪(光点感) + bell(1046.50, now + 0.2, 0.45, 0.06); + // 揭示「叮咚」门铃:高音 ding -> 低音 dong + bell(1567.98, now + 3.6, 0.55, 0.22); // G6 叮 + bell(1046.50, now + 4.25, 0.95, 0.22); // C6 咚 return master; } @@ -8563,6 +8510,12 @@ if (typeof fitWorld === 'function') fitWorld(); font-size:22px;line-height:1;color:#9cf;transition:transform .22s ease,box-shadow .18s;} #worldChat.collapsed #wcBubble{display:flex;} #wcBubble:hover{transform:scale(1.1);box-shadow:0 0 16px rgba(139,92,246,.35);} +/* ===== 最小化态:缩小版完整聊天框(头部+消息区+输入条齐全,总高约为展开态一半) ===== */ +#worldChat.minified{width:340px;max-height:206px;} +#worldChat.minified #wcPanel{display:flex;max-height:200px;} +#worldChat.minified #wcBody{height:100px;max-height:100px;} +#worldChat.minified #wcEmoji, +#worldChat.minified #wcMention{display:none !important;} #wcBadge{position:absolute;top:-2px;right:-4px;min-width:18px;height:18px;border-radius:9px; background:linear-gradient(135deg,#e74c3c,#c0392b);color:#fff;font-size:10px;font-weight:700; display:flex;align-items:center;justify-content:center;padding:0 5px;line-height:1; @@ -8581,6 +8534,9 @@ if (typeof fitWorld === 'function') fitWorld(); transition:border-color .18s,background .18s;} .wc-emoji-btn:hover{background:rgba(139,92,246,.22);border-color:rgba(139,92,246,.4);} .wc-emoji-btn.active{background:rgba(139,92,246,.32);border-color:rgba(139,92,246,.5);} +/* 收起/展开切换按钮:最小化态显示 ▲,展开态显示 — */ +.wc-toggle{transition:background .18s,color .18s;} +.wc-toggle:hover{background:rgba(139,92,246,.28);color:#fff;} .wc-emoji-box{position:absolute;bottom:46px;left:10px;right:10px;overflow:hidden;scrollbar-width:none; display:grid;grid-template-columns:repeat(auto-fill,minmax(30px,1fr));gap:2px;padding:6px; background:rgba(8,14,28,.97);border:1px solid rgba(139,92,246,.4);border-radius:10px; @@ -8659,7 +8615,7 @@ if (typeof fitWorld === 'function') fitWorld(); .wc-msg.guide .wc-name{color:#c4b5fd;} .wc-msg.guide .wc-bubble{background:linear-gradient(135deg,#2a2150,#3b2f63);border:1px solid #4c3a82;color:#ede9fe;} -
+
💬 @@ -8670,7 +8626,7 @@ if (typeof fitWorld === 'function') fitWorld();
- — + —
@@ -8700,6 +8656,8 @@ if (typeof fitWorld === 'function') fitWorld(); let pollTimer = null; const badge = document.getElementById('wcBadge'); const mentionBox = document.getElementById('wcMention'); + const wcSend = document.getElementById('wcSend'); + const toggleBtn = document.getElementById('wcToggleBtn'); function totalUnread(){ let s = 0; for (const k in unreadByChannel) s += (unreadByChannel[k] || 0); return s; } function updateBadge(){ @@ -8818,11 +8776,33 @@ if (typeof fitWorld === 'function') fitWorld(); loadMessages(true); updateChannelOptions(); updateBadge(); }); + // 头部按钮随状态切换:最小化态 ▲(展开),展开态 —(收起) + function syncChatState(){ + if(!toggleBtn) return; + const minified = chat.classList.contains('minified'); + toggleBtn.textContent = minified ? '▲' : '—'; + toggleBtn.title = minified ? '展开聊天' : '收起聊天'; + } + // 展开为完整聊天面板(清当前频道未读 + 滚到底部) + function expandChat(){ + chat.classList.remove('minified'); + syncChatState(); + unreadByChannel[currentChannel] = 0; updateChannelOptions(); updateBadge(); + body.scrollTop = body.scrollHeight; + } window.toggleWorldChat = function(){ - const wasCollapsed = chat.classList.contains('collapsed'); - chat.classList.toggle('collapsed'); - if(wasCollapsed){ unreadByChannel[currentChannel] = 0; updateChannelOptions(); updateBadge(); } + if(chat.classList.contains('collapsed')){ + chat.classList.remove('collapsed'); + chat.classList.add('minified'); + syncChatState(); + } else if(chat.classList.contains('minified')){ + expandChat(); + } else { + chat.classList.add('minified'); + syncChatState(); + } }; + syncChatState(); window.wcSendMsg = async function(){ const t = input.value.trim(); if(!t || !currentChannel) return; @@ -8931,7 +8911,8 @@ if (typeof fitWorld === 'function') fitWorld(); // 对外暴露:好友面板「私聊」调用 window.ChatAPI = { open(channelId){ - chat.classList.remove('collapsed'); + chat.classList.remove('collapsed','minified'); + syncChatState(); if(channelId && channels.some(c => c.id === channelId)){ sel.value = channelId; currentChannel = channelId; unreadByChannel[channelId] = 0; loadMessages(true); updateChannelOptions(); updateBadge();