163 lines
6.3 KiB
Go
163 lines
6.3 KiB
Go
package common
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// Scene metadata is exported from the installed Client's RuntimeFloor tables.
|
|
// A complete group list allows us to exclude deleted/client-owned groups
|
|
// without maintaining special cases for individual maps or group IDs.
|
|
type sceneMetadata struct {
|
|
ClientVersion string `json:"client_version"`
|
|
Floors map[uint32]struct {
|
|
MapTeleportEntryID uint32 `json:"map_teleport_entry_id"`
|
|
DimensionID *uint32 `json:"dimension_id"`
|
|
DimensionGroups map[uint32][]uint32 `json:"dimension_groups"`
|
|
GroupProperties map[uint32]map[string]GroupPropertyRule `json:"group_properties"`
|
|
SavedValues map[string]int32 `json:"saved_values"`
|
|
SavedValueRules map[string]GroupPropertyRule `json:"saved_value_rules"`
|
|
EntrySavedValues map[uint32]map[string]int32 `json:"entry_saved_values"`
|
|
Props map[uint32][]ScenePropInfo `json:"props"`
|
|
StartAnchor *AnchorItem `json:"start_anchor"`
|
|
FloorDefaultAnchor *AnchorItem `json:"floor_default_anchor"`
|
|
Monsters map[uint32]map[uint32]TeleportInfo `json:"monsters"`
|
|
GroupCount int `json:"group_count"`
|
|
Groups map[uint32]struct {
|
|
LoadSide string `json:"load_side"`
|
|
Deleted bool `json:"is_deleted"`
|
|
} `json:"groups"`
|
|
} `json:"floors"`
|
|
}
|
|
|
|
func applySceneMetadata(res *JsonConfig) {
|
|
raw, err := ReadResourceFile("resources/SceneMetadata.json")
|
|
if err != nil {
|
|
return
|
|
}
|
|
var metadata sceneMetadata
|
|
if err := json.Unmarshal(raw, &metadata); err != nil {
|
|
LogInfo("[SCENE] Invalid SceneMetadata: %v", err)
|
|
return
|
|
}
|
|
config, err := LoadRuntimeConfig()
|
|
if err != nil || metadata.ClientVersion != config.ClientVersion {
|
|
LogInfo("[SCENE] SceneMetadata Client revision does not match runtime config; not applied")
|
|
return
|
|
}
|
|
for entryID, configs := range res.LevelOutputConfigs {
|
|
for name, scene := range configs {
|
|
var planeID, floorID uint32
|
|
if n, _ := fmt.Sscanf(name, "P%d_F%d", &planeID, &floorID); n != 2 {
|
|
continue
|
|
}
|
|
floor, ok := metadata.Floors[floorID]
|
|
if !ok {
|
|
continue
|
|
}
|
|
if floor.GroupCount == 0 || len(floor.Groups) != floor.GroupCount {
|
|
LogInfo("[SCENE] Incomplete group metadata for floor %d; not applied", floorID)
|
|
continue
|
|
}
|
|
if target := floor.MapTeleportEntryID; target != 0 {
|
|
entrance, exists := res.MapEntrances[target]
|
|
canonical := fmt.Sprintf("P%d_F%d", planeID, floorID)
|
|
if exists && entrance.PlaneID == planeID && entrance.FloorID == floorID &&
|
|
entrance.EntranceType == "Town" && res.LevelOutputConfigs[target][canonical] != nil {
|
|
if res.MapTeleportEntrances == nil {
|
|
res.MapTeleportEntrances = map[uint32]uint32{}
|
|
}
|
|
res.MapTeleportEntrances[floorID] = target
|
|
res.MapDefaultEntranceMap[floorID] = target
|
|
} else {
|
|
LogInfo("[SCENE] Map teleport entrance %d is not a configured Town on floor %d; routing override not applied", target, floorID)
|
|
}
|
|
}
|
|
if floor.StartAnchor != nil {
|
|
res.SceneAnchors[entryID] = []AnchorItem{*floor.StartAnchor}
|
|
}
|
|
if floor.FloorDefaultAnchor != nil && floor.FloorDefaultAnchor.ID != 0 {
|
|
if res.SceneDefaultAnchors == nil {
|
|
res.SceneDefaultAnchors = map[uint32]AnchorItem{}
|
|
}
|
|
res.SceneDefaultAnchors[floorID] = *floor.FloorDefaultAnchor
|
|
}
|
|
if floor.GroupProperties != nil {
|
|
scene.GroupProperties = floor.GroupProperties
|
|
}
|
|
if floor.DimensionID != nil {
|
|
scene.DimensionID = *floor.DimensionID
|
|
}
|
|
var dimensionGroups map[uint32]bool
|
|
if floor.DimensionGroups != nil {
|
|
ids, exists := floor.DimensionGroups[scene.DimensionID]
|
|
if !exists || len(ids) == 0 {
|
|
LogInfo("[SCENE] Missing group membership for floor %d dimension %d; scene override not applied", floorID, scene.DimensionID)
|
|
continue
|
|
}
|
|
dimensionGroups = make(map[uint32]bool, len(ids))
|
|
for _, id := range ids {
|
|
dimensionGroups[id] = true
|
|
}
|
|
}
|
|
if floor.SavedValues != nil {
|
|
// RuntimeDimension exports the actual DefaultValue, not MaxValue.
|
|
// This applies only to floors with a complete captured override.
|
|
scene.SavedValues = floor.SavedValues
|
|
}
|
|
if overrides, exists := floor.EntrySavedValues[entryID]; exists {
|
|
entrance, known := res.MapEntrances[entryID]
|
|
valid := known && entrance.FloorID == floorID && entrance.PlaneID == planeID
|
|
values := make(map[string]int32, len(floor.SavedValues))
|
|
for key, value := range floor.SavedValues {
|
|
values[key] = value
|
|
}
|
|
for key, value := range overrides {
|
|
rule, known := floor.SavedValueRules[key]
|
|
if !known || value < rule.MinValue || value > rule.MaxValue {
|
|
valid = false
|
|
break
|
|
}
|
|
values[key] = value
|
|
}
|
|
if valid {
|
|
// Never mutate the shared floor defaults: entrances sharing a
|
|
// floor can intentionally represent different story states.
|
|
scene.SavedValues = values
|
|
} else {
|
|
LogInfo("[SCENE] Invalid saved-value profile entry=%d floor=%d; override not applied", entryID, floorID)
|
|
}
|
|
}
|
|
for groupID := range scene.Scenes {
|
|
group, exists := floor.Groups[groupID]
|
|
if !exists || group.Deleted || group.LoadSide == "Client" || (dimensionGroups != nil && !dimensionGroups[groupID]) {
|
|
delete(scene.Scenes, groupID)
|
|
LogInfo("[SCENE] Excluded group entry=%d floor=%d group=%d present=%t deleted=%t side=%s", entryID, floorID, groupID, exists, group.Deleted, group.LoadSide)
|
|
continue
|
|
}
|
|
data := scene.Scenes[groupID]
|
|
if floor.Props != nil {
|
|
data.Props = floor.Props[groupID]
|
|
}
|
|
for index := range data.Monsters {
|
|
monster := &data.Monsters[index]
|
|
if placement, ok := floor.Monsters[groupID][monster.InstID]; ok {
|
|
monster.Pos, monster.Rot = placement.Pos, placement.Rot
|
|
}
|
|
}
|
|
scene.Scenes[groupID] = data
|
|
}
|
|
// Resource updates may introduce server props in previously empty groups.
|
|
for groupID, props := range floor.Props {
|
|
group, exists := floor.Groups[groupID]
|
|
if !exists || group.Deleted || group.LoadSide == "Client" || (dimensionGroups != nil && !dimensionGroups[groupID]) {
|
|
continue
|
|
}
|
|
data := scene.Scenes[groupID]
|
|
data.Props = props
|
|
scene.Scenes[groupID] = data
|
|
}
|
|
}
|
|
}
|
|
}
|