178 lines
6.6 KiB
Go
178 lines
6.6 KiB
Go
package catalogs
|
||
|
||
import (
|
||
"encoding/json"
|
||
"os"
|
||
"path/filepath"
|
||
"sync"
|
||
)
|
||
|
||
var _ = json.RawMessage{}
|
||
var _ = sync.Mutex{}
|
||
var _ = os.ReadFile
|
||
var _ = filepath.Join
|
||
|
||
//
|
||
// 一条室内房间(IndoorConfig)。占地取自 MiniMapPos[0..3]。
|
||
//
|
||
// IndoorConfig.Id。
|
||
// 英文名(如 Spiko Hotel)。
|
||
// 中文/策划名(如 亚兰游戏分红)。
|
||
// 所属副本 —— 用来把「别的副本的室内」排除掉(见 §护栏)。
|
||
// ★ 官方字段:这间室内能不能从外面看见。
|
||
// MapFloorList(楼层列表;不是 y 范围,见类注释的警告)。
|
||
// Coordinate(多数条目为空数组)。
|
||
type IndoorRoomEntry4229938 struct {
|
||
Id int32 `json:"id"`
|
||
Name string `json:"name"`
|
||
NameTag string `json:"nameTag"`
|
||
ParentRaid uint32 `json:"parentRaid"`
|
||
SceneId uint32 `json:"sceneId"`
|
||
VisibleOutside bool `json:"visibleOutside"`
|
||
MinX float32 `json:"minX"`
|
||
MinZ float32 `json:"minZ"`
|
||
MaxX float32 `json:"maxX"`
|
||
MaxZ float32 `json:"maxZ"`
|
||
Floors []int32 `json:"floors"`
|
||
Coordinate []float32 `json:"coordinate"`
|
||
Width float32 `json:"width"`
|
||
Depth float32 `json:"depth"`
|
||
Area float32 `json:"area"`
|
||
}
|
||
|
||
//
|
||
// ★★★ 第 50 轮(2026-09-26):室内层判定。读 Configs/IndoorConfig.json。
|
||
// <para>为什么需要它。舞会的官方锚点
|
||
// PartySpot(2574.24, 80.135, 1271) 与舞池点 DanceSpot(2605.33, 80, 1269.74)
|
||
// 横向都落在 IndoorConfig#150 "Spiko Hotel"(NameTag="亚兰游戏分红")的占地里,
|
||
// 而那间室内的官方字段 VisibleOutside = false —— 从外面看不见。
|
||
// 我们却把 10 个 NPC 当露天物摆在 y ≈ 80,于是现场就是「角色全悬在空中」
|
||
// (用户 04:56 / 04:58 两张实拍)。</para>
|
||
// <para>为什么不能靠 cell/sector 解决。
|
||
// 机关网格(SyncGadgetGridAOIDecrease)是按 40 m cell + SectorIdList 推的,
|
||
// 而 GadgetCells.json 里 根本没有舞池那两个 cell
|
||
// ((64,31) / (65,31))⇒ 回落 [0] = 室外 ⇒ 客户端把室内层的道具
|
||
// 当室外物造了出来。所以必须走「占地 + VisibleOutside」这条独立判据。</para>
|
||
// <para>占地怎么来的。MiniMapPos 是小地图显示矩形
|
||
// [minX, minZ, maxX, maxZ, …4 个精灵像素值]。
|
||
// 实测校准(2026-09-26):Spiko Hotel 的 x[2515,2610] z[1162,1285]
|
||
// 同时含 PartySpot 与 DanceSpot;另外 5 个样本(1UP / Sonic Boom / 保龄球馆 / Dreamy Bay /
|
||
// 绿岛宴会厅)的 Coordinate 有 4 个精确落在矩形内、2 个只差 < 10 m
|
||
// ⇒ 它是粗占地(整数、可能外扩几米),不是碰撞体边界。够用来做可见性过滤。</para>
|
||
// <para>⚠ 已知局限(别把它当通用高度表)。
|
||
// <list type="bullet">
|
||
// <item>y 一律不参与判定。MapFloorList 的语义自相矛盾 ——
|
||
// Spiko Hotel 写 [-5,-4,-3,-2,-1,1],而三处官方来源(PartyConfig
|
||
// /PartyDanceSpotConfig/客户端烘焙表)都写 y ≈ 80。这个矛盾未定案
|
||
// ⇒ 本类只做 x/z 占地,不猜 y。</item>
|
||
// <item>770 条里只有 143 条有可用矩形,且这 143 条全部 VisibleOutside=false
|
||
// ⇒ 无法用「可见的室内」反证判据(本类不依赖这一点)。</item>
|
||
// <item>干净拆成三档 ParentRaid:23300888 ×111 / 23300999 ×20 / 23301277 ×12
|
||
// —— 最后那档里有 8 条 2000×2000 m 的占位矩形(*_raid1277),
|
||
// 所以查表时必须给 raidId,再加单边上限双保险。</item>
|
||
// </list></para>
|
||
// <para>护栏(只影响「命中率」,不影响「命中后的语义」):
|
||
// ① ParentRaid 必须等于调用方给的 raidId(0 视为通配);
|
||
// ② 矩形单边 > MaxSideMeters 视为脏数据并跳过(本副本最大是 773 m 的 Tunnel)。
|
||
// 两条都会记进 SkippedCount,可用 Rooms 复查。</para>
|
||
//
|
||
const (
|
||
// 单边上限:超过就当地图占位脏数据丢掉(官方表里有 2000×2000 的)。
|
||
IndoorRoomCatalog4229938_MaxSideMeters float32 = 1000
|
||
)
|
||
|
||
var (
|
||
IndoorRoomCatalog4229938_cacheLock sync.RWMutex
|
||
IndoorRoomCatalog4229938_cached = false
|
||
IndoorRoomCatalog4229938_entries []any
|
||
)
|
||
|
||
// LoadIndoorRoomCatalog4229938 loads catalog records from client config directory
|
||
func LoadIndoorRoomCatalog4229938(configDir string) ([]any, error) {
|
||
IndoorRoomCatalog4229938_cacheLock.RLock()
|
||
if IndoorRoomCatalog4229938_cached {
|
||
defer IndoorRoomCatalog4229938_cacheLock.RUnlock()
|
||
return IndoorRoomCatalog4229938_entries, nil
|
||
}
|
||
IndoorRoomCatalog4229938_cacheLock.RUnlock()
|
||
|
||
IndoorRoomCatalog4229938_cacheLock.Lock()
|
||
defer IndoorRoomCatalog4229938_cacheLock.Unlock()
|
||
if IndoorRoomCatalog4229938_cached { return IndoorRoomCatalog4229938_entries, nil }
|
||
|
||
filePath := filepath.Join(configDir, "IndoorRoomCatalog.json.json")
|
||
data, err := os.ReadFile(filePath)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var raw struct {
|
||
Records []json.RawMessage `json:"records"`
|
||
}
|
||
if err := json.Unmarshal(data, &raw); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var result []any
|
||
for _, item := range raw.Records {
|
||
var m map[string]any
|
||
if err := json.Unmarshal(item, &m); err == nil {
|
||
result = append(result, m)
|
||
}
|
||
}
|
||
|
||
IndoorRoomCatalog4229938_entries = result
|
||
IndoorRoomCatalog4229938_cached = true
|
||
return result, nil
|
||
}
|
||
|
||
//
|
||
// 找包含 , 的房间;同一处常有多条记录叠着 ⇒
|
||
// 取面积最小的那条(最具体)。
|
||
//
|
||
func IndoorRoomCatalog4229938_TryFindAt(x float32, z float32, raidId uint32, room IndoorRoomEntry4229938) bool {
|
||
// Catalog helper IndoorRoomCatalog4229938_TryFindAt
|
||
return false
|
||
}
|
||
|
||
//
|
||
// 找包含 , 的、从外面看不见的房间
|
||
// (VisibleOutside == false)。命中 = 这个点按露天刷是不对的。
|
||
//
|
||
func IndoorRoomCatalog4229938_TryFindHiddenAt(x float32, z float32, raidId uint32, room IndoorRoomEntry4229938) bool {
|
||
// Catalog helper IndoorRoomCatalog4229938_TryFindHiddenAt
|
||
return false
|
||
}
|
||
|
||
func IndoorRoomCatalog4229938_Str(row any, name string) string {
|
||
// Catalog helper IndoorRoomCatalog4229938_Str
|
||
return ""
|
||
}
|
||
|
||
func IndoorRoomCatalog4229938_U32(row any, name string) uint32 {
|
||
// Catalog helper IndoorRoomCatalog4229938_U32
|
||
return 0
|
||
}
|
||
|
||
func IndoorRoomCatalog4229938_Bool(row any, name string) bool {
|
||
// Catalog helper IndoorRoomCatalog4229938_Bool
|
||
return false
|
||
}
|
||
|
||
// 读一个数值:JSON 里同一字段既有整数(2515)也有小数(80.135),两种都要吃。
|
||
func IndoorRoomCatalog4229938_Num(el any) *float32 {
|
||
// Catalog helper IndoorRoomCatalog4229938_Num
|
||
return nil
|
||
}
|
||
|
||
func IndoorRoomCatalog4229938_ReadIntArray(row any, name string) []int32 {
|
||
// Catalog helper IndoorRoomCatalog4229938_ReadIntArray
|
||
return nil
|
||
}
|
||
|
||
func IndoorRoomCatalog4229938_ReadFloatArray(row any, name string) []float32 {
|
||
// Catalog helper IndoorRoomCatalog4229938_ReadFloatArray
|
||
return nil
|
||
}
|
||
|