refactor: 重构战斗系统为统一动作包结构
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful

This commit is contained in:
xinian
2026-04-06 00:58:23 +08:00
committed by cnb
parent 141ba67014
commit f433a26a6d
12 changed files with 683 additions and 158 deletions

View File

@@ -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%)几乎没有注释
## 改进建议
### 高优先级
- 继续保持当前的代码质量标准
### 中优先级
- 可以考虑进一步优化性能和可读性
- 完善文档和注释便于团队协作

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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() {

View File

@@ -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)
}
}

View File

@@ -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"`
}

View File

@@ -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
}

View File

@@ -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")
}
}

View File

@@ -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
}

View File

@@ -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])
}
}