Save endgame progress, team presets, technique support and camera controls

This commit is contained in:
CAtlas0784 committed 2026-10-17 23:44:36 +07:00
1 parent a681affdb3
commit 639cef7d19
52 files changed
+11940 -766

No files matched your search

+46 -119
View File
@@ -3,8 +3,6 @@ package gameserver
import (
"errors"
"fmt"
"math"
"sync"
"google.golang.org/protobuf/proto"
@@ -16,11 +14,6 @@ import (
// 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()
@@ -41,101 +34,38 @@ func loadChallengeScene(s *Session, entryID, targetGroupID, monsterID, eventID u
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
}
// 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, fmt.Errorf("challenge entrance %d group %d has no monster placement", entryID, targetGroupID)
}
// 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
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"
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"
}
SceneDebugLog("[CHALLENGE] spawn entry=%d floor=%d source=%s player=%v monster=%v", entryID, floorID, spawnSource, playerMotion, monsPos)
centerVX := float64(-monsPos.X)
centerVZ := float64(-monsPos.Z)
distToCenter := math.Sqrt(centerVX*centerVX + centerVZ*centerVZ)
s.resetTechniqueRoom()
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,
GameModeType: sceneModeChallenge,
LeaderEntityId: 1,
WorldId: scene.WorldID,
SceneIdentifier: &goproto.SceneIdentifier{FloorId: floorID},
SceneMissionInfo: &goproto.MissionStatusBySceneInfo{},
}
if sceneInfo.WorldId == 100 {
sceneInfo.WorldId = 501
FloorSavedData: scene.SavedValues,
}
sceneInfo.LightenSectionList = scene.Sections
@@ -144,7 +74,7 @@ func loadChallengeScene(s *Session, entryID, targetGroupID, monsterID, eventID u
s.PropEntities = map[uint32]PropEntry{}
for _, gid := range sortedU32Keys2(scene.Scenes) {
group := scene.Scenes[gid]
groupInfo := &goproto.SceneEntityGroupInfo{GroupId: gid}
groupInfo := &goproto.SceneEntityGroupInfo{State: 1, GroupId: gid}
for i := range group.Props {
prop := &group.Props[i]
propEntityID++
@@ -165,7 +95,7 @@ func loadChallengeScene(s *Session, entryID, targetGroupID, monsterID, eventID u
}
// single boss monster (entity 30001, registered for SceneCastSkill battles)
monsterGroup := &goproto.SceneEntityGroupInfo{GroupId: targetGroupID}
monsterGroup := &goproto.SceneEntityGroupInfo{State: 1, GroupId: targetGroupID}
s.MonsterEntities = map[uint32]uint32{30001: eventID}
s.ChallengeStageID = eventID
monsterGroup.EntityList = append(monsterGroup.EntityList, &goproto.SceneEntityInfo{
@@ -203,7 +133,7 @@ func loadChallengeScene(s *Session, entryID, targetGroupID, monsterID, eventID u
AvatarType: goproto.AvatarType_AVATAR_FORMAL_TYPE,
BaseAvatarId: aid,
MapLayer: 0,
Uid: 1337,
Uid: playerUID,
},
},
})
@@ -229,10 +159,6 @@ func handleStartChallengePeak(s *Session, req *goproto.StartChallengePeakCsReq,
}
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)
@@ -240,34 +166,35 @@ func handleStartChallengePeak(s *Session, req *goproto.StartChallengePeakCsReq,
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()
buffs := append([]uint32(nil), stage.Buffs...)
if stage.IsBoss && s.PeakHardModes[stage.GroupID] {
eventID = stage.HardEventID
buffs = append([]uint32(nil), stage.HardBuffs...)
}
// 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!")
if req.BossBuffId != 0 {
buffs = append(buffs, req.BossBuffId)
}
if stage.Entrance == 0 || stage.Group == 0 || eventID == 0 || len(avatars) == 0 {
rsp.Retcode = uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)
return nil
}
custom := map[uint32]uint32{}
for i, aid := range avatars {
custom[uint32(i)] = aid
previousConfig := s.ChallengeBattleConfig
if err := setChallengeBattleConfig(s, avatars, peakID, eventID, buffs); err != nil {
SceneDebugLog("[PEAK] stage rejected: %v", err)
rsp.Retcode = uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)
return nil
}
lineupInfo := common.ToLineupInfo(custom)
s.ChallengeBattleConfig.BattleType = common.BattleTypeAA
sceneInfo, _, 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)
return s.Send(&goproto.EnterSceneByServerScNotify{
Scene: sceneInfo,