fix: resolve Calyx NullReferenceException by omitting WaveParam, capping wave count, and setting default monster HP

This commit is contained in:
CAtlas0784 committed 2026-10-16 20:07:49 +07:00
1 parent 57714379de
commit 5b33715c1b
2 files changed
+162 -29

No files matched your search

+84
View File
@@ -1,6 +1,10 @@
package gameserver
import (
"encoding/json"
"os"
"sync"
"gac-hkrpg-go/internal/common"
goproto "gac-hkrpg-go/protocol/proto"
)
@@ -8,6 +12,20 @@ import (
func init() {
handle("StartCocoonStage", func(s *Session, req *goproto.StartCocoonStageCsReq, rsp *goproto.StartCocoonStageScRsp) error {
battleInfo := createBattleInfo(s, 0, 0)
if req.WorldLevel > 0 {
battleInfo.WorldLevel = req.WorldLevel
}
if req.Wave > 0 && int(req.Wave) <= len(battleInfo.MonsterWaveList) {
battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:req.Wave]
} else if len(battleInfo.MonsterWaveList) > 1 {
battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:1]
}
battleInfo.JAOIOKBBMFC = uint32(len(battleInfo.MonsterWaveList))
for _, w := range battleInfo.MonsterWaveList {
w.WaveId = 1
w.WaveParam = nil
w.BattleStageId = battleInfo.StageId
}
CURRENT_BATTLE.Store(s.Token, battleInfo)
rsp.PropEntityId = req.PropEntityId
@@ -21,6 +39,20 @@ func init() {
// (the Rust code referenced an older QuickStartCocoonStage* name).
handleNamed("StartQuickCocoonStage", "StartQuickCocoonStageRsp", func(s *Session, req *goproto.StartQuickCocoonStageCsReq, rsp *goproto.StartQuickCocoonStageRsp) error {
battleInfo := createBattleInfo(s, 0, 0)
if req.WorldLevel > 0 {
battleInfo.WorldLevel = req.WorldLevel
}
if req.Wave > 0 && int(req.Wave) <= len(battleInfo.MonsterWaveList) {
battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:req.Wave]
} else if len(battleInfo.MonsterWaveList) > 1 {
battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:1]
}
battleInfo.JAOIOKBBMFC = uint32(len(battleInfo.MonsterWaveList))
for _, w := range battleInfo.MonsterWaveList {
w.WaveId = 1
w.WaveParam = nil
w.BattleStageId = battleInfo.StageId
}
CURRENT_BATTLE.Store(s.Token, battleInfo)
rsp.CocoonId = req.CocoonId
@@ -378,6 +410,27 @@ func createBattleInfo(s *Session, casterID, skillIndex uint32) *goproto.SceneBat
})
}
// Stage invasion buff (pearl-sr 4.6.53: e.g. Peak 11 stage 4 30511021/30511022 -> buff 3034004)
if invasionID := getStageInvasionID(battleInfo.StageId); invasionID != 0 {
battleInfo.BuffList = append(battleInfo.BuffList, &goproto.BattleBuff{
Id: 3034000 + invasionID,
Level: 1,
OwnerId: firstAvatarIdx,
WaveFlag: 0xffffffff,
TargetIndexList: []uint32{0},
DynamicValues: map[string]float32{"SkillIndex": 1},
})
// Aha Invasion support buff
battleInfo.BuffList = append(battleInfo.BuffList, &goproto.BattleBuff{
Id: 3034013,
Level: 1,
OwnerId: firstAvatarIdx,
WaveFlag: 0xffffffff,
TargetIndexList: []uint32{0},
DynamicValues: map[string]float32{"SkillIndex": 1},
})
}
// Aha Global Passive (151104) — always on, same as the Rust build
battleInfo.BuffList = append(battleInfo.BuffList, &goproto.BattleBuff{
Id: 151104, Level: 1, OwnerId: 0xffffffff, WaveFlag: 0xffffffff,
@@ -386,6 +439,37 @@ func createBattleInfo(s *Session, casterID, skillIndex uint32) *goproto.SceneBat
return battleInfo
}
type stageInvasionConfigData struct {
StageInvasionConfig []struct {
StageID uint32 `json:"StageID"`
InvasionID uint32 `json:"InvasionID"`
} `json:"stage_invasion_config"`
}
var (
stageInvasionOnce sync.Once
stageInvasionMap = map[uint32]uint32{}
)
func getStageInvasionID(stageID uint32) uint32 {
stageInvasionOnce.Do(func() {
for _, path := range []string{"resources/StageInvasionConfig.json", "StageInvasionConfig.json"} {
raw, err := os.ReadFile(path)
if err != nil {
continue
}
var cfg stageInvasionConfigData
if err := json.Unmarshal(raw, &cfg); err == nil {
for _, entry := range cfg.StageInvasionConfig {
stageInvasionMap[entry.StageID] = entry.InvasionID
}
break
}
}
})
return stageInvasionMap[stageID]
}
func sortedLineup(m map[uint32]uint32) []uint32 {
keys := make([]uint32, 0, len(m))
for k := range m {