Files
ngzz-mc/internal/ui/crafting.go
NianGao Dev 768a9b8096 feat(crafting): 工作台 3×3 合成界面——右键开台、槽位交换、输出格合成、关闭退回
- 会话层(工作台与合成表.md §5):TryOpenCrafting 右键命中工作台开台;
  CraftingSwap 网格槽与手持栈交换/合并;CraftingTakeResult 匹配配方
  (有序优先)消耗材料取产物;CloseCrafting 材料退回背包(放不下掉落)
- 客户端屏幕状态 3:gui/container/crafting_table.png 176×166 窗口、
  3×3 网格 (30,17) + 输出格 (124,35) + 热键栏行 (7,141)(UI还原.md §3.4);
  ESC 关闭并回收手持栈;界面打开时释放光标、关闭恢复捕获并重置鼠标基准
- 物品图标双图集:block/* 图标回退 blocks 图集(原版 items 图集不收录
  block 纹理,此前方块物品图标全部缺失)
- 单测:shaped 2×2 木板→工作台、shapeless 原木→木板、木镐多符号、
  关闭退回材料;dev 参数 -crafttest 全链路截图验证
2026-08-16 10:23:01 +08:00

135 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build gl
// 工作台界面(UI还原.md §3.4、工作台与合成表.md §5):
// 176×166 窗口(gui/container/crafting_table.png)+ 3×3 合成网格 (30,17)
// + 输出格 (124,35) + 热键栏行 (7,141);点击槽位与手持栈交换/合并。
package ui
import (
"fmt"
"mc/internal/item"
)
// CraftingScreen 工作台合成界面。
type CraftingScreen struct {
bgTex uint32 // crafting_table.png(256×256,实际内容 176×166)
items *item.Registry
itemTex uint32
itemUV map[string][4]float32
// 方块物品图标回退来源(原版 block/* 图标在 blocks 图集)
blockTex uint32
blockUV map[string][4]float32
// 布局(逻辑 427×240 基准,UI还原.md §3.4)
winW, winH float32 // 176×166
}
// NewCraftingScreen 创建工作台界面。
func NewCraftingScreen(bgTex uint32, items *item.Registry) *CraftingScreen {
return &CraftingScreen{bgTex: bgTex, items: items,
itemUV: map[string][4]float32{}, blockUV: map[string][4]float32{}, winW: 176, winH: 166}
}
// SetItemAtlas 设置物品图标纹理与 UV 表(与 HUD 共用 items 图集)。
func (s *CraftingScreen) SetItemAtlas(tex uint32, uv map[string][4]float32) {
s.itemTex = tex
s.itemUV = uv
}
// SetBlockAtlas 设置方块图集(方块物品图标回退来源)。
func (s *CraftingScreen) SetBlockAtlas(tex uint32, uv map[string][4]float32) {
s.blockTex = tex
s.blockUV = uv
}
// 合成台窗口槽位(相对窗口左上角,UI还原.md §3.4)。
var craftSlots = [10][2]float32{
{30, 17}, {48, 17}, {66, 17},
{30, 35}, {48, 35}, {66, 35},
{30, 53}, {48, 53}, {66, 53}, // 3×3 网格
{124, 35}, // 输出格
}
// HitTest 命中检测:返回 "grid0".."grid8" / "result" / "hot0".."hot8" / ""。
func (s *CraftingScreen) HitTest(mx, my float32, screenW, screenH, scale float32) string {
bx, by := screenW/2-s.winW/2*scale, screenH/2-s.winH/2*scale
hit := func(px, py float32) bool {
return mx >= bx+px*scale && mx < bx+(px+18)*scale &&
my >= by+py*scale && my < by+(py+18)*scale
}
for i, p := range craftSlots {
if hit(p[0], p[1]) {
if i < 9 {
return "grid" + string(rune('0'+i))
}
return "result"
}
}
// 热键栏行(窗口内底部,与物品栏窗口同规格)
for i := 0; i < 9; i++ {
if hit(7+float32(i)*18, 141) {
return "hot" + string(rune('0'+i))
}
}
return ""
}
// Draw 绘制工作台界面(调用方已 Begin;mx/my 为鼠标像素坐标,held 为手持栈)。
func (s *CraftingScreen) Draw(r *Renderer, grid [9]item.Stack, result item.Stack, hasResult bool, hotbar []item.Stack, held item.Stack, mx, my, scale float32) {
w, h := float32(r.w), float32(r.h)
// 半透明遮罩(原版容器界面背景压暗)
r.DrawRect(0, 0, w, h, [4]float32{0, 0, 0, 0.35})
// 窗口背景(纹理 256×256,内容区 176×166,UI还原.md §3.2)
bx, by := w/2-s.winW/2*scale, h/2-s.winH/2*scale
if s.bgTex != 0 {
r.pushQuad(quad{x: bx, y: by, w: s.winW * scale, h: s.winH * scale,
u0: 0, v0: 0, u1: 176.0 / 256.0, v1: 166.0 / 256.0,
r: 1, g: 1, b: 1, a: 1, tex: s.bgTex})
}
// 3×3 网格 + 输出格
for i, p := range craftSlots {
var st item.Stack
if i < 9 {
st = grid[i]
} else if hasResult {
st = result
}
s.drawSlot(r, st, bx+p[0]*scale+1*scale, by+p[1]*scale+1*scale, scale)
}
// 热键栏行
for i := 0; i < 9 && i < len(hotbar); i++ {
s.drawSlot(r, hotbar[i], bx+(7+float32(i)*18)*scale+1*scale, by+141*scale+1*scale, scale)
}
// 手持栈跟随鼠标
if !held.Empty() {
s.drawSlot(r, held, mx-8*scale, my-8*scale, scale)
}
}
// drawSlot 在指定位置绘制物品图标(16×16 逻辑像素)。
func (s *CraftingScreen) drawSlot(r *Renderer, st item.Stack, x, y, scale float32) {
if st.Empty() || s.items == nil {
return
}
def, ok := s.items.Get(st.Name)
if !ok {
return
}
// 原版约定:block/* 图标在 blocks 图集,item/* 在 items 图集
tex := s.itemTex
uv, found := s.itemUV[def.Texture]
if !found {
uv, found = s.blockUV[def.Texture]
tex = s.blockTex
}
if found && tex != 0 {
r.pushQuad(quad{x: x, y: y, w: 16 * scale, h: 16 * scale,
u0: uv[0], v0: uv[1], u1: uv[2], v1: uv[3],
r: 1, g: 1, b: 1, a: 1, tex: tex})
}
if st.Count > 1 {
r.DrawText(x+10*scale, y+8*scale, fmt.Sprintf("%d", st.Count), scale, [4]float32{1, 1, 1, 1})
}
}