feat(currency_wars): complete systems, settlement Rank SS, damage report, camera and recovery fixes

This commit is contained in:
CAtlas0784 committed 2026-10-31 20:47:36 +07:00
1 parent c008243f11
commit 0995055296
78 files changed
+33840 -9525

No files matched your search

+160 -15
View File
@@ -53,6 +53,11 @@ func ApplyBattleReportContext(report *BattleReport, battle *goproto.SceneBattleI
return
}
report.BattleID, report.StageID = battle.BattleId, battle.StageId
if battle.EIGMKOJOEEI != nil {
report.IsCurrencyWars = true
applyCurrencyWarsBattleReportContext(report, battle)
return
}
indices := make(map[uint32]uint32, len(battle.BattleAvatarList))
metadata := make(map[uint32]*goproto.BattleAvatar, len(battle.BattleAvatarList))
for _, a := range battle.BattleAvatarList {
@@ -82,6 +87,108 @@ func ApplyBattleReportContext(report *BattleReport, battle *goproto.SceneBattleI
})
}
// In Currency Wars (GridFight), deployed avatars can exceed 4 (front + back rows).
// All active board roles are registered in grid.GridGameRoleList and their damages
// are tracked via stt.APAGMJNECCN.DOGMMGLBJIJ.
func applyCurrencyWarsBattleReportContext(report *BattleReport, battle *goproto.SceneBattleInfo) {
grid := battle.EIGMKOJOEEI
if grid == nil {
return
}
metadata := make(map[uint32]*goproto.BattleAvatar)
for _, a := range battle.BattleAvatarList {
if a != nil {
metadata[a.Id] = a
}
}
for _, a := range grid.OPHONCKPNMG {
if a != nil {
metadata[a.Id] = a
}
}
existingByAvatar := make(map[uint32]*BattleAvatarReport, len(report.Avatars))
for _, a := range report.Avatars {
if a != nil {
existingByAvatar[a.AvatarID] = a
}
}
if len(grid.GridGameRoleList) > 0 {
var cwTotalDamage float64
for _, role := range grid.GridGameRoleList {
if role == nil {
continue
}
var dmg float64
if report.CurrencyWarsRoleDamages != nil {
dmg = report.CurrencyWarsRoleDamages[role.RoleId]
} else if ex, ok := existingByAvatar[role.AvatarId]; ok {
dmg = ex.TotalDamage
}
cwTotalDamage += dmg
}
if cwTotalDamage > 0 {
report.TeamTotalDamage = cwTotalDamage
}
newAvatars := make([]*BattleAvatarReport, 0, len(grid.GridGameRoleList))
for _, role := range grid.GridGameRoleList {
if role == nil {
continue
}
avatarID := role.AvatarId
if avatarID == 0 && role.RoleId > 100 {
avatarID = role.RoleId / 100
}
ar := existingByAvatar[avatarID]
if ar == nil {
ar = &BattleAvatarReport{
AvatarID: avatarID,
AttackTypeDamage: make(map[string]float64),
AttackTypeBreakDamage: make(map[string]float64),
AttackTypeMaxDamage: make(map[string]float64),
}
}
slot := role.Pos
ar.Slot = &slot
ar.AvatarRank = role.RoleStar
if meta := metadata[avatarID]; meta != nil {
ar.AvatarLevel = meta.Level
if ar.AvatarRank == 0 && meta.Rank > 0 {
ar.AvatarRank = meta.Rank
}
}
if ar.AvatarLevel == 0 {
ar.AvatarLevel = 80
}
if report.CurrencyWarsRoleDamages != nil {
if dmg, ok := report.CurrencyWarsRoleDamages[role.RoleId]; ok {
ar.TotalDamage = dmg
}
}
if report.TeamTotalDamage > 0 {
ar.DamagePercent = (ar.TotalDamage / report.TeamTotalDamage) * 100.0
} else {
ar.DamagePercent = 0
}
newAvatars = append(newAvatars, ar)
}
sort.SliceStable(newAvatars, func(i, j int) bool {
a, b := newAvatars[i], newAvatars[j]
if a == nil || a.Slot == nil {
return false
}
if b == nil || b.Slot == nil {
return true
}
return *a.Slot < *b.Slot
})
report.Avatars = newAvatars
}
}
type BattleMonsterReport struct {
EntityID uint32 `json:"entity_id"`
MonsterID uint32 `json:"monster_id"`
@@ -93,20 +200,22 @@ type BattleMonsterReport struct {
}
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"`
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"`
IsCurrencyWars bool `json:"is_currency_wars,omitempty"`
CurrencyWarsRoleDamages map[uint32]float64 `json:"currency_wars_role_damages,omitempty"`
}
// BattleReportFromStatistics converts a protobuf BattleStatistics packet from the game client
@@ -129,6 +238,24 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32,
Monsters: make([]*BattleMonsterReport, 0, len(stt.MonsterList)),
}
// In Currency Wars (GridFight), per-role combat damage is explicitly recorded in
// stt.APAGMJNECCN.DOGMMGLBJIJ (*pb.CPNCAPLOIHI).
var cwRoleDamages map[uint32]float64
var cwTotalRoleDamage float64
if stt.APAGMJNECCN != nil && len(stt.APAGMJNECCN.DOGMMGLBJIJ) > 0 {
cwRoleDamages = make(map[uint32]float64, len(stt.APAGMJNECCN.DOGMMGLBJIJ))
for _, item := range stt.APAGMJNECCN.DOGMMGLBJIJ {
if item != nil && item.RoleId != 0 {
cwRoleDamages[item.RoleId] = item.Damage
if item.Damage > 0 {
cwTotalRoleDamage += item.Damage
}
}
}
report.CurrencyWarsRoleDamages = cwRoleDamages
report.IsCurrencyWars = true
}
// The 4.6.53 schema exposes OKFALMGFHMA entries with avatar_id and damage,
// but the enclosing message and several fields remain obfuscated. Treating
// these as per-avatar damage records is an inference that still needs a raw
@@ -177,6 +304,14 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32,
if damage == 0 {
damage = attackDamageByAvatar[a.Id]
}
if damage == 0 && cwRoleDamages != nil {
for rID, rDmg := range cwRoleDamages {
if rID == a.Id || rID/100 == a.Id {
damage = rDmg
break
}
}
}
primaryAvatarDamage += damage
if damage == 0 {
damage = supplementalDamageByAvatar[a.Id]
@@ -185,7 +320,9 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32,
teamHeal += a.TotalHeal
teamShield += a.TotalShield
}
if primaryAvatarDamage == 0 && stt.MFOJKJGKDGD != nil && stt.MFOJKJGKDGD.TotalDamage > 0 {
if cwTotalRoleDamage > 0 {
teamDmg = cwTotalRoleDamage
} else if primaryAvatarDamage == 0 && stt.MFOJKJGKDGD != nil && stt.MFOJKJGKDGD.TotalDamage > 0 {
teamDmg = stt.MFOJKJGKDGD.TotalDamage
}
// If neither avatar aggregate source contains damage, enemy damage-taken
@@ -216,6 +353,14 @@ func BattleReportFromStatistics(stt *goproto.BattleStatistics, battleID uint32,
if damage == 0 {
damage = attackDamageByAvatar[a.Id]
}
if damage == 0 && cwRoleDamages != nil {
for rID, rDmg := range cwRoleDamages {
if rID == a.Id || rID/100 == a.Id {
damage = rDmg
break
}
}
}
if damage == 0 {
damage = supplementalDamageByAvatar[a.Id]
}