package gameserver import ( "errors" "fmt" "google.golang.org/protobuf/proto" "gac-hkrpg-go/internal/common" goproto "gac-hkrpg-go/protocol/proto" ) // --------------------------------------------------------------------------- // tierce lineup store + challenge scene loading // --------------------------------------------------------------------------- // 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, bool, error) { player := s.JSON() if player == nil { return nil, nil, false, errors.New("data is not set!") } res, err := common.GameRes() if err != nil { return nil, nil, false, err } name, scene, ok := firstSceneOfEntry(res, entryID) if !ok { return nil, nil, false, fmt.Errorf("Challenge Map Entrance Not Found %d", entryID) } planeID, floorID, err := parsePlaneFloor(name) if err != nil { return nil, nil, false, err } // Use the selected group's resource placement, with no guessed arena // height or offset towards world origin (arena floors need not be there). group, ok := scene.Scenes[targetGroupID] if !ok || len(group.Monsters) == 0 { return nil, nil, false, fmt.Errorf("challenge entrance %d group %d has no monster placement", entryID, targetGroupID) } placement := group.Monsters[0] monsPos := common.Position{X: placement.Pos.X, Y: placement.Pos.Y, Z: placement.Pos.Z, RotY: placement.Rot.Y} instID := placement.InstID playerMotion := monsPos.ToMotionInfo() spawnSource := "monster-placement" hasAnchor := false if anchors := res.SceneAnchors[entryID]; len(anchors) > 0 { anchor := anchors[0] // PearlSR scene_mgr.zig getAnchorMotion. playerMotion = (&common.Position{X: anchor.Pos.X, Y: anchor.Pos.Y, Z: anchor.Pos.Z, RotY: anchor.Rot.Y}).ToMotionInfo() spawnSource = "anchor" hasAnchor = true } SceneDebugLog("[CHALLENGE] spawn entry=%d floor=%d source=%s player=%v monster=%v", entryID, floorID, spawnSource, playerMotion, monsPos) s.resetTechniqueRoom() // NOTE: keep player.Scene intact so returning to overworld or relogging spawns in overworld sceneInfo := &goproto.SceneInfo{ FloorId: floorID, PlaneId: planeID, EntryId: entryID, GameModeType: sceneModeChallenge, LeaderEntityId: 1, WorldId: scene.WorldID, SceneIdentifier: &goproto.SceneIdentifier{FloorId: floorID}, SceneMissionInfo: &goproto.MissionStatusBySceneInfo{}, FloorSavedData: scene.SavedValues, } sceneInfo.LightenSectionList = scene.Sections // load all props in the arena scene (do not filter out prop groups like group 2 / group 3) propEntityID := uint32(1000) s.PropEntities = map[uint32]PropEntry{} s.CurrentSceneFloor = floorID for _, gid := range sortedU32Keys2(scene.Scenes) { group := scene.Scenes[gid] groupInfo := &goproto.SceneEntityGroupInfo{State: 1, GroupId: gid, PropertyMap: s.sceneGroupProperties(floorID, gid, scene.GroupProperties[gid])} for i := range group.Props { prop := &group.Props[i] propEntityID++ s.PropEntities[propEntityID] = PropEntry{GroupID: prop.GroupID, 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{State: 1, GroupId: targetGroupID} s.MonsterEntities = map[uint32]uint32{30001: eventID} s.SceneMonsterIdentities = map[uint32]sceneMonsterIdentity{30001: {MonsterID: monsterID, GroupID: targetGroupID}} s.ChallengeStageID = 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: playerWorldLevel, }, }, }) 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: playerUID, }, }, }) } 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, hasAnchor, 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!") rsp.Retcode = uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST) return nil } if stage.SceneResourceError != "" { SceneDebugLog("[PEAK] scene resource rejected peak=%d: %s", peakID, stage.SceneResourceError) rsp.Retcode = uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST) return nil } eventID := stage.EventID monsterID := stage.MonsterID 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) } buffs := append([]uint32(nil), stage.Buffs...) if stage.IsBoss && s.PeakHardModes[stage.GroupID] { eventID = stage.HardEventID buffs = append([]uint32(nil), stage.HardBuffs...) } if req.BossBuffId != 0 { buffs = append(buffs, req.BossBuffId) } if stage.Entrance == 0 || stage.Group == 0 || monsterID == 0 || eventID == 0 || len(avatars) == 0 { rsp.Retcode = uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST) return nil } previousConfig := s.ChallengeBattleConfig if err := setPeakBattleConfig(s, avatars, peakID, eventID, buffs); err != nil { SceneDebugLog("[PEAK] stage rejected: %v", err) rsp.Retcode = uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST) return nil } sceneInfo, playerMotion, hasAnchor, err := loadChallengeScene(s, stage.Entrance, stage.Group, monsterID, eventID, avatars) if err != nil { s.ChallengeBattleConfig = previousConfig SceneDebugLog("[PEAK] scene rejected: %v", err) rsp.Retcode = uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST) return nil } s.ChallengeRun = &challengeRun{ID: peakID, Peak: true, Single: true, Rooms: []challengeRoom{{Entrance: stage.Entrance, Group: stage.Group, Monster: monsterID, Event: eventID, Avatars: avatars, Buffs: buffs}}, Scores: make([]uint32, 1), PeakBuff: req.BossBuffId} lineupInfo := lineupInfoFromAvatars(avatars, sceneInfo.PlaneId, goproto.ExtraLineupType_ExtraLineupType_LineupChallenge, 0) // Match the Client-tested PearlSR AA transition: send the arena scene, // move each actor to the scene anchor when present, then let the typed // dispatcher send StartChallengePeakScRsp last. The lineup is included in // EnterSceneByServerScNotify, so a separate SyncLineupNotify is unnecessary. if err := s.Send(&goproto.EnterSceneByServerScNotify{ Scene: sceneInfo, Lineup: lineupInfo, }); err != nil { return err } anchorMoves := 0 if hasAnchor { for _, group := range sceneInfo.EntityGroupList { for _, entity := range group.EntityList { if entity.GetActor() == nil { continue } if err := s.Send(&goproto.SceneEntityMoveScNotify{ EntityId: entity.EntityId, EntryId: sceneInfo.EntryId, Motion: playerMotion, }); err != nil { return err } anchorMoves++ } } } SceneDebugLog("[PEAK] transition peak=%d entry=%d anchor_moves=%d", peakID, sceneInfo.EntryId, anchorMoves) return nil } var _ = proto.Marshal