feat: capture in-game battle damage statistics & add battle_report API endpoints
This commit is contained in:
1 parent
5c4d0d4149
commit
365e66ffe0
5 files changed
+415
No files matched your search
@@ -0,0 +1,219 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
goproto "gac-hkrpg-go/protocol/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBattleReportSerialization(t *testing.T) {
|
||||||
|
defer os.Remove(BattleReportFile)
|
||||||
|
|
||||||
|
stt := &goproto.BattleStatistics{
|
||||||
|
TotalBattleTurns: 12,
|
||||||
|
TotalAutoTurns: 0,
|
||||||
|
RoundCnt: 2,
|
||||||
|
CostTime: 45.5,
|
||||||
|
ChallengeScore: 1000,
|
||||||
|
BattleAvatarList: []*goproto.AvatarBattleInfo{
|
||||||
|
{
|
||||||
|
Id: 1310,
|
||||||
|
AvatarLevel: 80,
|
||||||
|
AvatarRank: 2,
|
||||||
|
TotalDamage: 500000,
|
||||||
|
TotalDamageTaken: 2500,
|
||||||
|
TotalHeal: 0,
|
||||||
|
TotalBreakDamage: 400000,
|
||||||
|
TotalTurns: 6,
|
||||||
|
AttackTypeDamage: []*goproto.AttackDamageProperty{
|
||||||
|
{AttackType: "Normal", Damage: 20000},
|
||||||
|
{AttackType: "BPSkill", Damage: 80000},
|
||||||
|
{AttackType: "BreakDamage", Damage: 400000},
|
||||||
|
},
|
||||||
|
AttackTypeMaxDamage: []*goproto.AttackDamageProperty{
|
||||||
|
{AttackType: "BreakDamage", Damage: 250000},
|
||||||
|
},
|
||||||
|
SkillTimes: []*goproto.SkillUseProperty{
|
||||||
|
{SkillId: 131001, SkillType: "Normal", SkillUseCount: 1},
|
||||||
|
{SkillId: 131002, SkillType: "BPSkill", SkillUseCount: 4},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: 1001,
|
||||||
|
AvatarLevel: 80,
|
||||||
|
AvatarRank: 6,
|
||||||
|
TotalDamage: 50000,
|
||||||
|
TotalDamageTaken: 1200,
|
||||||
|
TotalShield: 4500,
|
||||||
|
TotalTurns: 6,
|
||||||
|
AttackTypeDamage: []*goproto.AttackDamageProperty{
|
||||||
|
{AttackType: "Normal", Damage: 50000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MonsterList: []*goproto.MonsterBattleInfo{
|
||||||
|
{
|
||||||
|
EntityId: 101,
|
||||||
|
MonsterId: 3024010,
|
||||||
|
TotalTurns: 3,
|
||||||
|
TotalDamageTaken: 550000,
|
||||||
|
TotalDamage: 3700,
|
||||||
|
Wave: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
report := BattleReportFromStatistics(stt, 999, 1001, "WIN")
|
||||||
|
if report == nil {
|
||||||
|
t.Fatal("expected report, got nil")
|
||||||
|
}
|
||||||
|
if report.TeamTotalDamage != 550000 {
|
||||||
|
t.Fatalf("expected team total damage 550000, got %f", report.TeamTotalDamage)
|
||||||
|
}
|
||||||
|
if len(report.Avatars) != 2 {
|
||||||
|
t.Fatalf("expected 2 avatars, got %d", len(report.Avatars))
|
||||||
|
}
|
||||||
|
if report.Avatars[0].DamagePercent < 90 || report.Avatars[0].DamagePercent > 91 {
|
||||||
|
t.Fatalf("unexpected damage percent %f", report.Avatars[0].DamagePercent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := SaveBattleReport(report); err != nil {
|
||||||
|
t.Fatalf("failed to save report: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded, err := LoadLatestBattleReport()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load report: %v", err)
|
||||||
|
}
|
||||||
|
if loaded.BattleID != 999 {
|
||||||
|
t.Fatalf("expected battle id 999, got %d", loaded.BattleID)
|
||||||
|
}
|
||||||
|
if loaded.TeamTotalDamage != 550000 {
|
||||||
|
t.Fatalf("expected team damage 550000, got %f", loaded.TeamTotalDamage)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ClearBattleReport(); err != nil {
|
||||||
|
t.Fatalf("failed to clear report: %v", err)
|
||||||
|
}
|
||||||
|
_, err = LoadLatestBattleReport()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error after clearing report, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,6 +113,23 @@ func init() {
|
|||||||
rsp.EndStatus = req.EndStatus
|
rsp.EndStatus = req.EndStatus
|
||||||
rsp.BattleId = req.BattleId
|
rsp.BattleId = req.BattleId
|
||||||
rsp.StageId = req.StageId
|
rsp.StageId = req.StageId
|
||||||
|
|
||||||
|
if req.Stt != nil {
|
||||||
|
statusStr := "WIN"
|
||||||
|
switch req.EndStatus {
|
||||||
|
case goproto.BattleEndStatus_BATTLE_END_LOSE:
|
||||||
|
statusStr = "LOSE"
|
||||||
|
case goproto.BattleEndStatus_BATTLE_END_QUIT:
|
||||||
|
statusStr = "QUIT"
|
||||||
|
}
|
||||||
|
report := common.BattleReportFromStatistics(req.Stt, req.BattleId, req.StageId, statusStr)
|
||||||
|
if err := common.SaveBattleReport(report); err != nil {
|
||||||
|
Log(fmt.Sprintf("[DAMAGE_ANALYST] Failed to save battle report: %v", err))
|
||||||
|
} else {
|
||||||
|
Log(fmt.Sprintf("[DAMAGE_ANALYST] Battle report captured: BattleID=%d Dmg=%.1f Cycles=%d Turns=%d Status=%s",
|
||||||
|
req.BattleId, report.TeamTotalDamage, report.RoundCnt, report.TotalBattleTurns, statusStr))
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package gameserver
|
package gameserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gac-hkrpg-go/internal/common"
|
||||||
goproto "gac-hkrpg-go/protocol/proto"
|
goproto "gac-hkrpg-go/protocol/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +36,15 @@ func init() {
|
|||||||
|
|
||||||
handle("QuitBattle", func(s *Session, req *goproto.QuitBattleCsReq, rsp *goproto.QuitBattleScRsp) error {
|
handle("QuitBattle", func(s *Session, req *goproto.QuitBattleCsReq, rsp *goproto.QuitBattleScRsp) error {
|
||||||
rsp.Retcode = 0
|
rsp.Retcode = 0
|
||||||
|
if req.Stt != nil {
|
||||||
|
report := common.BattleReportFromStatistics(req.Stt, 0, 0, "QUIT")
|
||||||
|
if err := common.SaveBattleReport(report); err != nil {
|
||||||
|
Log(fmt.Sprintf("[DAMAGE_ANALYST] Failed to save quit battle report: %v", err))
|
||||||
|
} else {
|
||||||
|
Log(fmt.Sprintf("[DAMAGE_ANALYST] Quit battle report captured: Dmg=%.1f Cycles=%d Turns=%d",
|
||||||
|
report.TeamTotalDamage, report.RoundCnt, report.TotalBattleTurns))
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ func registerAPIRoutes(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("/api/gachabase/fetch", corsWrapper(apiGachabaseFetch))
|
mux.HandleFunc("/api/gachabase/fetch", corsWrapper(apiGachabaseFetch))
|
||||||
mux.HandleFunc("/api/command", corsWrapper(apiCommand))
|
mux.HandleFunc("/api/command", corsWrapper(apiCommand))
|
||||||
mux.HandleFunc("/api/data-in-game", corsWrapper(apiDataInGame))
|
mux.HandleFunc("/api/data-in-game", corsWrapper(apiDataInGame))
|
||||||
|
mux.HandleFunc("/api/battle_report", corsWrapper(apiBattleReport))
|
||||||
|
mux.HandleFunc("/api/battle_report/reset", corsWrapper(apiBattleReportReset))
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiRoot(w http.ResponseWriter, r *http.Request) {
|
func apiRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -76,6 +78,7 @@ func apiRoot(w http.ResponseWriter, r *http.Request) {
|
|||||||
"avatar_max_all": "POST /api/avatar/max_all",
|
"avatar_max_all": "POST /api/avatar/max_all",
|
||||||
"teleport": "POST /api/teleport",
|
"teleport": "POST /api/teleport",
|
||||||
"command": "POST /api/command",
|
"command": "POST /api/command",
|
||||||
|
"battle_report": "GET, POST /api/battle_report",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -771,3 +774,64 @@ func apiCommand(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apiBattleReport handles GET (fetch latest in-game battle) and POST (import battle report)
|
||||||
|
func apiBattleReport(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
report, err := common.LoadLatestBattleReport()
|
||||||
|
if err != nil {
|
||||||
|
writeJSON(w, map[string]any{
|
||||||
|
"status": 200,
|
||||||
|
"has_report": false,
|
||||||
|
"message": "No battle report recorded yet",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, map[string]any{
|
||||||
|
"status": 200,
|
||||||
|
"has_report": true,
|
||||||
|
"report": report,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
var report common.BattleReport
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&report); err != nil {
|
||||||
|
writeJSON(w, map[string]any{"error": "invalid json: " + err.Error(), "status": 400})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if report.Timestamp == 0 {
|
||||||
|
report.Timestamp = time.Now().Unix()
|
||||||
|
}
|
||||||
|
if err := common.SaveBattleReport(&report); err != nil {
|
||||||
|
writeJSON(w, map[string]any{"error": err.Error(), "status": 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, map[string]any{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Battle report saved successfully",
|
||||||
|
"report": report,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiBattleReportReset clears the current recorded battle report
|
||||||
|
func apiBattleReportReset(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost && r.Method != http.MethodGet {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := common.ClearBattleReport(); err != nil {
|
||||||
|
writeJSON(w, map[string]any{"error": err.Error(), "status": 500})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, map[string]any{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Battle report cleared",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Reference in new issue
Block a user