Files
GaCy-GO/internal/common/battle_report.go
T

220 lines
7.4 KiB
Go

package common
import (
"encoding/json"
"os"
"sync"
"time"
goproto "gac-hkrpg-go/protocol/proto"
)
var battleReportMu sync.RWMutex
const BattleReportFile = "battle_report.json"
type BattleSkillReport struct {
SkillID uint32 `json:"skill_id"`
SkillType string `json:"skill_type"`
SkillLevel uint32 `json:"skill_level"`
SkillUseCount uint32 `json:"skill_use_count"`
}
type BattleAvatarReport struct {
AvatarID uint32 `json:"avatar_id"`
AvatarLevel uint32 `json:"level"`
AvatarRank uint32 `json:"rank"`
TotalDamage float64 `json:"total_damage"`
DamagePercent float64 `json:"damage_percent"`
TotalDamageTaken float64 `json:"total_damage_taken"`
TotalHeal float64 `json:"total_heal"`
TotalHpRecover float64 `json:"total_hp_recover"`
TotalShield float64 `json:"total_shield"`
TotalShieldTaken float64 `json:"total_shield_taken"`
TotalBreakDamage float64 `json:"total_break_damage"`
TotalTurns uint32 `json:"total_turns"`
ExtraTurns uint32 `json:"extra_turns"`
DieTimes uint32 `json:"die_times"`
ReviveTimes uint32 `json:"revive_times"`
BreakTimes uint32 `json:"break_times"`
AttackTypeDamage map[string]float64 `json:"attack_type_damage"`
AttackTypeBreakDamage map[string]float64 `json:"attack_type_break_damage"`
AttackTypeMaxDamage map[string]float64 `json:"attack_type_max_damage"`
SkillTimes []*BattleSkillReport `json:"skill_times"`
}
type BattleMonsterReport struct {
EntityID uint32 `json:"entity_id"`
MonsterID uint32 `json:"monster_id"`
TotalTurns uint32 `json:"total_turns"`
TotalDamage float64 `json:"total_damage"`
TotalDamageTaken float64 `json:"total_damage_taken"`
Wave uint32 `json:"wave"`
AttackTypeDamage map[string]float64 `json:"attack_type_damage"`
}
type BattleReport struct {
Timestamp int64 `json:"timestamp"`
BattleID uint32 `json:"battle_id"`
StageID uint32 `json:"stage_id"`
EndStatus string `json:"end_status"` // "WIN", "FAIL", "QUIT"
TotalBattleTurns uint32 `json:"total_battle_turns"`
TotalAutoTurns uint32 `json:"total_auto_turns"`
RoundCnt uint32 `json:"round_cnt"`
CostTime float64 `json:"cost_time"`
ChallengeScore uint32 `json:"challenge_score"`
TeamTotalDamage float64 `json:"team_total_damage"`
TeamTotalHeal float64 `json:"team_total_heal"`
TeamTotalShield float64 `json:"team_total_shield"`
Avatars []*BattleAvatarReport `json:"avatars"`
Monsters []*BattleMonsterReport `json:"monsters"`
}
// BattleReportFromStatistics converts a protobuf BattleStatistics packet from the game client
// into a standardized BattleReport.
func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32, stageID uint32, endStatus string) *BattleReport {
if stt == nil {
return nil
}
report := &BattleReport{
Timestamp: time.Now().Unix(),
BattleID: battleID,
StageID: stageID,
EndStatus: endStatus,
TotalBattleTurns: stt.TotalBattleTurns,
TotalAutoTurns: stt.TotalAutoTurns,
RoundCnt: stt.RoundCnt,
CostTime: stt.CostTime,
ChallengeScore: stt.ChallengeScore,
Avatars: make([]*BattleAvatarReport, 0, len(stt.BattleAvatarList)),
Monsters: make([]*BattleMonsterReport, 0, len(stt.MonsterList)),
}
var teamDmg, teamHeal, teamShield float64
for _, a := range stt.BattleAvatarList {
if a == nil {
continue
}
teamDmg += a.TotalDamage
teamHeal += a.TotalHeal
teamShield += a.TotalShield
}
report.TeamTotalDamage = teamDmg
report.TeamTotalHeal = teamHeal
report.TeamTotalShield = teamShield
for _, a := range stt.BattleAvatarList {
if a == nil {
continue
}
var dmgPercent float64
if teamDmg > 0 {
dmgPercent = (a.TotalDamage / teamDmg) * 100.0
}
ar := &BattleAvatarReport{
AvatarID: a.Id,
AvatarLevel: a.AvatarLevel,
AvatarRank: a.AvatarRank,
TotalDamage: a.TotalDamage,
DamagePercent: dmgPercent,
TotalDamageTaken: a.TotalDamageTaken,
TotalHeal: a.TotalHeal,
TotalHpRecover: a.TotalHpRecover,
TotalShield: a.TotalShield,
TotalShieldTaken: a.TotalShieldTaken,
TotalBreakDamage: a.TotalBreakDamage,
TotalTurns: a.TotalTurns,
ExtraTurns: a.ExtraTurns,
DieTimes: a.DieTimes,
ReviveTimes: a.ReviveTimes,
BreakTimes: a.BreakTimes,
AttackTypeDamage: make(map[string]float64),
AttackTypeBreakDamage: make(map[string]float64),
AttackTypeMaxDamage: make(map[string]float64),
SkillTimes: make([]*BattleSkillReport, 0, len(a.SkillTimes)),
}
for _, prop := range a.AttackTypeDamage {
if prop != nil && prop.AttackType != "" {
ar.AttackTypeDamage[prop.AttackType] += prop.Damage
}
}
for _, prop := range a.AttackTypeBreakDamage {
if prop != nil && prop.AttackType != "" {
ar.AttackTypeBreakDamage[prop.AttackType] += prop.Damage
}
}
for _, prop := range a.AttackTypeMaxDamage {
if prop != nil && prop.AttackType != "" {
ar.AttackTypeMaxDamage[prop.AttackType] = prop.Damage
}
}
for _, sk := range a.SkillTimes {
if sk != nil {
ar.SkillTimes = append(ar.SkillTimes, &BattleSkillReport{
SkillID: sk.SkillId,
SkillType: sk.SkillType,
SkillLevel: sk.SkillLevel,
SkillUseCount: sk.SkillUseCount,
})
}
}
report.Avatars = append(report.Avatars, ar)
}
for _, m := range stt.MonsterList {
if m == nil {
continue
}
mr := &BattleMonsterReport{
EntityID: m.EntityId,
MonsterID: m.MonsterId,
TotalTurns: m.TotalTurns,
TotalDamage: m.TotalDamage,
TotalDamageTaken: m.TotalDamageTaken,
Wave: m.Wave,
AttackTypeDamage: make(map[string]float64),
}
for _, prop := range m.AttackTypeDamage {
if prop != nil && prop.AttackType != "" {
mr.AttackTypeDamage[prop.AttackType] += prop.Damage
}
}
report.Monsters = append(report.Monsters, mr)
}
return report
}
// SaveBattleReport writes the battle report to disk atomically.
func SaveBattleReport(report *BattleReport) error {
if report == nil {
return nil
}
battleReportMu.Lock()
defer battleReportMu.Unlock()
return writeJSONAtomic(BattleReportFile, report)
}
// LoadLatestBattleReport loads the most recent battle report from disk.
func LoadLatestBattleReport() (*BattleReport, error) {
battleReportMu.RLock()
defer battleReportMu.RUnlock()
raw, err := os.ReadFile(BattleReportFile)
if err != nil {
return nil, err
}
var report BattleReport
if err := json.Unmarshal(raw, &report); err != nil {
return nil, err
}
return &report, nil
}
// ClearBattleReport deletes the current battle report file.
func ClearBattleReport() error {
battleReportMu.Lock()
defer battleReportMu.Unlock()
_ = os.Remove(BattleReportFile)
return nil
}