feat: 世界聊天敏感词过滤+菜单整合, 部署脚本绑定 yt_game

This commit is contained in:
李鹏宇
2026-08-14 15:40:56 +08:00
parent 73d764938d
commit ec2f07dfab
13 changed files with 242 additions and 27 deletions
+71 -9
View File
@@ -42,7 +42,7 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
}
#topBar .title .worldName.editable { cursor: pointer; padding: 2px 8px; border-radius: 5px; transition: background .15s; }
#topBar .title .worldName.editable:hover { background: rgba(255,255,255,.08); }
#topBar .title .worldName-input {
.worldName-input {
background: transparent; border: 1px solid rgba(124,199,255,.6); color: #cfe1f3;
font-size: 15px; font-weight: 600; padding: 2px 6px; border-radius: 5px; width: 180px;
font-family: inherit; outline: none;
@@ -172,7 +172,7 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
/* 世界等级经验条(位于 bottomBar 内最左侧,加大加清晰) */
#worldExpBarWrap{
flex:0 0 auto; width:380px; height:100%;
flex:0 0 auto; width:520px; height:100%;
display:flex; flex-direction:column; justify-content:center; gap:7px;
padding:0 16px 0 0; background:transparent; border:none;
}
@@ -180,8 +180,9 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
.we-lv{font-size:16px; font-weight:800; color:#c4b5fd; text-shadow:0 0 10px rgba(139,92,246,.5);}
.we-title{font-size:13px; color:#9fb0c4; max-width:130px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.we-exp-txt{margin-left:auto; font-size:13px; font-weight:600; color:#cfe1f3;}
.we-bar{width:100%; height:12px; background:rgba(255,255,255,.10); border-radius:6px; overflow:hidden;}
.we-fill{height:100%; width:0%; background:linear-gradient(90deg,#8b5cf6,#4af); border-radius:6px; transition:width .3s ease; box-shadow:0 0 12px rgba(139,92,246,.45);}
.we-pct{margin-left:2px; font-size:13px; font-weight:700; color:#c4b5fd; min-width:34px; text-align:right;}
.we-bar{width:100%; height:14px; background:rgba(255,255,255,.10); border-radius:7px; overflow:hidden;}
.we-fill{height:100%; width:0%; background:linear-gradient(90deg,#8b5cf6,#4af); border-radius:7px; transition:width .3s ease; box-shadow:0 0 12px rgba(139,92,246,.45);}
/* 主沙盘底部功能框架(预留区:放功能按钮 / 素材槽,比微观边框更高) */
#bottomBar {
@@ -238,6 +239,8 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
}
#microHeader .backBtn:hover { background: rgba(100,200,255,0.25); }
#microHeader .microTitle { margin-left: 16px; font-size: 16px; font-weight: bold; color: #e0e0e0; }
#microHeader .microTitle.editable { cursor: pointer; padding: 2px 8px; border-radius: 5px; transition: background .15s; }
#microHeader .microTitle.editable:hover { background: rgba(255,255,255,.08); }
#microHeader .microSubtitle { display: none; }
#microBody { margin-top: 96px; flex: 1; display: flex; flex-direction: column; position: relative; overflow: hidden; }
@@ -715,7 +718,7 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
@media (max-width:420px){
#topBar .title .worldName{display:none;}
#bottomBar .bb-btn{padding:7px 9px;font-size:11px;}
#worldExpBarWrap{width:220px;gap:5px;}
#worldExpBarWrap{width:280px;gap:5px;}
#worldExpBarWrap .we-title{display:none;}
#sceneToolbar{right:8px;gap:5px;padding:6px 7px;top:calc(58px + env(safe-area-inset-top) + 6px);}
#sceneToolbar .top-icon{width:34px;height:34px;font-size:16px;}
@@ -818,6 +821,7 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
<span class="we-lv">世界 Lv.<b id="weLv">--</b></span>
<span class="we-title" id="weTitle">--</span>
<span class="we-exp-txt" id="weExpTxt">EXP -- / --</span>
<span class="we-pct" id="weExpPct">--%</span>
</div>
<div class="we-bar"><div class="we-fill" id="weExpFill" style="width:0%"></div></div>
</div>
@@ -1690,6 +1694,7 @@ let ambParticles = [];
// 微观世界
let microState = null;
let microNames = {}; // 地块 key("q,r") -> 该微观世界自定义名称(后端持久化,记忆)
let microBuildRevealAt = 0; // 建筑显示时间戳:过渡结束 + 200ms 后才浮现
// 游戏化系统全局状态(与 Odoo 后端打通)
let microChar = null; // 玩家角色:{name,level,exp,gold,inventory,equip}
@@ -2972,6 +2977,15 @@ function pickTransitionImage() {
return TRANSITION_IMAGES[idx];
}
// ============ 微观世界标题:有自定义名显示「层 · 自定义名」,否则默认「层 · 地形村落」 ============
function microTitleText(tile) {
const key = tile.q + ',' + tile.r;
const custom = microNames[key];
const layerLabel = { earth: '🌍 大地', under: '🕳 地下', core: '🔥 地心', sky: '🌤 天空', star: '✦ 星空' }[tile.layer] || '';
if (custom) return `${layerLabel} · ${custom}`;
return `${layerLabel} · ${TERRAIN[tile.terrain].name}村落`;
}
// ============ 进入微观世界 ============
function enterMicroWorld(tile) {
if (!tile) return;
@@ -3012,8 +3026,7 @@ function enterMicroWorld(tile) {
microZoom = 1; microPanX = 0; microPanY = 0;
microViewOpen = true; // 暂停主世界重绘
microView.classList.add('active'); // 入场动画(缩放+去模糊)在遮罩掩护下播放,揭幕时早已归位
const layerLabel = { earth:'🌍 大地', under:'🕳 地下', core:'🔥 地心', sky:'🌤 天空', star:'✦ 星空' }[tile.layer] || '';
document.getElementById('microTitle').textContent = `${layerLabel} · ${TERRAIN[tile.terrain].name}村落`;
document.getElementById('microTitle').textContent = microTitleText(tile);
document.getElementById('microSubtitle').textContent = `坐标(${tile.q}, ${tile.r}) · ${microState.units.length}位居民`;
applyMicroAccent(tile.terrain);
updateSidebar();
@@ -3682,8 +3695,7 @@ function setMicroTile(tile) {
? [{ ...BUILDINGS[tile.buildingType], type: tile.buildingType, level: tile.buildingLevel, hp: 80 + tile.buildingLevel * 10 }]
: [];
microState.resources = calcResources(tile);
const layerLabel = { earth: '🌍 大地', under: '🕳 地下', core: '🔥 地心', sky: '🌤 天空', star: '✦ 星空' }[tile.layer] || '';
document.getElementById('microTitle').textContent = `${layerLabel} · ${TERRAIN[tile.terrain].name}村落`;
document.getElementById('microTitle').textContent = microTitleText(tile);
document.getElementById('microSubtitle').textContent = `坐标(${tile.q}, ${tile.r}) · ${microState.units.length}位居民`;
updateSidebar();
microAutoRoadCells(); // 切地块时重新计算自动道路贴图
@@ -5044,6 +5056,7 @@ function applyWorldState(ws) {
if (ov.terrain) t.terrain = ov.terrain;
if (ov.height != null) { t.height = ov.height; t.tier = heightToTier(ov.height); }
if (ov.flags) t.flags = ov.flags;
if (ov.micro_name != null) microNames[ov.q + ',' + ov.r] = ov.micro_name; // 恢复微观世界命名记忆
}
// 微观建造布置:按 'q,r' 恢复(覆盖所有层,进入地块时即显示)
for (const ov of ws.tiles) {
@@ -5946,6 +5959,51 @@ function setupWorldNameEdit() {
});
}
// ============ 微观世界标题:点击进入编辑,回车/失焦保存到后端,Esc 取消 ============
function setupMicroNameEdit() {
const mt = document.getElementById('microTitle');
if (!mt) return;
mt.classList.add('editable');
mt.title = '点击重命名此微观世界';
mt.addEventListener('click', () => {
if (!microState || !microState.tile) return; // 仅在微观视图内可编辑
if (mt.querySelector('input')) return; // 已在编辑
const tile = microState.tile;
const key = tile.q + ',' + tile.r;
const current = microNames[key] || '';
const input = document.createElement('input');
input.type = 'text';
input.value = current;
input.placeholder = '输入此微观世界名称';
input.maxLength = 24;
input.className = 'worldName-input';
input.spellcheck = false;
mt.textContent = '';
mt.appendChild(input);
input.focus();
input.select();
let done = false;
const restore = () => { if (done) return; done = true; mt.textContent = microTitleText(tile); };
const commit = async () => {
if (done) return;
const next = input.value.trim();
if (!next || next === current) { restore(); return; }
restore(); // 先本地显示,再异步写后端
microNames[key] = next;
try {
const r = await API.renameMicro(tile.q, tile.r, next);
if (r && r.ok && r.name) { microNames[key] = r.name; }
else console.warn('微观世界重命名后端未响应,已本地保存', r);
} catch (e) { console.warn('微观世界重命名失败,已本地保存', e); }
};
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') { e.preventDefault(); commit(); }
else if (e.key === 'Escape') { e.preventDefault(); restore(); }
});
input.addEventListener('blur', commit);
});
}
function init() {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight - BOTTOM_BAR_H;
@@ -5960,6 +6018,8 @@ function init() {
setupBuildUI();
// 顶栏世界名称点击编辑
setupWorldNameEdit();
// 微观世界标题点击编辑(按地块独立命名,持久化到后端)
setupMicroNameEdit();
// 钟表已移走;时间文字由 syncTimeUI 每 100ms 节流更新
// 世界切换导航
document.querySelectorAll('#worldNav .wn-item').forEach(el => {
@@ -6245,6 +6305,8 @@ if (typeof fitWorld === 'function') fitWorld();
if (weExpFill) weExpFill.style.width = worldExpPct + '%';
const weExpTxt = document.getElementById('weExpTxt');
if (weExpTxt) weExpTxt.textContent = 'EXP ' + worldExp + ' / ' + worldExpMax;
const weExpPctEl = document.getElementById('weExpPct');
if (weExpPctEl) weExpPctEl.textContent = Math.floor(worldExpPct) + '%';
// 邮件未读角标(顶部图标栏)
const topMailBadge = document.getElementById('topMailBadge');
if (topMailBadge) {