package gameserver import ( "errors" "fmt" "math" "sync" "google.golang.org/protobuf/proto" "gac-hkrpg-go/internal/common" goproto "gac-hkrpg-go/protocol/proto" ) // --------------------------------------------------------------------------- // tierce lineup store + challenge scene loading // --------------------------------------------------------------------------- var ( tierceLineups = map[uint32][][]uint32{} tierceLineupsMu sync.Mutex ) // loadChallengeScene is the Go port of scene.rs load_challenge_scene. func loadChallengeScene(s *Session, entryID, targetGroupID, monsterID, eventID uint32, avatarIDs []uint32) (*goproto.SceneInfo, *goproto.MotionInfo, error) { player := s.JSON() if player == nil { return nil, nil, errors.New("data is not set!") } res, err := common.GameRes() if err != nil { return nil, nil, err } name, scene, ok := firstSceneOfEntry(res, entryID) if !ok { return nil, nil, fmt.Errorf("Challenge Map Entrance Not Found %d", entryID) } planeID, floorID, err := parsePlaneFloor(name) if err != nil { return nil, nil, err } // 1. locate the boss monster position from scene config var monsPos common.Position var instID uint32 = 1 fallbackPos := common.Position{X: -61000, Y: -2141, Z: -170700, RotY: 90000} if grp, okg := scene.Scenes[targetGroupID]; okg && len(grp.Monsters) > 0 { m := grp.Monsters[0] monsPos = common.Position{X: m.Pos.X, Y: m.Pos.Y, Z: m.Pos.Z, RotY: m.Rot.Y} instID = m.InstID } else { found := false for _, gid := range sortedU32Keys2(scene.Scenes) { g := scene.Scenes[gid] if len(g.Monsters) > 0 { m := g.Monsters[0] monsPos = common.Position{X: m.Pos.X, Y: m.Pos.Y, Z: m.Pos.Z, RotY: m.Rot.Y} instID = m.InstID found = true break } } if !found { monsPos = fallbackPos } } // 2. position the player safely between boss and arena center centerVX := float64(-monsPos.X) centerVZ := float64(-monsPos.Z) distToCenter := math.Sqrt(centerVX*centerVX + centerVZ*centerVZ) var spawnX, spawnY, spawnZ, spawnRotY, bossRotY int32 if distToCenter > 1000.0 { nx := centerVX / distToCenter nz := centerVZ / distToCenter offset := math.Min(6000.0, distToCenter*0.6) spawnX = monsPos.X + int32(nx*offset) spawnZ = monsPos.Z + int32(nz*offset) pDeg := int32(math.Round(math.Atan2(-nx, -nz) * 180.0 / math.Pi)) if pDeg < 0 { pDeg += 360 } bDeg := int32(math.Round(math.Atan2(nx, nz) * 180.0 / math.Pi)) if bDeg < 0 { bDeg += 360 } spawnY = monsPos.Y spawnRotY = pDeg * 1000 bossRotY = bDeg * 1000 } else { spawnX, spawnY, spawnZ, spawnRotY, bossRotY = 0, monsPos.Y, 6000, 180000, 0 } monsPos.RotY = bossRotY playerMotion := &goproto.MotionInfo{ Pos: &goproto.Vector{X: spawnX, Y: spawnY, Z: spawnZ}, Rot: &goproto.Vector{Y: spawnRotY}, } player.Scene.EntryID = entryID player.Scene.FloorID = floorID player.Scene.PlaneID = planeID sceneInfo := &goproto.SceneInfo{ FloorId: floorID, PlaneId: planeID, EntryId: entryID, GameModeType: scene.PlaneType, LeaderEntityId: 1, WorldId: scene.WorldID, SceneIdentifier: &goproto.SceneIdentifier{FloorId: floorID}, SceneMissionInfo: &goproto.MissionStatusBySceneInfo{}, } if sceneInfo.WorldId == 100 { sceneInfo.WorldId = 501 } sceneInfo.LightenSectionList = scene.Sections // load props for group 1, target group, and common prop groups (7/8) propEntityID := uint32(1000) s.PropEntities = map[uint32]PropEntry{} for _, gid := range sortedU32Keys2(scene.Scenes) { if gid != 1 && gid != targetGroupID && gid != 7 && gid != 8 { continue } group := scene.Scenes[gid] groupInfo := &goproto.SceneEntityGroupInfo{GroupId: gid} for i := range group.Props { prop := &group.Props[i] propEntityID++ s.PropEntities[propEntityID] = PropEntry{PropID: prop.PropID, InstID: prop.InstID, State: prop.PropState} groupInfo.EntityList = append(groupInfo.EntityList, &goproto.SceneEntityInfo{ InstId: prop.InstID, GroupId: prop.GroupID, EntityId: propEntityID, Motion: (&common.Position{X: prop.Pos.X, Y: prop.Pos.Y, Z: prop.Pos.Z, RotY: prop.Rot.Y}).ToMotionInfo(), Entity: &goproto.SceneEntityInfo_Prop{ Prop: &goproto.ScenePropInfo{PropState: prop.PropState, PropId: prop.PropID}, }, }) } if len(groupInfo.EntityList) > 0 { sceneInfo.EntityGroupList = append(sceneInfo.EntityGroupList, groupInfo) } } // single boss monster (entity 30001, registered for SceneCastSkill battles) monsterGroup := &goproto.SceneEntityGroupInfo{GroupId: targetGroupID} s.MonsterEntities = map[uint32]uint32{30001: eventID} monsterGroup.EntityList = append(monsterGroup.EntityList, &goproto.SceneEntityInfo{ InstId: instID, GroupId: targetGroupID, EntityId: 30001, Motion: monsPos.ToMotionInfo(), Entity: &goproto.SceneEntityInfo_NpcMonster{ NpcMonster: &goproto.SceneNpcMonsterInfo{ MonsterId: monsterID, EventId: eventID, WorldLevel: 6, }, }, }) sceneInfo.EntityGroupList = append(sceneInfo.EntityGroupList, monsterGroup) // player team actors avatarsToUse := avatarIDs if len(avatarsToUse) == 0 { for _, slot := range player.SortedLineupKeys() { if v := player.Lineups[slot]; v != 0 { avatarsToUse = append(avatarsToUse, v) } } } playerGroup := &goproto.SceneEntityGroupInfo{State: 0, GroupId: 0} for slot, aid := range avatarsToUse { playerGroup.EntityList = append(playerGroup.EntityList, &goproto.SceneEntityInfo{ EntityId: uint32(slot) + 1, Motion: playerMotion, Entity: &goproto.SceneEntityInfo_Actor{ Actor: &goproto.SceneActorInfo{ AvatarType: goproto.AvatarType_AVATAR_FORMAL_TYPE, BaseAvatarId: aid, MapLayer: 0, Uid: 1337, }, }, }) } sceneInfo.EntityGroupList = append(sceneInfo.EntityGroupList, playerGroup) // NOTE: do not overwrite the saved scene so the player returns to the // open world on logout/battle end return sceneInfo, playerMotion, nil } // handleStartChallengePeak ports on_start_challenge_peak_cs_req (cmd 8948): // StartChallengePeakScRsp has no scene field, so the arena is sent via // EnterSceneByServerScNotify (cmd 1427). func handleStartChallengePeak(s *Session, req *goproto.StartChallengePeakCsReq, rsp *goproto.StartChallengePeakScRsp) error { rsp.Retcode = 0 peakID := req.PeakId stage, ok := getChallengeData().Peak[itoa(int(peakID))] if !ok { Log("[PEAK] peak_id=" + itoa(int(peakID)) + " not found in challenge_data!") return nil } eventID := stage.EventID monsterID := stage.MonsterID buff := stage.DefaultBuff if req.BossBuffId != 0 { buff = req.BossBuffId } Log("[PEAK] Start: peak_id=" + itoa(int(peakID)) + " event=" + itoa(int(eventID)) + " monster=" + itoa(int(monsterID))) avatars := dedupeTruncate4(req.PeakAvatarIdList) if len(avatars) == 0 { avatars = challengeAvatarsFallback(s) } if player := s.JSON(); player != nil { customLineup := map[uint32]uint32{} for i, aid := range avatars { customLineup[uint32(i)] = aid } player.BattleConfig.CustomBattleLineup = customLineup player.BattleConfig.StageID = eventID player.BattleConfig.CycleCount = 30 player.BattleConfig.BattleType = common.BattleTypeDefault player.BattleConfig.Monsters = [][]common.Monster{{{Level: 68, MonsterID: monsterID}}} if buff != 0 { player.BattleConfig.Blessings = []common.BattleBuffJson{{ID: buff, Level: 1}} } player.SavePersistent() } // standard arena 3000101 (group 2 = boss position) sceneInfo, _, err := loadChallengeScene(s, 3000101, 2, monsterID, eventID, avatars) if err != nil { Log("[PEAK] Failed to load peak arena scene!") return nil } custom := map[uint32]uint32{} for i, aid := range avatars { custom[uint32(i)] = aid } lineupInfo := common.ToLineupInfo(custom) return s.Send(&goproto.EnterSceneByServerScNotify{ Scene: sceneInfo, Lineup: lineupInfo, }) } var _ = proto.Marshal