241 lines
8.8 KiB
Go
241 lines
8.8 KiB
Go
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
|