158 lines
6.1 KiB
Go
158 lines
6.1 KiB
Go
package catalogs
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
)
|
|
|
|
var _ = json.RawMessage{}
|
|
var _ = sync.Mutex{}
|
|
var _ = os.ReadFile
|
|
var _ = filepath.Join
|
|
|
|
// One row of AgentDataSetsActivityConfig — something an NPC can be seen doing.
|
|
type NpcActivityEntry4229938 struct {
|
|
Id uint32 `json:"id"`
|
|
AgentTag uint32 `json:"agentTag"`
|
|
NpcCultivationId uint32 `json:"npcCultivationId"`
|
|
Raid uint32 `json:"raid"`
|
|
Performance string `json:"performance"`
|
|
MapName string `json:"mapName"`
|
|
X float32 `json:"x"`
|
|
Y float32 `json:"y"`
|
|
Z float32 `json:"z"`
|
|
// Authored rows carry MapPosition; the rest are "临时点位" placeholders with a zero vector.
|
|
HasPosition bool `json:"hasPosition"`
|
|
}
|
|
|
|
// What NpcActivityCatalog4229938.Resolve picked, and why — for logs.
|
|
type NpcActivityChoice struct {
|
|
Entry *NpcActivityEntry4229938 `json:"entry"`
|
|
Mode string `json:"mode"`
|
|
Timed *NpcActivityEntry4229938 `json:"timed"`
|
|
Spot *RosterNpcStaticSpot `json:"spot"`
|
|
// 有没有一个能落地的坐标 —— 官方活动坐标,或手工常驻点。
|
|
Placeable bool `json:"placeable"`
|
|
PlaceX float32 `json:"placeX"`
|
|
PlaceY float32 `json:"placeY"`
|
|
PlaceZ float32 `json:"placeZ"`
|
|
}
|
|
|
|
//
|
|
// Reads Configs/NpcActivityCatalog.json — the server copy of the client's
|
|
// AgentDataSetsActivityConfig (284 rows), generated by _gen_npc_life_cfg.py out of
|
|
// 补丁和工具\DUMPS\readable_world_dump_4229938_final.zip
|
|
// (01_CONFIGS/npc_systems/table/AgentDataSetsActivityConfig.json).
|
|
// Why it matters: this is the table that turns "an NPC tag" into "a place and a performance".
|
|
// AgentDataSetsActivityCfgId on the agent's sync info is an id into THIS table, and
|
|
// MapPosition is where the official build actually puts the character (Richie on the
|
|
// treadmill at 1191,0,2052 is just one row). 153 of the 284 rows carry a real position; all of
|
|
// them sit in the two open-world raids (23300888 / 23300999).
|
|
// The id also reaches the UI: NpcDaliyManager:OnRenderActivity resolves it through the same
|
|
// table for the icon/description, and MapSubSystem_SpiritAcquisition reads the schedule's
|
|
// RaidId + NpcScheduleInfo.Position to drop the map marker — which is why the login frame's
|
|
// FavorNpcDailyScheduleInfos should carry these coordinates too.
|
|
//
|
|
var (
|
|
NpcActivityCatalog4229938_cacheLock sync.RWMutex
|
|
NpcActivityCatalog4229938_cached = false
|
|
NpcActivityCatalog4229938_entries []any
|
|
)
|
|
|
|
// LoadNpcActivityCatalog4229938 loads catalog records from client config directory
|
|
func LoadNpcActivityCatalog4229938(configDir string) ([]any, error) {
|
|
NpcActivityCatalog4229938_cacheLock.RLock()
|
|
if NpcActivityCatalog4229938_cached {
|
|
defer NpcActivityCatalog4229938_cacheLock.RUnlock()
|
|
return NpcActivityCatalog4229938_entries, nil
|
|
}
|
|
NpcActivityCatalog4229938_cacheLock.RUnlock()
|
|
|
|
NpcActivityCatalog4229938_cacheLock.Lock()
|
|
defer NpcActivityCatalog4229938_cacheLock.Unlock()
|
|
if NpcActivityCatalog4229938_cached { return NpcActivityCatalog4229938_entries, nil }
|
|
|
|
filePath := filepath.Join(configDir, "NpcActivityCatalog.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)
|
|
}
|
|
}
|
|
|
|
NpcActivityCatalog4229938_entries = result
|
|
NpcActivityCatalog4229938_cached = true
|
|
return result, nil
|
|
}
|
|
|
|
func NpcActivityCatalog4229938_TryGet(activityId uint32, entry NpcActivityEntry4229938) bool {
|
|
// Catalog helper NpcActivityCatalog4229938_TryGet
|
|
return false
|
|
}
|
|
|
|
//
|
|
// The activity an AgentTag is doing right now, or 0 when that tag has neither a schedule nor
|
|
// an authored position (the two protagonists — AgentTag 2/3 — are exactly this case).
|
|
//
|
|
func NpcActivityCatalog4229938_CurrentFor(agentTag uint32, daySecond int32) *NpcActivityEntry4229938 {
|
|
// Catalog helper NpcActivityCatalog4229938_CurrentFor
|
|
return nil
|
|
}
|
|
|
|
//
|
|
// Same tag, but ignoring the clock: the authored activity with a real position inside
|
|
// , lowest Id first. Used when the current time slice
|
|
// points at an activity we cannot place — either it has no MapPosition at all (Richie's 05:00
|
|
// "给垃圾桶蟹开罚单" is a 临时点位) or it belongs to the other open-world raid.
|
|
// Returns null when this tag has nothing placeable in that raid.
|
|
// ⛑⛑ Intentionally not "nearest to the player". A player-relative pick is re-evaluated
|
|
// every tick, so the answer changes as the player walks and the character appears to follow
|
|
// him around; combined with the enter radius it degenerates into "whoever has no schedule
|
|
// materialises at your feet". Official data is a fixed spot — pick one deterministically and
|
|
// let the enter radius decide whether the player is close enough to see it.
|
|
//
|
|
func NpcActivityCatalog4229938_FirstPositioned(agentTag uint32, raid uint32) *NpcActivityEntry4229938 {
|
|
// Catalog helper NpcActivityCatalog4229938_FirstPositioned
|
|
return nil
|
|
}
|
|
|
|
//
|
|
// Hand-authored standing spot for a character that has no official daily-life data at all
|
|
// (gameplay.rosterNpc.staticSpots). Returns null when this spirit has none, or when the
|
|
// spot belongs to a different raid.
|
|
//
|
|
func NpcActivityCatalog4229938_StaticSpotFor(spiritId uint32, raid uint32) *RosterNpcStaticSpot {
|
|
// Catalog helper NpcActivityCatalog4229938_StaticSpotFor
|
|
return nil
|
|
}
|
|
|
|
//
|
|
// The single selection policy for "what is this character doing right now" — used by both the
|
|
// switch-away path and the world roster so the two can never disagree.
|
|
// Mode is one of: no-tag / no-schedule / timed /
|
|
// fallback-in-raid / no-position / other-raid / static-spot.
|
|
// ⛑ no-tag / no-schedule / other-raid / no-position carry a null
|
|
// Entry AND a null Spot — the caller must NOT fall back to "stand
|
|
// next to the player" or you get 09-25's crowd-at-your-feet bug.
|
|
//
|
|
func NpcActivityCatalog4229938_Resolve(spiritId uint32, daySecond int32, raid uint32, requirePosition bool, px float32, py float32, pz float32) NpcActivityChoice {
|
|
// Catalog helper NpcActivityCatalog4229938_Resolve
|
|
var zero NpcActivityChoice; return zero
|
|
}
|
|
|