220 lines
7.4 KiB
Go
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
|
|
}
|