Files
GaCy-GO/internal/gameserver/handlers_challenge_scene.go
T

278 lines
8.5 KiB
Go

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
// 2. detect arena floor platform height from props in scene
// In some challenge arenas (e.g. entrance 3012602 / AS Starward 30225),
// the boss monster pos in config is placed at Y = -193081 (22m below the floor),
// while the arena stage prop (102259) is at Y = -170770.
// Spawning at monsPos.Y puts the player under the map into the void.
hasFloorProp := false
var floorY int32 = -2147483648
for _, g := range scene.Scenes {
for _, p := range g.Props {
dx := float64(p.Pos.X)
dz := float64(p.Pos.Z)
if math.Sqrt(dx*dx+dz*dz) < 150000.0 { // within 150 meters of center
if p.Pos.Y > floorY {
floorY = p.Pos.Y
hasFloorProp = true
}
}
}
}
if hasFloorProp && floorY > monsPos.Y {
monsPos.Y = floorY
}
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},
}
// NOTE: keep player.Scene intact so returning to overworld or relogging spawns in overworld
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 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{}
for _, gid := range sortedU32Keys2(scene.Scenes) {
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