Files
ngzz-mc/internal/ui/crafting.go
2026-09-19 14:21:08 +08:00

216 lines
5.8 KiB
Go
Raw Permalink 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 窗口 + 3×3 (30,17) + 输出 (124,35) + 主区 (7,83) + 热键栏 (7,141)
// + 右侧最小配方书。
package ui
import (
"fmt"
"strings"
"mc/internal/item"
"mc/internal/recipe"
)
// CraftingScreen 工作台合成界面。
type CraftingScreen struct {
itemIcons
bgTex uint32
winW, winH float32
BookOpen bool
BookQuery string
BookScroll int
OpenT float32
Labels map[string]string
tipTex uint32
tipW, tipH float32
}
// NewCraftingScreen 创建工作台界面。
func NewCraftingScreen(bgTex uint32, items *item.Registry) *CraftingScreen {
s := &CraftingScreen{bgTex: bgTex, winW: 176, winH: 166, OpenT: 1}
s.items = items
s.itemUV = map[string][4]float32{}
s.blockUV = map[string][4]float32{}
return s
}
// SetItemAtlas 设置物品图标纹理与 UV 表。
func (s *CraftingScreen) SetItemAtlas(tex uint32, uv map[string][4]float32) {
s.setItemAtlas(tex, uv)
}
// SetBlockAtlas 设置方块图集。
func (s *CraftingScreen) SetBlockAtlas(tex uint32, uv map[string][4]float32) {
s.setBlockAtlas(tex, uv)
}
// SetTooltip 设置悬停名称纹理。
func (s *CraftingScreen) SetTooltip(tex uint32, w, h float32) {
s.tipTex = tex
s.tipW, s.tipH = w, h
}
var craftSlots = [10][2]float32{
{30, 17}, {48, 17}, {66, 17},
{30, 35}, {48, 35}, {66, 35},
{30, 53}, {48, 53}, {66, 53},
{124, 35},
}
// HitTest 命中:grid0–8 / result / main0–26 / hot0–8 / book / recipeN。
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
for i, p := range craftSlots {
if hitSlot(mx, my, bx, by, p[0], p[1], scale) {
if i < 9 {
return "grid" + string(rune('0'+i))
}
return "result"
}
}
for i := 0; i < 27; i++ {
px := 7 + float32(i%9)*18
py := 83 + float32(i/9)*18
if hitSlot(mx, my, bx, by, px, py, scale) {
return "main" + fmt.Sprintf("%d", i)
}
}
for i := 0; i < 9; i++ {
if hitSlot(mx, my, bx, by, 7+float32(i)*18, 141, scale) {
return "hot" + fmt.Sprintf("%d", i)
}
}
// 配方书按钮:窗口右侧
if mx >= bx+(s.winW+2)*scale && mx < bx+(s.winW+20)*scale &&
my >= by+8*scale && my < by+26*scale {
return "book"
}
if s.BookOpen {
if mx >= bx+(s.winW+22)*scale && mx < bx+(s.winW+100)*scale &&
my >= by && my < by+16*scale {
return "search"
}
for i := 0; i < 8; i++ {
px := s.winW + 22
py := 20 + float32(i)*16
if hitSlot(mx, my, bx, by, px, py, scale) {
return "recipe" + fmt.Sprintf("%d", s.BookScroll+i)
}
}
}
return ""
}
// FilterRecipes 按搜索过滤。
func FilterRecipes(all []*recipe.Recipe, q string, labels map[string]string) []*recipe.Recipe {
q = strings.ToLower(strings.TrimSpace(q))
if q == "" {
return all
}
var out []*recipe.Recipe
for _, rc := range all {
if rc == nil {
continue
}
if strings.Contains(strings.ToLower(rc.Result.Item), q) {
out = append(out, rc)
continue
}
if labels != nil && strings.Contains(strings.ToLower(labels[rc.Result.Item]), q) {
out = append(out, rc)
}
}
return out
}
// Draw 绘制工作台(slots 为完整背包;recipes 为配方书条目)。
func (s *CraftingScreen) Draw(r *Renderer, grid [9]item.Stack, result item.Stack, hasResult bool, slots []item.Stack, held item.Stack, recipes []*recipe.Recipe, unlocked map[string]bool, 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})
sc := scale * (0.85 + 0.15*s.OpenT)
bx, by := w/2-s.winW/2*sc, h/2-s.winH/2*sc
if s.bgTex != 0 {
r.pushQuad(quad{x: bx, y: by, w: s.winW * sc, h: s.winH * sc,
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})
}
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]*sc+1*sc, by+p[1]*sc+1*sc, sc)
}
for i := 0; i < 27; i++ {
idx := 9 + i
st := item.Stack{}
if idx < len(slots) {
st = slots[idx]
}
px := 7 + float32(i%9)*18
py := 83 + float32(i/9)*18
s.drawSlot(r, st, bx+(px+1)*sc, by+(py+1)*sc, sc)
}
for i := 0; i < 9 && i < len(slots); i++ {
s.drawSlot(r, slots[i], bx+(7+float32(i)*18)*sc+1*sc, by+141*sc+1*sc, sc)
}
btn := [4]float32{0.55, 0.45, 0.25, 1}
if s.BookOpen {
btn = [4]float32{0.75, 0.65, 0.35, 1}
}
r.DrawRect(bx+(s.winW+2)*sc, by+8*sc, 18*sc, 18*sc, btn)
r.DrawText(bx+(s.winW+6)*sc, by+13*sc, "R", sc, [4]float32{1, 1, 0.85, 1})
if s.BookOpen {
r.DrawRect(bx+(s.winW+20)*sc, by*1, 102*sc, 160*sc, [4]float32{0.12, 0.12, 0.12, 0.88})
r.DrawRect(bx+(s.winW+22)*sc, by+2*sc, 78*sc, 14*sc, [4]float32{0, 0, 0, 0.55})
qs := s.BookQuery
if qs == "" {
qs = "search"
}
r.DrawText(bx+(s.winW+24)*sc, by+5*sc, qs, 0.65*sc, [4]float32{0.85, 0.85, 0.85, 1})
vis := FilterRecipes(recipes, s.BookQuery, s.Labels)
if s.BookScroll < 0 {
s.BookScroll = 0
}
n := 8
if len(vis)-s.BookScroll < n {
n = len(vis) - s.BookScroll
}
if n < 0 {
n = 0
}
for i := 0; i < n; i++ {
rc := vis[s.BookScroll+i]
py := 20 + float32(i)*16
st := item.Stack{Name: rc.Result.Item, Count: uint8(min(rc.Result.Count, 127))}
s.drawSlot(r, st, bx+(s.winW+23)*sc, by+(py+1)*sc, sc)
col := [4]float32{1, 1, 1, 1}
if unlocked != nil && !unlocked[rc.Result.Item] {
col = [4]float32{0.45, 0.45, 0.45, 1}
}
r.DrawText(bx+(s.winW+42)*sc, by+(py+4)*sc, rc.Result.Item, 0.6*sc, col)
}
}
if !held.Empty() {
s.drawSlot(r, held, mx-8*scale, my-8*scale, scale)
}
if s.tipTex != 0 && s.tipW > 0 {
tx, ty := mx+12, my+12
drawTooltipBG(r, tx, ty, s.tipW, s.tipH)
r.pushQuad(quad{x: tx, y: ty, w: s.tipW, h: s.tipH,
u0: 0, v0: 0, u1: 1, v1: 1, r: 1, g: 1, b: 1, a: 1, tex: s.tipTex})
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}