宝箱站位优化 #39

Merged
Ghost merged 1 commits from why_ga into main 2026-09-14 17:34:37 +08:00
2 changed files with 36 additions and 13 deletions
+5 -1
View File
@@ -90,8 +90,12 @@ class YtWorldChest(models.Model):
# 删掉最旧的,腾出一个空位给新宝箱
to_remove = existing[:len(existing) - MAX_CHESTS_PER_TILE + 1]
to_remove.unlink()
# 2) 占用格集合(建筑 footprint)
# 2) 占用格集合(建筑 footprint + 已有 active 宝箱格子)
# 宝箱作为 1×1 建筑:不能和建筑重叠,也不能多个宝箱叠在同一格
occ = self._occupied_cells(world, q, r, grid_n)
keep_chests = [c for c in existing if c not in to_remove]
chest_cells = {(int(c.cell_i), int(c.cell_j)) for c in keep_chests}
occ |= chest_cells
# 3) 随机空格(留 1 格边距,防止贴边/溢出岛外)
margin = 1
free = []
+31 -12
View File
@@ -2780,7 +2780,13 @@ async function microLoadChests(q, r) {
try {
const j = await API.chests(q, r);
if (j && j.ok && Array.isArray(j.chests)) {
microChests = j.chests.map(c => ({ ...c, _claiming: false }));
// 同格去重:宝箱为 1×1 建筑,每格只保留一个(防御性,避免后端异常时叠箱)
const seen = new Set();
microChests = j.chests.filter(c => {
const k = Math.round(c.cell_i) + ',' + Math.round(c.cell_j);
if (seen.has(k)) return false;
seen.add(k); return true;
}).map(c => ({ ...c, _claiming: false }));
window.microChests = microChests; // 同步给 characters.js(否则其读到的是旧空数组引用)
}
} catch (e) { /* 离线兜底 */ }
@@ -2868,27 +2874,32 @@ function getChestImage(url) {
function microChestTopImage(tier) { return MICRO_CHEST_TOP_IMAGES[tier] || null; }
function microChestIconImage(tier) { return MICRO_CHEST_ICON_IMAGES[tier] || null; }
// 在网格层之上绘制所有活跃宝箱(呼吸光晕 + 漂浮主体/俯视图)
// 在网格层之上绘制所有活跃宝箱。
// 宝箱 = 1×1 建筑:俯视图素材本身已是等距 3D 视角(无需再做 iso 投影),
// 直接以格心为原点绘制、clip 到 1×1 菱形 footprint 防溢出。
function microDrawChests(c) {
if (!microChests.length) return;
const t = microState.animTime;
const g = microBuildGeom();
const cell = g.cell, hw = cell / 2, hh = g.hh; // 单格菱形半宽 hw、半高 hh
microChests.forEach(ch => {
const sp = microCellToScreen(ch.cell_i + 0.5, ch.cell_j + 0.5);
// 主体:优先使用俯视图 PNG,未加载成功则回退 emoji
const sp = microCellToScreen(ch.cell_i + 0.5, ch.cell_j + 0.5); // 1×1 占位中心 = 格心
const imgUrl = microChestTopImage(ch.tier);
const img = imgUrl ? getChestImage(imgUrl) : null;
if (img && img.complete && img.naturalWidth) {
const baseW = 42, baseH = 42;
c.save();
c.translate(sp.x, sp.y - 14);
// 45度俯视:轻微压扁并旋转,贴合等距地面
c.scale(1, 0.72);
c.drawImage(img, -baseW / 2, -baseH / 2, baseW, baseH);
c.translate(sp.x, sp.y);
// 1×1 菱形 footprint clip(防溢出到相邻格子)
c.beginPath();
c.moveTo(0, -hh); c.lineTo(hw, 0); c.lineTo(0, hh); c.lineTo(-hw, 0); c.closePath();
c.clip();
// 俯视图本身已是 3D 等距视角,直接按格子比例缩放绘制,不再做 rotate/scale 投影
const s = cell * 0.46; // 缩小到完整落入单格菱形内,不被 clip 裁切
c.drawImage(img, -s / 2, -s / 2, s, s);
c.restore();
} else {
c.save();
c.translate(sp.x, sp.y - 14);
c.font = '30px sans-serif'; c.textAlign = 'center'; c.textBaseline = 'middle';
c.translate(sp.x, sp.y);
c.font = '28px sans-serif'; c.textAlign = 'center'; c.textBaseline = 'middle';
c.fillText(microChestEmoji(ch.tier), 0, 0);
c.restore();
}
@@ -7268,6 +7279,14 @@ function microFootprintHit(list, i, j, ew, eh, ignore) {
const c = microItemCells(o);
if (i < o.i + c.ew && i + ew > o.i && j < o.j + c.eh && j + eh > o.j) return true;
}
// 宝箱作为 1×1 建筑占用格:不在建筑栏,但生成出来占网格,建造时不能放到宝箱所在格
if (typeof microChests !== 'undefined' && microChests && microChests.length) {
for (const ch of microChests) {
if (ch === ignore) continue;
const ci = Math.round(ch.cell_i), cj = Math.round(ch.cell_j);
if (i < ci + 1 && i + ew > ci && j < cj + 1 && j + eh > cj) return true;
}
}
return false;
}
// 可见矩形(内缩后)在 (i,j) 连续坐标下的覆盖范围,用于约束建筑落点不出框