pre cw with probe and a bunch script to read game behavior

This commit is contained in:
CAtlas0784 committed 2026-10-21 20:40:13 +07:00
1 parent 5120219ae2
commit da8a32f60d
310 files changed
+219693 -872

No files matched your search

+100 -2
View File
@@ -3,6 +3,7 @@ package common
import (
"encoding/json"
"os"
"sort"
"sync"
"time"
@@ -22,9 +23,11 @@ type BattleSkillReport struct {
type BattleAvatarReport struct {
AvatarID uint32 `json:"avatar_id"`
Slot *uint32 `json:"slot,omitempty"`
AvatarLevel uint32 `json:"level"`
AvatarRank uint32 `json:"rank"`
TotalDamage float64 `json:"total_damage"`
MemospriteDamage float64 `json:"memosprite_damage"`
DamagePercent float64 `json:"damage_percent"`
TotalDamageTaken float64 `json:"total_damage_taken"`
TotalHeal float64 `json:"total_heal"`
@@ -43,6 +46,42 @@ type BattleAvatarReport struct {
SkillTimes []*BattleSkillReport `json:"skill_times"`
}
// The battle's lineup is authoritative; overworld lineup and native statistics
// list enumeration can have different orders.
func ApplyBattleReportContext(report *BattleReport, battle *goproto.SceneBattleInfo) {
if report == nil || battle == nil {
return
}
report.BattleID, report.StageID = battle.BattleId, battle.StageId
indices := make(map[uint32]uint32, len(battle.BattleAvatarList))
metadata := make(map[uint32]*goproto.BattleAvatar, len(battle.BattleAvatarList))
for _, a := range battle.BattleAvatarList {
if a != nil {
indices[a.Id] = a.Index
metadata[a.Id] = a
}
}
for _, a := range report.Avatars {
if a != nil {
if slot, ok := indices[a.AvatarID]; ok {
value := slot
a.Slot = &value
a.AvatarLevel, a.AvatarRank = metadata[a.AvatarID].Level, metadata[a.AvatarID].Rank
}
}
}
sort.SliceStable(report.Avatars, func(i, j int) bool {
a, b := report.Avatars[i], report.Avatars[j]
if a == nil || a.Slot == nil {
return false
}
if b == nil || b.Slot == nil {
return true
}
return *a.Slot < *b.Slot
})
}
type BattleMonsterReport struct {
EntityID uint32 `json:"entity_id"`
MonsterID uint32 `json:"monster_id"`
@@ -103,8 +142,30 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32,
damageByAvatar[event.AvatarId] += event.Damage
damageEventTotal += event.Damage
}
attackDamageProperties := make(map[uint32][]*goproto.AttackDamageProperty, len(stt.BattleAvatarList))
attackDamageByAvatar := make(map[uint32]float64, len(stt.BattleAvatarList))
for _, avatar := range stt.BattleAvatarList {
if avatar == nil {
continue
}
properties := avatarAttackDamageProperties(avatar)
attackDamageProperties[avatar.Id] = properties
attackDamageByAvatar[avatar.Id] = sumAttackDamageProperties(properties)
}
// Some Client reports leave AvatarBattleInfo totals empty and include a
// supplemental list whose nested schema explicitly names AvatarId and Damage.
// Use it only as a fallback so two report sources are never added together.
supplementalDamageByAvatar := make(map[uint32]float64)
if stt.MFOJKJGKDGD != nil {
for _, entry := range stt.MFOJKJGKDGD.INBNCAKGDPJ {
if entry == nil || entry.AvatarId == 0 || entry.Damage <= 0 {
continue
}
supplementalDamageByAvatar[entry.AvatarId] += entry.Damage
}
}
var teamDmg, teamHeal, teamShield float64
var teamDmg, primaryAvatarDamage, teamHeal, teamShield float64
for _, a := range stt.BattleAvatarList {
if a == nil {
continue
@@ -113,10 +174,20 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32,
if damage == 0 {
damage = damageByAvatar[a.Id]
}
if damage == 0 {
damage = attackDamageByAvatar[a.Id]
}
primaryAvatarDamage += damage
if damage == 0 {
damage = supplementalDamageByAvatar[a.Id]
}
teamDmg += damage
teamHeal += a.TotalHeal
teamShield += a.TotalShield
}
if primaryAvatarDamage == 0 && stt.MFOJKJGKDGD != nil && stt.MFOJKJGKDGD.TotalDamage > 0 {
teamDmg = stt.MFOJKJGKDGD.TotalDamage
}
// If neither avatar aggregate source contains damage, enemy damage-taken
// stats still provide a reliable team total. They cannot identify which
// avatar dealt each hit, so per-avatar shares remain unavailable in that case.
@@ -142,6 +213,12 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32,
if damage == 0 {
damage = damageByAvatar[a.Id]
}
if damage == 0 {
damage = attackDamageByAvatar[a.Id]
}
if damage == 0 {
damage = supplementalDamageByAvatar[a.Id]
}
var dmgPercent float64
if teamDmg > 0 {
dmgPercent = (damage / teamDmg) * 100.0
@@ -168,7 +245,7 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32,
AttackTypeMaxDamage: make(map[string]float64),
SkillTimes: make([]*BattleSkillReport, 0, len(a.SkillTimes)),
}
for _, prop := range a.AttackTypeDamage {
for _, prop := range attackDamageProperties[a.Id] {
if prop != nil && prop.AttackType != "" {
ar.AttackTypeDamage[prop.AttackType] += prop.Damage
}
@@ -217,9 +294,30 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32,
report.Monsters = append(report.Monsters, mr)
}
applyBattleServantStatistics(report, stt.GetIAGKLJEKAOL().GetGFOAHHHNBJC())
return report
}
// Native snapshots show NOGHNMCDDAI matches damage taken by attack type,
// including a support with zero damage dealt. It must not become a fallback
// for damage dealt merely because it shares the AttackDamageProperty schema.
func avatarAttackDamageProperties(avatar *goproto.AvatarBattleInfo) []*goproto.AttackDamageProperty {
if avatar == nil {
return nil
}
return avatar.AttackTypeDamage
}
func sumAttackDamageProperties(properties []*goproto.AttackDamageProperty) float64 {
var total float64
for _, property := range properties {
if property != nil && property.Damage > 0 {
total += property.Damage
}
}
return total
}
// SaveBattleReport writes the battle report to disk atomically.
func SaveBattleReport(report *BattleReport) error {
if report == nil {