From f433a26a6d65faa1413175ede2641e19fddd9b2f Mon Sep 17 00:00:00 2001 From: xinian Date: Mon, 6 Apr 2026 00:58:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E6=88=98?= =?UTF-8?q?=E6=96=97=E7=B3=BB=E7=BB=9F=E4=B8=BA=E7=BB=9F=E4=B8=80=E5=8A=A8?= =?UTF-8?q?=E4=BD=9C=E5=8C=85=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- help/report.md | 99 ---------------- logic/controller/fight_base.go | 38 +------ logic/controller/fight_unified.go | 79 +++++++++++++ logic/service/fight/boss/NewSeIdx_501.go | 21 ++-- logic/service/fight/boss/NewSeIdx_502.go | 26 +++-- logic/service/fight/boss/NewSeIdx_503.go | 27 ++++- logic/service/fight/cmd_unified.go | 138 +++++++++++++++++++++++ logic/service/fight/info/unified_info.go | 102 +++++++++++++++++ logic/service/fight/input/team.go | 55 +++++++++ logic/service/fight/input/team_test.go | 30 +++++ logic/service/fight/unified_state.go | 113 +++++++++++++++++++ logic/service/fight/unified_test.go | 113 +++++++++++++++++++ 12 files changed, 683 insertions(+), 158 deletions(-) delete mode 100644 help/report.md create mode 100644 logic/controller/fight_unified.go create mode 100644 logic/service/fight/cmd_unified.go create mode 100644 logic/service/fight/info/unified_info.go create mode 100644 logic/service/fight/input/team.go create mode 100644 logic/service/fight/input/team_test.go create mode 100644 logic/service/fight/unified_state.go create mode 100644 logic/service/fight/unified_test.go diff --git a/help/report.md b/help/report.md deleted file mode 100644 index b6dd5a49a..000000000 --- a/help/report.md +++ /dev/null @@ -1,99 +0,0 @@ -# 屎山代码分析报告 - -## 总体评估 - -- **质量评分**: 31.03/100 -- **质量等级**: 🌸 偶有异味 - 基本没事,但是有伤风化 -- **分析文件数**: 203 -- **代码总行数**: 20972 - -## 质量指标 - -| 指标 | 得分 | 权重 | 状态 | -|------|------|------|------| -| 状态管理 | 4.84 | 0.15 | ✓✓ | -| 循环复杂度 | 6.28 | 0.25 | ✓✓ | -| 命名规范 | 25.00 | 0.10 | ✓ | -| 错误处理 | 35.00 | 0.15 | ○ | -| 代码结构 | 45.00 | 0.20 | ○ | -| 代码重复度 | 55.00 | 0.15 | • | -| 注释覆盖率 | 55.94 | 0.15 | • | - -## 问题文件 (Top 5) - -### 1. /workspace/blazing/common/utils/sturc/field.go (得分: 53.85) -**问题分类**: 🔄 复杂度问题:10, 📝 注释问题:1, ⚠️ 其他问题:5 - -**主要问题**: -- 函数 Size 的循环复杂度较高 (12),建议简化 -- 函数 packVal 的循环复杂度过高 (23),考虑重构 -- 函数 Pack 的循环复杂度较高 (14),建议简化 -- 函数 unpackVal 的循环复杂度过高 (21),考虑重构 -- 函数 Unpack 的循环复杂度较高 (12),建议简化 -- 函数 'Size' () 较长 (33 行),可考虑重构 -- 函数 'Size' () 复杂度过高 (12),建议简化 -- 函数 'packVal' () 过长 (69 行),建议拆分 -- 函数 'packVal' () 复杂度严重过高 (23),必须简化 -- 函数 'Pack' () 较长 (48 行),可考虑重构 -- 函数 'Pack' () 复杂度过高 (14),建议简化 -- 函数 'unpackVal' () 过长 (57 行),建议拆分 -- 函数 'unpackVal' () 复杂度严重过高 (21),必须简化 -- 函数 'Unpack' () 较长 (33 行),可考虑重构 -- 函数 'Unpack' () 复杂度过高 (12),建议简化 -- 代码注释率极低 (1.38%),几乎没有注释 - -### 2. /workspace/blazing/common/utils/sturc/fields.go (得分: 46.83) -**问题分类**: 🔄 复杂度问题:4, 📝 注释问题:1, ⚠️ 其他问题:2 - -**主要问题**: -- 函数 Pack 的循环复杂度较高 (12),建议简化 -- 函数 Unpack 的循环复杂度过高 (21),考虑重构 -- 函数 'Pack' () 较长 (42 行),可考虑重构 -- 函数 'Pack' () 复杂度过高 (12),建议简化 -- 函数 'Unpack' () 过长 (73 行),建议拆分 -- 函数 'Unpack' () 复杂度严重过高 (21),必须简化 -- 代码注释率极低 (3.91%),几乎没有注释 - -### 3. /workspace/blazing/common/utils/sturc/parse.go (得分: 46.68) -**问题分类**: 🔄 复杂度问题:4, 📝 注释问题:1, ⚠️ 其他问题:3 - -**主要问题**: -- 代码注释率较低 (6.93%),建议增加注释 -- 函数 parseField 的循环复杂度较高 (13),建议简化 -- 函数 parseFieldsLocked 的循环复杂度过高 (18),考虑重构 -- 函数 'parseField' () 过长 (64 行),建议拆分 -- 函数 'parseField' () 复杂度过高 (13),建议简化 -- 函数 'parseFieldsLocked' () 过长 (64 行),建议拆分 -- 函数 'parseFieldsLocked' () 复杂度严重过高 (18),必须简化 -- 函数 'parseFields' () 较长 (31 行),可考虑重构 - -### 4. /workspace/blazing/common/utils/xml/typeinfo.go (得分: 46.13) -**问题分类**: 🔄 复杂度问题:6, ⚠️ 其他问题:3 - -**主要问题**: -- 函数 getTypeInfo 的循环复杂度过高 (18),考虑重构 -- 函数 structFieldInfo 的循环复杂度过高 (33),考虑重构 -- 函数 addFieldInfo 的循环复杂度过高 (20),考虑重构 -- 函数 'getTypeInfo' () 过长 (58 行),建议拆分 -- 函数 'getTypeInfo' () 复杂度严重过高 (18),必须简化 -- 函数 'structFieldInfo' () 极度过长 (114 行),必须拆分 -- 函数 'structFieldInfo' () 复杂度严重过高 (33),必须简化 -- 函数 'addFieldInfo' () 过长 (66 行),建议拆分 -- 函数 'addFieldInfo' () 复杂度严重过高 (20),必须简化 - -### 5. /workspace/blazing/common/utils/go-jsonrpc/auth/handler.go (得分: 45.61) -**问题分类**: 📝 注释问题:1, ⚠️ 其他问题:1 - -**主要问题**: -- 函数 'ServeHTTP' () 较长 (31 行),可考虑重构 -- 代码注释率极低 (0.00%),几乎没有注释 - -## 改进建议 - -### 高优先级 -- 继续保持当前的代码质量标准 - -### 中优先级 -- 可以考虑进一步优化性能和可读性 -- 完善文档和注释,便于团队协作 - diff --git a/logic/controller/fight_base.go b/logic/controller/fight_base.go index 30ef8bd7b..b9278bbaf 100644 --- a/logic/controller/fight_base.go +++ b/logic/controller/fight_base.go @@ -2,7 +2,6 @@ package controller import ( "blazing/common/socket/errorcode" - "blazing/modules/player/model" "blazing/logic/service/fight" "blazing/logic/service/fight/info" @@ -31,7 +30,7 @@ func (h Controller) UseSkill(data *UseSkillInInfo, c *player.Player) (result *fi if err := h.checkFightStatus(c); err != 0 { return nil, err } - go c.FightC.UseSkill(c, data.SkillId) + h.dispatchFightActionEnvelope(c, buildLegacyUseSkillEnvelope(data)) return nil, 0 } @@ -41,31 +40,7 @@ func (h Controller) UseSkillAt(data *UseSkillAtInboundInfo, c *player.Player) (r if err := h.checkFightStatus(c); err != 0 { return nil, err } - actorIndex := int(data.ActorIndex) - targetIndex := int(data.TargetIndex) - targetRelation := data.TargetRelation - - // 前端未显式给 relation 时,按 AtkType 兜底:3=自己,1=己方,其他按对方。 - if targetRelation > fight.SkillTargetAlly { - switch data.AtkType { - case 3: - targetRelation = fight.SkillTargetSelf - case 1: - targetRelation = fight.SkillTargetAlly - default: - targetRelation = fight.SkillTargetOpponent - } - } - - switch targetRelation { - case fight.SkillTargetSelf: - targetIndex = actorIndex - go c.FightC.UseSkillAt(c, data.SkillId, actorIndex, fight.EncodeTargetIndex(targetIndex, false)) - case fight.SkillTargetAlly: - go c.FightC.UseSkillAt(c, data.SkillId, actorIndex, fight.EncodeTargetIndex(targetIndex, false)) - default: - go c.FightC.UseSkillAt(c, data.SkillId, actorIndex, fight.EncodeTargetIndex(targetIndex, true)) - } + h.dispatchFightActionEnvelope(c, buildIndexedUseSkillEnvelope(data)) return nil, 0 } @@ -74,8 +49,7 @@ func (h Controller) Escape(data *EscapeFightInboundInfo, c *player.Player) (resu if err := h.checkFightStatus(c); err != 0 { return nil, err } - - go c.FightC.Over(c, model.BattleOverReason.PlayerEscape) + h.dispatchFightActionEnvelope(c, buildLegacyEscapeEnvelope()) return nil, 0 } @@ -84,7 +58,7 @@ func (h Controller) ChangePet(data *ChangePetInboundInfo, c *player.Player) (res if err := h.checkFightStatus(c); err != 0 { return nil, err } - go c.FightC.ChangePet(c, data.CatchTime) + h.dispatchFightActionEnvelope(c, buildLegacyChangeEnvelope(data)) return nil, -1 } @@ -123,7 +97,7 @@ func (h Controller) UsePetItemInboundInfo(data *UsePetItemInboundInfo, c *player } } - go c.FightC.UseItem(c, data.CatchTime, data.ItemId) + h.dispatchFightActionEnvelope(c, buildLegacyUseItemEnvelope(data)) return nil, -1 } @@ -132,6 +106,6 @@ func (h Controller) FightChat(data *ChatInfo, c *player.Player) (result *fight.N if err := h.checkFightStatus(c); err != 0 { return nil, err } - go c.FightC.Chat(c, data.Message) + h.dispatchFightActionEnvelope(c, buildChatEnvelope(data)) return nil, -1 } diff --git a/logic/controller/fight_unified.go b/logic/controller/fight_unified.go new file mode 100644 index 000000000..c9f9cc557 --- /dev/null +++ b/logic/controller/fight_unified.go @@ -0,0 +1,79 @@ +package controller + +import ( + "blazing/modules/player/model" + + "blazing/logic/service/fight" + "blazing/logic/service/player" +) + +// dispatchFightActionEnvelope 把控制器层收到的统一动作结构分发回现有 FightI 接口。 +func (h Controller) dispatchFightActionEnvelope(c *player.Player, envelope fight.FightActionEnvelope) { + if c == nil || c.FightC == nil { + return + } + + switch envelope.ActionType { + case fight.FightActionTypeSkill: + go c.FightC.UseSkillAt(c, envelope.SkillID, envelope.ActorIndex, envelope.EncodedTargetIndex()) + case fight.FightActionTypeItem: + go c.FightC.UseItemAt(c, envelope.CatchTime, envelope.ItemID, envelope.ActorIndex, envelope.EncodedTargetIndex()) + case fight.FightActionTypeChange: + go c.FightC.ChangePetAt(c, envelope.CatchTime, envelope.ActorIndex) + case fight.FightActionTypeEscape: + go c.FightC.Over(c, model.BattleOverReason.PlayerEscape) + case fight.FightActionTypeChat: + go c.FightC.Chat(c, envelope.Chat) + } +} + +// buildLegacyUseSkillEnvelope 把旧 2405 技能包映射成统一动作结构。 +func buildLegacyUseSkillEnvelope(data *UseSkillInInfo) fight.FightActionEnvelope { + if data == nil { + return fight.NewSkillActionEnvelope(0, 0, 0, fight.SkillTargetOpponent, 0) + } + return fight.NewSkillActionEnvelope(data.SkillId, 0, 0, fight.SkillTargetOpponent, 0) +} + +// buildIndexedUseSkillEnvelope 把 7505 多战位技能包映射成统一动作结构。 +func buildIndexedUseSkillEnvelope(data *UseSkillAtInboundInfo) fight.FightActionEnvelope { + if data == nil { + return fight.NewSkillActionEnvelope(0, 0, 0, fight.SkillTargetOpponent, 0) + } + return fight.NewSkillActionEnvelope( + data.SkillId, + int(data.ActorIndex), + int(data.TargetIndex), + data.TargetRelation, + data.AtkType, + ) +} + +// buildLegacyUseItemEnvelope 把旧 2406 道具包映射成统一动作结构。 +func buildLegacyUseItemEnvelope(data *UsePetItemInboundInfo) fight.FightActionEnvelope { + if data == nil { + return fight.NewItemActionEnvelope(0, 0, 0, 0, fight.SkillTargetOpponent) + } + return fight.NewItemActionEnvelope(data.CatchTime, data.ItemId, 0, 0, fight.SkillTargetOpponent) +} + +// buildLegacyChangeEnvelope 把旧 2407 切宠包映射成统一动作结构。 +func buildLegacyChangeEnvelope(data *ChangePetInboundInfo) fight.FightActionEnvelope { + if data == nil { + return fight.NewChangeActionEnvelope(0, 0) + } + return fight.NewChangeActionEnvelope(data.CatchTime, 0) +} + +// buildLegacyEscapeEnvelope 构造旧 2410 逃跑包对应的统一动作结构。 +func buildLegacyEscapeEnvelope() fight.FightActionEnvelope { + return fight.NewEscapeActionEnvelope() +} + +// buildChatEnvelope 把战斗聊天包映射成统一动作结构。 +func buildChatEnvelope(data *ChatInfo) fight.FightActionEnvelope { + if data == nil { + return fight.NewChatActionEnvelope("") + } + return fight.NewChatActionEnvelope(data.Message) +} diff --git a/logic/service/fight/boss/NewSeIdx_501.go b/logic/service/fight/boss/NewSeIdx_501.go index 0007755c4..4d360c9a7 100644 --- a/logic/service/fight/boss/NewSeIdx_501.go +++ b/logic/service/fight/boss/NewSeIdx_501.go @@ -1,26 +1,25 @@ package effect -import ( - "blazing/logic/service/fight/input" -) +import "blazing/logic/service/fight/input" // 501. g1. 最后一个死 (只要有队友没死, 则自己又恢复hp和pp) -// TODO: 需要了解如何判断队友状态并恢复HP和PP type NewSel501 struct { NewSel0 } +// SwitchOut 在拥有者死亡离场时触发;若仍有存活队友,则把自己回满留作后续再上场。 func (e *NewSel501) SwitchOut(in *input.Input) bool { - //魂印特性有不在场的情况,绑定时候将精灵和特性绑定 - if e.ID().GetCatchTime() != e.Ctx().Our.CurPet[0].Info.CatchTime { + owner := e.SourceInput() + if owner == nil || in != owner || !e.IsOwner() { + return true + } + currentPet := owner.CurrentPet() + if currentPet == nil || currentPet.Info.Hp > 0 || !owner.HasLivingTeammate() { return true } - // TODO: 检查是否有队友还活着 - // 如果有队友活着,恢复自身HP和PP - // e.Ctx().Our.Heal(e.Ctx().Our, nil, e.Ctx().Our.CurPet[0].GetMaxHP()) - // TODO: 恢复PP值的方法 - + currentPet.Info.Hp = currentPet.Info.MaxHp + owner.HealPP(-1) return true } diff --git a/logic/service/fight/boss/NewSeIdx_502.go b/logic/service/fight/boss/NewSeIdx_502.go index 66bf26fc0..a77d2f579 100644 --- a/logic/service/fight/boss/NewSeIdx_502.go +++ b/logic/service/fight/boss/NewSeIdx_502.go @@ -1,25 +1,31 @@ package effect -import ( - "blazing/logic/service/fight/input" -) +import "blazing/logic/service/fight/input" // 502. g2. 如果自身死亡, 恢复队友所有体力和PP值 -// TODO: 实现恢复队友所有体力和PP值的核心逻辑 type NewSel502 struct { NewSel0 } -// TODO: 需要找到精灵死亡时的回调接口 +// SwitchOut 在拥有者死亡离场时触发;把仍在场的队友体力和 PP 恢复到满值。 func (e *NewSel502) SwitchOut(in *input.Input) bool { - //魂印特性有不在场的情况,绑定时候将精灵和特性绑定 - if e.ID().GetCatchTime() != e.Ctx().Our.CurPet[0].Info.CatchTime { + owner := e.SourceInput() + if owner == nil || in != owner || !e.IsOwner() { + return true + } + currentPet := owner.CurrentPet() + if currentPet == nil || currentPet.Info.Hp > 0 { return true } - // TODO: 检查精灵是否死亡(HP为0) - // 如果死亡,恢复队友所有体力和PP值 - // 需要遍历队友的精灵并调用相应的方法 + for _, teammate := range owner.LivingTeammates() { + pet := teammate.CurrentPet() + if pet == nil { + continue + } + pet.Info.Hp = pet.Info.MaxHp + teammate.HealPP(-1) + } return true } diff --git a/logic/service/fight/boss/NewSeIdx_503.go b/logic/service/fight/boss/NewSeIdx_503.go index 35fa97908..781e1e3dc 100644 --- a/logic/service/fight/boss/NewSeIdx_503.go +++ b/logic/service/fight/boss/NewSeIdx_503.go @@ -6,20 +6,35 @@ import ( ) // 503. g3. 群体攻击技能可额外增加一个目标(最多不超过5个目标) -// TODO: 需要了解如何修改群体攻击技能的目标数量 type NewSel503 struct { NewSel0 } +// TurnStart 在拥有者本回合准备出手时触发;若本次技能是群体技能,则把目标数额外加 1。 func (e *NewSel503) TurnStart(fattack *action.SelectSkillAction, sattack *action.SelectSkillAction) { - //魂印特性有不在场的情况,绑定时候将精灵和特性绑定 - if e.ID().GetCatchTime() != e.Ctx().Our.CurPet[0].Info.CatchTime { + owner := e.SourceInput() + if owner == nil || !e.IsOwner() { return } - // TODO: 检查技能是否是群体攻击技能 - // 如果是群体攻击,增加一个目标(最多不超过5个) - // 需要了解技能的目标数量限制机制 + for _, act := range []*action.SelectSkillAction{fattack, sattack} { + if act == nil || act.SkillEntity == nil || act.SkillEntity.Pet == nil { + continue + } + if act.SkillEntity.Pet.Info.CatchTime != e.ID().GetCatchTime() { + continue + } + if act.SkillEntity.XML.AtkType != 0 { + return + } + if act.SkillEntity.XML.AtkNum <= 0 { + act.SkillEntity.XML.AtkNum = 1 + } + if act.SkillEntity.XML.AtkNum < 5 { + act.SkillEntity.XML.AtkNum++ + } + return + } } func init() { diff --git a/logic/service/fight/cmd_unified.go b/logic/service/fight/cmd_unified.go new file mode 100644 index 000000000..55a646d98 --- /dev/null +++ b/logic/service/fight/cmd_unified.go @@ -0,0 +1,138 @@ +package fight + +import ( + "blazing/logic/service/common" + "blazing/modules/player/model" +) + +// FightActionType 表示统一动作包里的动作类型。 +type FightActionType string + +const ( + // FightActionTypeSkill 表示使用技能。 + FightActionTypeSkill FightActionType = "skill" + // FightActionTypeItem 表示使用道具。 + FightActionTypeItem FightActionType = "item" + // FightActionTypeChange 表示主动切宠。 + FightActionTypeChange FightActionType = "change" + // FightActionTypeEscape 表示逃跑。 + FightActionTypeEscape FightActionType = "escape" + // FightActionTypeChat 表示战斗内聊天。 + FightActionTypeChat FightActionType = "chat" +) + +// FightActionEnvelope 是统一入站动作结构。 +// 约定: +// 1. actorIndex 始终表示发起方所在的我方槽位。 +// 2. targetIndex 始终表示目标在所属阵营内的槽位。 +// 3. targetRelation 用来区分 targetIndex 属于敌方、自己还是队友。 +type FightActionEnvelope struct { + ActionType FightActionType `json:"actionType"` + ActorIndex int `json:"actorIndex"` + TargetIndex int `json:"targetIndex"` + TargetRelation uint8 `json:"targetRelation,omitempty"` + SkillID uint32 `json:"skillId,omitempty"` + ItemID uint32 `json:"itemId,omitempty"` + CatchTime uint32 `json:"catchTime,omitempty"` + Escape bool `json:"escape,omitempty"` + Chat string `json:"chat,omitempty"` + AtkType uint8 `json:"atkType,omitempty"` +} + +// NewSkillActionEnvelope 构造技能动作 envelope。 +func NewSkillActionEnvelope(skillID uint32, actorIndex, targetIndex int, targetRelation, atkType uint8) FightActionEnvelope { + return FightActionEnvelope{ + ActionType: FightActionTypeSkill, + ActorIndex: actorIndex, + TargetIndex: targetIndex, + TargetRelation: targetRelation, + SkillID: skillID, + AtkType: atkType, + } +} + +// NewItemActionEnvelope 构造道具动作 envelope。 +func NewItemActionEnvelope(catchTime, itemID uint32, actorIndex, targetIndex int, targetRelation uint8) FightActionEnvelope { + return FightActionEnvelope{ + ActionType: FightActionTypeItem, + ActorIndex: actorIndex, + TargetIndex: targetIndex, + TargetRelation: targetRelation, + ItemID: itemID, + CatchTime: catchTime, + } +} + +// NewChangeActionEnvelope 构造切宠动作 envelope。 +func NewChangeActionEnvelope(catchTime uint32, actorIndex int) FightActionEnvelope { + return FightActionEnvelope{ + ActionType: FightActionTypeChange, + ActorIndex: actorIndex, + CatchTime: catchTime, + } +} + +// NewEscapeActionEnvelope 构造逃跑动作 envelope。 +func NewEscapeActionEnvelope() FightActionEnvelope { + return FightActionEnvelope{ + ActionType: FightActionTypeEscape, + Escape: true, + } +} + +// NewChatActionEnvelope 构造聊天动作 envelope。 +func NewChatActionEnvelope(chat string) FightActionEnvelope { + return FightActionEnvelope{ + ActionType: FightActionTypeChat, + Chat: chat, + } +} + +// normalizedTargetRelation 根据 TargetRelation 和 AtkType 兜底出最终目标关系。 +func (e FightActionEnvelope) normalizedTargetRelation() uint8 { + if e.TargetRelation <= SkillTargetAlly { + return e.TargetRelation + } + switch e.AtkType { + case 3: + return SkillTargetSelf + case 1: + return SkillTargetAlly + default: + return SkillTargetOpponent + } +} + +// EncodedTargetIndex 把统一结构里的目标信息编码成现有 FightC 内部使用的目标格式。 +func (e FightActionEnvelope) EncodedTargetIndex() int { + targetIndex := e.TargetIndex + switch e.normalizedTargetRelation() { + case SkillTargetSelf: + targetIndex = e.ActorIndex + return EncodeTargetIndex(targetIndex, false) + case SkillTargetAlly: + return EncodeTargetIndex(targetIndex, false) + default: + return EncodeTargetIndex(targetIndex, true) + } +} + +// HandleActionEnvelope 把统一动作结构派发到现有 FightC 的 indexed 接口上。 +func (f *FightC) HandleActionEnvelope(c common.PlayerI, envelope FightActionEnvelope) { + if f == nil || c == nil { + return + } + + switch envelope.ActionType { + case FightActionTypeSkill: + f.UseSkillAt(c, envelope.SkillID, envelope.ActorIndex, envelope.EncodedTargetIndex()) + case FightActionTypeItem: + f.UseItemAt(c, envelope.CatchTime, envelope.ItemID, envelope.ActorIndex, envelope.EncodedTargetIndex()) + case FightActionTypeChange: + f.ChangePetAt(c, envelope.CatchTime, envelope.ActorIndex) + case FightActionTypeEscape: + f.Over(c, model.BattleOverReason.PlayerEscape) + case FightActionTypeChat: + f.Chat(c, envelope.Chat) + } +} diff --git a/logic/service/fight/info/unified_info.go b/logic/service/fight/info/unified_info.go new file mode 100644 index 000000000..21d5981e7 --- /dev/null +++ b/logic/service/fight/info/unified_info.go @@ -0,0 +1,102 @@ +package info + +import "blazing/modules/player/model" + +// FightStatePhase 表示统一战斗状态包的阶段。 +type FightStatePhase string + +const ( + // FightStatePhaseStart 表示开战快照。 + FightStatePhaseStart FightStatePhase = "start" + // FightStatePhaseSkillHurt 表示技能结算后的伤害快照。 + FightStatePhaseSkillHurt FightStatePhase = "skill_hurt" + // FightStatePhaseChange 表示切宠快照。 + FightStatePhaseChange FightStatePhase = "change" + // FightStatePhaseOver 表示战斗结束快照。 + FightStatePhaseOver FightStatePhase = "over" + // FightStatePhaseLoad 表示加载进度快照。 + FightStatePhaseLoad FightStatePhase = "load" + // FightStatePhaseChat 表示战斗内聊天快照。 + FightStatePhaseChat FightStatePhase = "chat" +) + +// FighterState 是统一的战位快照。 +// position 表示该 side 下的槽位编号;actorIndex/targetIndex 与其一一对应。 +type FighterState struct { + // Side 阵营标识:1=我方,2=敌方。 + Side int `json:"side"` + // Position 战位下标;在各自 side 内部从 0 开始编号。 + Position int `json:"position"` + // UserID 当前战位所属玩家 ID;野怪/NPC 通常为 0。 + UserID uint32 `json:"userId"` + // ControllerUserID 当前上场精灵的实际操作者 ID;组队时可与 UserID 联合定位操作者和战位归属。 + ControllerUserID uint32 `json:"controllerUserId"` + // PetID 当前上场精灵的物种/配置 ID。 + PetID uint32 `json:"petId"` + // CatchTime 当前上场精灵的唯一实例 ID,可理解为这只精灵在玩家背包中的唯一标识。 + CatchTime uint32 `json:"catchTime"` + // Name 当前上场精灵名字。 + Name string `json:"name,omitempty"` + // HP 当前生命值。 + HP uint32 `json:"hp"` + // MaxHP 最大生命值。 + MaxHP uint32 `json:"maxHp"` + // Level 当前等级。 + Level uint32 `json:"level"` + // Anger 怒气值;当前服务端主链路暂未实际填充时默认为 0,先为协议对齐预留。 + Anger uint32 `json:"anger"` + // Status 当前异常/增益状态回合数组;下标语义沿用现有战斗状态定义。 + Status [20]int8 `json:"status"` + // Prop 当前能力等级变化数组:攻击、防御、特攻、特防、速度、命中。 + Prop [6]int8 `json:"prop"` + // Skills 当前可见技能列表,包含技能 ID 和当前 PP 等信息。 + Skills []model.SkillInfo `json:"skills,omitempty"` +} + +// FightStateMeta 是统一状态包的公共元数据。 +type FightStateMeta struct { + Round uint32 `json:"round"` + Weather uint32 `json:"weather,omitempty"` + WinnerID uint32 `json:"winnerId,omitempty"` + Reason model.EnumBattleOverReason `json:"reason,omitempty"` + LegacyCmd uint32 `json:"legacyCmd,omitempty"` +} + +// FightSkillHurtState 保存技能结算后的左右两侧战报快照。 +type FightSkillHurtState struct { + Left []model.AttackValue `json:"left,omitempty"` + Right []model.AttackValue `json:"right,omitempty"` +} + +// FightLoadState 保存加载进度信息。 +type FightLoadState struct { + UserID uint32 `json:"userId"` + Percent uint32 `json:"percent"` +} + +// FightChatState 保存战斗内聊天信息。 +type FightChatState struct { + SenderID uint32 `json:"senderId"` + SenderNickname string `json:"senderNickname"` + Message string `json:"message"` +} + +// FightStateEnvelope 是统一出站状态结构。 +type FightStateEnvelope struct { + // Phase 当前下发阶段,例如 start、skill_hurt、change、over、load、chat。 + Phase FightStatePhase `json:"phase"` + // Left 我方阵营当前所有战位快照。 + Left []FighterState `json:"left,omitempty"` + // Right 敌方阵营当前所有战位快照。 + Right []FighterState `json:"right,omitempty"` + // Meta 当前阶段共用的元数据,如回合号、胜方、结束原因、旧协议命令号。 + Meta FightStateMeta `json:"meta"` + // SkillHurt 技能结算阶段附带的详细战报;仅 phase=skill_hurt 时使用。 + SkillHurt *FightSkillHurtState `json:"skillHurt,omitempty"` + // Change 切宠阶段附带的切宠详情;仅 phase=change 时使用。 + Change *ChangePetInfo `json:"change,omitempty"` + // Load 加载阶段附带的进度信息;仅 phase=load 时使用。 + Load *FightLoadState `json:"load,omitempty"` + // Chat 聊天阶段附带的聊天内容;仅 phase=chat 时使用。 + Chat *FightChatState `json:"chat,omitempty"` +} diff --git a/logic/service/fight/input/team.go b/logic/service/fight/input/team.go new file mode 100644 index 000000000..f84a9090d --- /dev/null +++ b/logic/service/fight/input/team.go @@ -0,0 +1,55 @@ +package input + +// TeamSlots 返回当前输入所在阵营的全部有效战斗位视图。 +func (our *Input) TeamSlots() []*Input { + if our == nil { + return nil + } + if len(our.Team) == 0 { + return []*Input{our} + } + slots := make([]*Input, 0, len(our.Team)) + for _, teammate := range our.Team { + if teammate == nil { + continue + } + slots = append(slots, teammate) + } + return slots +} + +// Teammates 返回队友列表,不包含自己。 +func (our *Input) Teammates() []*Input { + if our == nil { + return nil + } + teammates := make([]*Input, 0, len(our.Team)) + for _, teammate := range our.TeamSlots() { + if teammate == nil || teammate == our { + continue + } + teammates = append(teammates, teammate) + } + return teammates +} + +// LivingTeammates 返回当前仍有存活出战精灵的队友列表。 +func (our *Input) LivingTeammates() []*Input { + if our == nil { + return nil + } + teammates := make([]*Input, 0, len(our.Team)) + for _, teammate := range our.Teammates() { + currentPet := teammate.CurrentPet() + if currentPet == nil || currentPet.Info.Hp == 0 { + continue + } + teammates = append(teammates, teammate) + } + return teammates +} + +// HasLivingTeammate 用于快速判断当前战斗位是否还有存活队友。 +func (our *Input) HasLivingTeammate() bool { + return len(our.LivingTeammates()) > 0 +} diff --git a/logic/service/fight/input/team_test.go b/logic/service/fight/input/team_test.go new file mode 100644 index 000000000..9059a99a3 --- /dev/null +++ b/logic/service/fight/input/team_test.go @@ -0,0 +1,30 @@ +package input + +import ( + "testing" + + fightinfo "blazing/logic/service/fight/info" + "blazing/modules/player/model" +) + +func TestLivingTeammatesFiltersSelfAndDeadSlots(t *testing.T) { + owner := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 10}}}} + aliveMate := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 5}}}} + deadMate := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 0}}}} + + team := []*Input{owner, aliveMate, deadMate} + owner.Team = team + aliveMate.Team = team + deadMate.Team = team + + teammates := owner.LivingTeammates() + if len(teammates) != 1 { + t.Fatalf("expected 1 living teammate, got %d", len(teammates)) + } + if teammates[0] != aliveMate { + t.Fatalf("expected alive teammate to be returned") + } + if owner.HasLivingTeammate() != true { + t.Fatalf("expected owner to detect living teammate") + } +} diff --git a/logic/service/fight/unified_state.go b/logic/service/fight/unified_state.go new file mode 100644 index 000000000..4ef43f2eb --- /dev/null +++ b/logic/service/fight/unified_state.go @@ -0,0 +1,113 @@ +package fight + +import ( + "blazing/logic/service/fight/info" + "blazing/logic/service/fight/input" +) + +// BuildFightStateStartEnvelope 构造开战阶段的统一状态包。 +func (f *FightC) BuildFightStateStartEnvelope() info.FightStateEnvelope { + return f.buildFightStateEnvelope(info.FightStatePhaseStart, 2504) +} + +// BuildFightStateSkillHurtEnvelope 构造技能结算阶段的统一状态包。 +func (f *FightC) BuildFightStateSkillHurtEnvelope() info.FightStateEnvelope { + envelope := f.buildFightStateEnvelope(info.FightStatePhaseSkillHurt, 2505) + envelope.SkillHurt = &info.FightSkillHurtState{ + Left: f.collectAttackValues(f.Our), + Right: f.collectAttackValues(f.Opp), + } + return envelope +} + +// BuildFightStateChangeEnvelope 构造切宠阶段的统一状态包。 +func (f *FightC) BuildFightStateChangeEnvelope(change info.ChangePetInfo) info.FightStateEnvelope { + envelope := f.buildFightStateEnvelope(info.FightStatePhaseChange, 2407) + envelope.Change = &change + return envelope +} + +// BuildFightStateOverEnvelope 构造结束阶段的统一状态包。 +func (f *FightC) BuildFightStateOverEnvelope() info.FightStateEnvelope { + envelope := f.buildFightStateEnvelope(info.FightStatePhaseOver, 2506) + envelope.Meta.WinnerID = f.FightOverInfo.WinnerId + envelope.Meta.Reason = f.FightOverInfo.Reason + return envelope +} + +// BuildFightStateLoadEnvelope 构造加载阶段的统一状态包。 +func (f *FightC) BuildFightStateLoadEnvelope(userID, percent uint32) info.FightStateEnvelope { + envelope := f.buildFightStateEnvelope(info.FightStatePhaseLoad, 2441) + envelope.Load = &info.FightLoadState{ + UserID: userID, + Percent: percent, + } + return envelope +} + +// BuildFightStateChatEnvelope 构造聊天阶段的统一状态包。 +func (f *FightC) BuildFightStateChatEnvelope(senderID uint32, senderNickname, message string) info.FightStateEnvelope { + envelope := f.buildFightStateEnvelope(info.FightStatePhaseChat, 50002) + envelope.Chat = &info.FightChatState{ + SenderID: senderID, + SenderNickname: senderNickname, + Message: message, + } + return envelope +} + +// buildFightStateEnvelope 组装左右两侧通用快照和元数据。 +func (f *FightC) buildFightStateEnvelope(phase info.FightStatePhase, legacyCmd uint32) info.FightStateEnvelope { + if f == nil { + return info.FightStateEnvelope{Phase: phase} + } + return info.FightStateEnvelope{ + Phase: phase, + Left: snapshotFighterStates(SideOur, f.Our), + Right: snapshotFighterStates(SideOpp, f.Opp), + Meta: info.FightStateMeta{ + Round: uint32(f.Round), + WinnerID: f.FightOverInfo.WinnerId, + Reason: f.FightOverInfo.Reason, + LegacyCmd: legacyCmd, + }, + } +} + +// snapshotFighterStates 把指定侧的战斗位数组转成统一 fighter 快照。 +func snapshotFighterStates(side int, fighters []*input.Input) []info.FighterState { + states := make([]info.FighterState, 0, len(fighters)) + for position, fighter := range fighters { + if fighter == nil { + continue + } + state := info.FighterState{ + Side: side, + Position: position, + } + if fighter.Player != nil && fighter.Player.GetInfo() != nil { + state.UserID = fighter.Player.GetInfo().UserID + } + if fighter.AttackValue != nil { + state.Status = fighter.AttackValue.Status + state.Prop = fighter.AttackValue.Prop + } + currentPet := fighter.CurrentPet() + if currentPet == nil { + states = append(states, state) + continue + } + state.ControllerUserID = currentPet.ControllerUserID + state.PetID = currentPet.Info.ID + state.CatchTime = currentPet.Info.CatchTime + state.Name = currentPet.Info.Name + state.HP = currentPet.Info.Hp + state.MaxHP = currentPet.Info.MaxHp + state.Level = currentPet.Info.Level + if len(currentPet.Info.SkillList) > 0 { + state.Skills = append(state.Skills, currentPet.Info.SkillList...) + } + states = append(states, state) + } + return states +} diff --git a/logic/service/fight/unified_test.go b/logic/service/fight/unified_test.go new file mode 100644 index 000000000..dfd03d1fd --- /dev/null +++ b/logic/service/fight/unified_test.go @@ -0,0 +1,113 @@ +package fight + +import ( + "testing" + + "blazing/common/socket/errorcode" + "blazing/logic/service/common" + fightinfo "blazing/logic/service/fight/info" + "blazing/logic/service/fight/input" + spaceinfo "blazing/logic/service/space/info" + "blazing/modules/player/model" +) + +type stubPlayer struct { + info model.PlayerInfo +} + +func (*stubPlayer) ApplyPetDisplayInfo(*spaceinfo.SimpleInfo) {} +func (*stubPlayer) GetPlayerCaptureContext() *fightinfo.PlayerCaptureContext { return nil } +func (*stubPlayer) Roll(int, int) (bool, float64, float64) { return false, 0, 0 } +func (*stubPlayer) Getfightinfo() fightinfo.Fightinfo { return fightinfo.Fightinfo{} } +func (*stubPlayer) ItemAdd(int64, int64) bool { return false } +func (p *stubPlayer) GetInfo() *model.PlayerInfo { return &p.info } +func (*stubPlayer) InvitePlayer(common.PlayerI) {} +func (*stubPlayer) SetFightC(common.FightI) {} +func (*stubPlayer) QuitFight() {} +func (*stubPlayer) MessWin(bool) {} +func (*stubPlayer) CanFight() errorcode.ErrorCode { return 0 } +func (*stubPlayer) SendPackCmd(uint32, any) {} +func (*stubPlayer) GetPetInfo(uint32) []model.PetInfo { return nil } + +func TestFightActionEnvelopeEncodedTargetIndex(t *testing.T) { + self := NewSkillActionEnvelope(1, 2, 0, SkillTargetSelf, 0) + if got := self.EncodedTargetIndex(); got != EncodeTargetIndex(2, false) { + t.Fatalf("expected self target to encode actor slot, got %d", got) + } + + ally := NewSkillActionEnvelope(1, 0, 1, SkillTargetAlly, 0) + if got := ally.EncodedTargetIndex(); got != EncodeTargetIndex(1, false) { + t.Fatalf("expected ally target to keep friendly slot, got %d", got) + } + + fallbackSelf := NewSkillActionEnvelope(1, 3, 0, 9, 3) + if got := fallbackSelf.EncodedTargetIndex(); got != EncodeTargetIndex(3, false) { + t.Fatalf("expected atkType=3 to fall back to self target, got %d", got) + } + + opponent := NewSkillActionEnvelope(1, 0, 2, SkillTargetOpponent, 0) + if got := opponent.EncodedTargetIndex(); got != EncodeTargetIndex(2, true) { + t.Fatalf("expected opponent target to stay on opposite side, got %d", got) + } +} + +func TestBuildFightStateStartEnvelope(t *testing.T) { + ourPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 1001}} + oppPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 2002}} + + our := input.NewInput(nil, ourPlayer) + our.InitAttackValue() + our.AttackValue.Prop[0] = 2 + our.AttackValue.Status[1] = 1 + ourPet := fightinfo.CreateBattlePetEntity(model.PetInfo{ + ID: 11, + Name: "Alpha", + Level: 20, + Hp: 88, + MaxHp: 100, + CatchTime: 101, + SkillList: []model.SkillInfo{{ID: 300, PP: 10}}, + }) + ourPet.BindController(ourPlayer.info.UserID) + our.SetCurPetAt(0, ourPet) + + opp := input.NewInput(nil, oppPlayer) + opp.InitAttackValue() + oppPet := fightinfo.CreateBattlePetEntity(model.PetInfo{ + ID: 22, + Name: "Beta", + Level: 21, + Hp: 77, + MaxHp: 110, + CatchTime: 202, + SkillList: []model.SkillInfo{{ID: 400, PP: 5}}, + }) + oppPet.BindController(oppPlayer.info.UserID) + opp.SetCurPetAt(0, oppPet) + + fc := &FightC{ + Our: []*input.Input{our}, + Opp: []*input.Input{opp}, + } + fc.Round = 7 + + envelope := fc.BuildFightStateStartEnvelope() + if envelope.Phase != fightinfo.FightStatePhaseStart { + t.Fatalf("expected start phase, got %s", envelope.Phase) + } + if envelope.Meta.Round != 7 { + t.Fatalf("expected round 7, got %d", envelope.Meta.Round) + } + if len(envelope.Left) != 1 || len(envelope.Right) != 1 { + t.Fatalf("expected one fighter on each side, got left=%d right=%d", len(envelope.Left), len(envelope.Right)) + } + if envelope.Left[0].UserID != 1001 || envelope.Left[0].PetID != 11 { + t.Fatalf("unexpected left fighter snapshot: %+v", envelope.Left[0]) + } + if envelope.Left[0].Prop[0] != 2 || envelope.Left[0].Status[1] != 1 { + t.Fatalf("expected prop/status snapshot to be copied, got %+v %+v", envelope.Left[0].Prop, envelope.Left[0].Status) + } + if envelope.Right[0].UserID != 2002 || envelope.Right[0].CatchTime != 202 { + t.Fatalf("unexpected right fighter snapshot: %+v", envelope.Right[0]) + } +}