pre cw with probe and a bunch script to read game behavior
This commit is contained in:
1 parent
5120219ae2
commit
da8a32f60d
310 files changed
+219693
-872
No files matched your search
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user