package gameserver import ( "encoding/json" "path/filepath" "strconv" "testing" "gac-hkrpg-go/internal/common" goproto "gac-hkrpg-go/protocol/proto" ) func TestSceneEntranceAnchorKeepsRoomsOnSameFloorDistinct(t *testing.T) { res := &common.JsonConfig{ MapEntrances: map[uint32]common.MapEntranceEntry{ 10: {FloorID: 20, StartAnchorID: 2}, 11: {FloorID: 20, StartAnchorID: 2}, }, SceneAnchors: map[uint32][]common.AnchorItem{ 10: {{ID: 1, Pos: common.Vector{X: 5}}, {ID: 2, Pos: common.Vector{Y: -15000}}}, 11: {{ID: 2, Pos: common.Vector{X: 25}}}, }, SceneDefaultAnchors: map[uint32]common.AnchorItem{20: {ID: 1, Pos: common.Vector{X: 99}}}, } first, ok := sceneEntranceAnchor(res, 10, 20) if !ok || first.ID != 2 || first.Pos.Y != -15000 { t.Fatalf("room must retain its selected anchor, including a negative height: %+v, %t", first, ok) } second, ok := sceneEntranceAnchor(res, 11, 20) if !ok || second.Pos.X != 25 { t.Fatalf("second room must use its own entrance anchor: %+v, %t", second, ok) } for _, pair := range [][2]uint32{{10, 21}, {12, 20}} { if _, ok := sceneEntranceAnchor(res, pair[0], pair[1]); ok { t.Fatalf("must reject an unknown entrance or mismatching floor: %v", pair) } } res.MapEntrances[10] = common.MapEntranceEntry{FloorID: 20, StartAnchorID: 3} if _, ok := sceneEntranceAnchor(res, 10, 20); ok { t.Fatal("must not substitute another anchor when the requested anchor is absent") } res.MapEntrances[12] = common.MapEntranceEntry{FloorID: 20} if anchor, ok := sceneEntranceAnchor(res, 12, 20); !ok || anchor.Pos.X != 99 { t.Fatal("entrance without an explicit start anchor must use the captured floor default") } } // Regression for the entrance reported by the Client, using the actual captured // membership and placements rather than a second hand-written group list. func TestTrainRoomScenePacketMatchesCapturedDimension(t *testing.T) { chdirRepositoryRoot(t) configPath, err := filepath.Abs("server.json") if err != nil { t.Fatal(err) } t.Setenv("GAC_CONFIG", configPath) res, err := common.GameRes() if err != nil { t.Fatal(err) } const reportedEntrance uint32 = 100000352 entrance, exists := res.MapEntrances[reportedEntrance] if !exists { t.Fatal("reported train entrance missing from Client MapEntrance") } raw, err := common.ReadResourceFile("resources/SceneMetadata.json") if err != nil { t.Fatal(err) } var captured struct { Floors map[string]struct { DimensionID uint32 `json:"dimension_id"` Groups map[string][]uint32 `json:"dimension_groups"` Saved map[string]int32 `json:"saved_values"` DefaultAnchor common.AnchorItem `json:"floor_default_anchor"` } `json:"floors"` } if err := json.Unmarshal(raw, &captured); err != nil { t.Fatal(err) } floor := captured.Floors[strconv.FormatUint(uint64(entrance.FloorID), 10)] allowed := map[uint32]bool{} for _, group := range floor.Groups[strconv.FormatUint(uint64(floor.DimensionID), 10)] { allowed[group] = true } if len(allowed) == 0 { t.Fatal("reported room has no captured dimension membership") } s := NewSession(func([]byte) error { t.Fatal("scene construction must not send or persist data"); return nil }, 1, 1) s.SetJSON(&common.FreesrData{Lineups: map[uint32]uint32{0: 1001}}) scene, err := loadScene(s, reportedEntrance, true, false, nil, &goproto.SceneIdentifier{FloorId: entrance.FloorID}) if err != nil { t.Fatal(err) } if scene.EntryId != reportedEntrance || scene.FloorId != entrance.FloorID || scene.DimensionId != floor.DimensionID { t.Fatalf("wrong scene identity: %v", scene) } for _, group := range scene.EntityGroupList { if group.GroupId != 0 && !allowed[group.GroupId] { t.Fatalf("packet includes group %d from another dimension", group.GroupId) } } for name, expected := range floor.Saved { if value, present := scene.FloorSavedData[name]; !present || value != expected { t.Fatalf("saved value %s: got %d/%t, expected %d", name, value, present, expected) } } anchor, exists := sceneEntranceAnchor(res, reportedEntrance, entrance.FloorID) if !exists { t.Fatal("reported entrance has no resource anchor") } pos := s.JSON().Position if pos.X != anchor.Pos.X || pos.Y != anchor.Pos.Y || pos.Z != anchor.Pos.Z || pos.RotY != anchor.Rot.Y { t.Fatalf("room spawned at a floor checkpoint instead of its entrance anchor: %+v", pos) } if floor.DefaultAnchor.ID == 0 { t.Fatal("train floor has no captured default anchor") } defaultEntryFound := false for id, row := range res.MapEntrances { if row.FloorID != entrance.FloorID || row.StartAnchorID != 0 || row.EntranceType != "Town" { continue } defaultEntryFound = true if _, err := loadScene(s, id, true, false, nil, nil); err != nil { t.Fatal(err) } pos = s.JSON().Position if pos.X != floor.DefaultAnchor.Pos.X || pos.Y != floor.DefaultAnchor.Pos.Y || pos.Z != floor.DefaultAnchor.Pos.Z { t.Fatalf("default entrance %d spawned at a checkpoint instead of native floor start: %+v", id, pos) } } if !defaultEntryFound { t.Fatal("no default Town entrance tested") } _, config, exists := firstSceneOfEntry(res, reportedEntrance) if !exists { t.Fatal("reported entrance has no scene config") } for _, group := range config.Scenes { for id, checkpoint := range group.Teleports { if _, err := loadScene(s, reportedEntrance, true, false, &id, nil); err != nil { t.Fatal(err) } pos = s.JSON().Position if pos.X != checkpoint.Pos.X || pos.Y != checkpoint.Pos.Y || pos.Z != checkpoint.Pos.Z { t.Fatal("explicit checkpoint teleport was replaced by an entrance anchor") } return } } t.Fatal("reported room has no checkpoint to verify explicit teleport precedence") }