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

+78 -29
View File
@@ -105,12 +105,56 @@ func (a *AvatarJson) ToAvatarProto(lightcone *Lightcone, mcID, marchID uint32) *
}
}
// EnhanceAvatarID contains avatars that get enhanced mode enabled by default (March 7th Hunt, SP forms, etc.)
var EnhanceAvatarID = map[uint32]bool{
1004: true, 1005: true, 1006: true, 1102: true, 1205: true,
1212: true, 1217: true, 1306: true, 1307: true, 1310: true,
}
// AvatarExRankMap maps AvatarID to its corresponding Ex-Eidolon (Affinitive Eidolon) ID
var AvatarExRankMap = map[uint32]uint32{
1204: 120401, // Jing Yuan
1213: 121301, // Dan Heng • IL
1308: 130801, // Acheron
}
// ExRankAvatarID matches pearl-sr 4.6.53 (Jing Yuan 120401, Dan Heng IL 121301, Acheron 130801)
var ExRankAvatarID = []uint32{120401, 121301, 130801}
// DefaultAvatarSkinMap defines default skins if not explicitly set
var DefaultAvatarSkinMap = map[uint32]uint32{
1303: 1130301,
1001: 1100101,
1310: 1131001,
1415: 1141501,
1407: 1140701,
1501: 1150101,
1505: 1150501,
1409: 1140901,
}
func (a *AvatarJson) ToAvatarPathDataProto(lightcone *Lightcone, relics []*Relic, dressedSkinID uint32) *goproto.AvatarPathData {
enhancedID := derefU32(a.EnhancedID)
if enhancedID == 0 && EnhanceAvatarID[a.AvatarID] {
enhancedID = 1
}
if dressedSkinID == 0 {
dressedSkinID = DefaultAvatarSkinMap[a.AvatarID]
}
var exRanks []uint32
if a.ExRankUnlocked {
if exID, ok := AvatarExRankMap[a.AvatarID]; ok {
exRanks = []uint32{exID}
}
}
out := &goproto.AvatarPathData{
AvatarId: a.AvatarID,
Rank: a.Data.Rank,
EnhancedId: derefU32(a.EnhancedID),
EnhancedId: enhancedID,
DressedSkinId: dressedSkinID,
IGDEHGIKFJA: exRanks,
}
for _, r := range relics {
@@ -137,16 +181,29 @@ func (a *AvatarJson) ToAvatarPathDataProto(lightcone *Lightcone, relics []*Relic
}
func (a *AvatarJson) ToBattleAvatarProto(index uint32, lightcone *Lightcone, relics []*Relic) (*goproto.BattleAvatar, []*goproto.BattleBuff) {
enhancedID := derefU32(a.EnhancedID)
if enhancedID == 0 && EnhanceAvatarID[a.AvatarID] {
enhancedID = 1
}
var exRanks []uint32
if a.ExRankUnlocked {
if exID, ok := AvatarExRankMap[a.AvatarID]; ok {
exRanks = []uint32{exID}
}
}
ba := &goproto.BattleAvatar{
Index: index,
AvatarType: goproto.AvatarType_AVATAR_FORMAL_TYPE,
Id: a.AvatarID,
Level: a.Level,
Rank: a.Data.Rank,
Hp: 10000,
Promotion: a.Promotion,
WorldLevel: 6,
EnhancedId: derefU32(a.EnhancedID),
Index: index,
AvatarType: goproto.AvatarType_AVATAR_FORMAL_TYPE,
Id: a.AvatarID,
Level: a.Level,
Rank: a.Data.Rank,
Hp: 10000,
Promotion: a.Promotion,
WorldLevel: 6,
EnhancedId: enhancedID,
IGDEHGIKFJA: exRanks,
SpBar: &goproto.SpBarInfo{
CurSp: a.SPCur(),
MaxSp: a.SPMaxVal(),
@@ -252,10 +309,14 @@ func ToLineupInfo(lineups map[uint32]uint32) *goproto.LineupInfo {
// ---------------------------------------------------------------------------
func (m *Monster) ToSceneMonster() *goproto.SceneMonster {
hp := m.MaxHP
if hp == 0 {
hp = 10000
}
return &goproto.SceneMonster{
MonsterId: m.MonsterID,
MaxHp: m.MaxHP,
CurHp: m.MaxHP,
MaxHp: hp,
CurHp: hp,
}
}
@@ -263,27 +324,15 @@ func monsterWave(waveID uint32, monsters []Monster) *goproto.SceneMonsterWave {
wave := &goproto.SceneMonsterWave{
WaveId: waveID,
MonsterList: make([]*goproto.SceneMonster, 0, len(monsters)),
WaveParam: &goproto.SceneMonsterWaveParam{},
// NOTE: WaveParam MUST be nil! In client 4.6.53 GameAssembly.dll
// AdventureModule.CreateBattleServerCheckResult (RVA 0x166b2ef6),
// if WaveParam is non-nil it tries to dereference [wave_param + 0x10]
// and throws NullReferenceException. When nil, it safely jumps over.
WaveParam: nil,
}
maxLevel := uint32(95)
for i := range monsters {
if monsters[i].Level > maxLevel || wave.WaveParam.Level == 0 {
if monsters[i].Level > maxLevel {
maxLevel = monsters[i].Level
}
}
wave.MonsterList = append(wave.MonsterList, monsters[i].ToSceneMonster())
}
// Rust: monsters.iter().map(level).max().unwrap_or(95)
if len(monsters) > 0 {
maxLevel = 0
for i := range monsters {
if monsters[i].Level > maxLevel {
maxLevel = monsters[i].Level
}
}
}
wave.WaveParam.Level = maxLevel
return wave
}