Files
bl/logic/service/fight/group_legacy.go
昔念 487ee0e726
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
feat(fight): 添加旧组队协议支持并优化战斗系统

- 实现了旧组队协议相关功能,包括GroupReadyFightFinish、GroupUseSkill、
  GroupUseItem、GroupChangePet和GroupEscape方法
- 新增组队战斗相关的入站信息结构体定义
- 实现了组队BOSS战斗逻辑,添加groupBossSlotLimit常量
- 重构宠物技能设置逻辑,调整金币消耗时机
- 优化战斗循环逻辑,添加对无行动槽位的处理
- 改进AI行动逻辑,增加多位置目标选择机制
- 完善捕获系统上下文处理,修复空指针问题
- 添加战斗状态更新和数据同步机制

fix(pet-skill): 修复宠物技能设置中的金币扣除逻辑错误

- 将金币扣除逻辑移到验证之后
- 修正宠物技能数量限制检查的顺序
- 防止重复添加已有技能的情况

refactor(fight): 重构战斗系统代码结构

- 分离新旧组队协议的战斗创建逻辑
- 优化战斗输入验证和处理流程
- 改进战斗循环中的错误处理机制
```
2026-04-09 02:14:09 +08:00

685 lines
20 KiB
Go

package fight
import (
"blazing/logic/service/common"
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/modules/player/model"
)
const (
groupCmdReadyToFight uint32 = 7555
groupCmdReadyFightFinish uint32 = 7556
groupCmdStartFight uint32 = 7557
groupCmdUseSkill uint32 = 7558
groupCmdSkillHurt uint32 = 7559
groupCmdFightOver uint32 = 7560
groupCmdSpriteDie uint32 = 7561
groupCmdUseItem uint32 = 7562
groupCmdChangePet uint32 = 7563
groupCmdEscape uint32 = 7565
groupCmdBoutDone uint32 = 7566
groupCmdChangePetSuc uint32 = 7567
groupCmdEscapeSuc uint32 = 7568
groupCmdChat uint32 = 7569
groupCmdLoadPercent uint32 = 7571
groupCmdLoadPercentNotice uint32 = 7572
groupCmdSpriteNotice uint32 = 7573
groupCmdFightWinClose uint32 = 7574
groupCmdFightOvertime uint32 = 7585
groupCmdSkillPlayOver uint32 = 7586
groupCmdFightTimeoutExit uint32 = 7587
groupCmdFightRelation uint32 = 7588
groupModelNPC uint32 = 3
groupModelBoss uint32 = 4
groupModelPlayerSingle uint32 = 5
groupModelPlayerMulti uint32 = 6
)
type fightPacketKind uint8
const (
fightPacketReady fightPacketKind = iota
fightPacketStart
fightPacketSkillResult
fightPacketOver
fightPacketChangePetSuccess
fightPacketUseItem
fightPacketChat
fightPacketLoadPercentNotice
)
type legacyEscapeSuccessInfo struct {
UserID uint32 `struc:"uint32"`
Nick string `struc:"[16]byte"`
Side uint8 `struc:"uint8"`
ActorIndex uint8 `struc:"uint8"`
}
type legacyBoutDoneInfo struct {
Round uint32 `struc:"uint32"`
}
type legacySpriteDieInfo struct {
Count uint8 `struc:"uint8"`
Side uint8 `struc:"uint8"`
ActorIndex uint8 `struc:"uint8"`
Flag uint8 `struc:"uint8"`
HasBackup uint32 `struc:"uint32"`
}
type legacyLegacySpriteDieItem struct {
Flag uint8 `struc:"uint8"`
Side uint8 `struc:"uint8"`
ActorIndex uint8 `struc:"uint8"`
Reserve uint8 `struc:"uint8"`
HasBackup uint32 `struc:"uint32"`
}
type legacyGroupReadyToFightInfo struct {
Model uint32 `struc:"uint32"`
GroupOneInfo legacyReadyToFightTeam `struc:""`
GroupTwoInfo legacyReadyToFightTeam `struc:""`
}
type legacyReadyToFightTeam struct {
InvitorID uint8 `struc:"uint8"`
LeaderID uint32 `struc:"uint32"`
GroupMembCnt uint8 `struc:"sizeof=GroupList"`
GroupList []legacyReadyFightUser `struc:""`
}
type legacyReadyFightUser struct {
UserID uint32 `struc:"uint32"`
Nick string `struc:"[16]byte"`
MonCnt uint32 `struc:"sizeof=MonList"`
MonList []legacyReadyFightPet `struc:""`
}
type legacyReadyFightPet struct {
ID uint32 `struc:"uint32"`
MoveCnt uint32 `struc:"sizeof=MoveList"`
MoveList []uint32 `struc:"[]uint32"`
}
type legacyGroupStartInfo struct {
IsGank uint8 `struc:"uint8"`
GroupOneN uint8 `struc:"sizeof=GroupOne"`
GroupOne []legacyGroupStartPet `struc:""`
GroupTwoN uint8 `struc:"sizeof=GroupTwo"`
GroupTwo []legacyGroupStartPet `struc:""`
}
type legacyGroupStartPet struct {
Side uint8 `struc:"uint8"`
Pos uint8 `struc:"uint8"`
UserID uint32 `struc:"uint32"`
IsChange uint8 `struc:"uint8"`
PetID uint32 `struc:"uint32"`
CatchTime uint32 `struc:"uint32"`
Hp uint32 `struc:"uint32"`
MaxHp uint32 `struc:"uint32"`
Level uint32 `struc:"uint32"`
Reserve uint32 `struc:"uint32"`
Flag uint32 `struc:"uint32"`
}
type legacyGroupSkillHurtPacket struct {
IsGank uint8 `struc:"uint8"`
Attack legacyGroupSkillAttackInfo `struc:""`
Attacked legacyGroupSkillDefendInfo `struc:""`
}
type legacyGroupSkillAttackInfo struct {
IsAttackor uint8 `struc:"uint8"`
Side uint8 `struc:"uint8"`
Pos uint8 `struc:"uint8"`
UserID uint32 `struc:"uint32"`
StatusList [20]uint8 `struc:"[20]byte"`
Reserve1 uint8 `struc:"uint8"`
Reserve2 uint8 `struc:"uint8"`
BatLvList [6]uint8 `struc:"[6]byte"`
PetID uint32 `struc:"uint32"`
MoveID uint32 `struc:"uint32"`
Hp uint32 `struc:"uint32"`
MaxHp uint32 `struc:"uint32"`
MoveCnt uint32 `struc:"sizeof=MoveMap"`
MoveMap []legacyGroupSkillMoveInfo `struc:""`
Flag uint32 `struc:"uint32"`
IsCrit uint32 `struc:"uint32"`
EffectName uint32 `struc:"uint32"`
AtkTimes uint32 `struc:"uint32"`
Dmg int32 `struc:"int32"`
ChgHp int32 `struc:"int32"`
SideEffectLen uint32 `struc:"uint32"`
}
type legacyGroupSkillDefendInfo struct {
IsAttackor uint8 `struc:"uint8"`
Side uint8 `struc:"uint8"`
Pos uint8 `struc:"uint8"`
UserID uint32 `struc:"uint32"`
StatusList [20]uint8 `struc:"[20]byte"`
Reserve1 uint8 `struc:"uint8"`
Reserve2 uint8 `struc:"uint8"`
BatLvList [6]uint8 `struc:"[6]byte"`
PetID uint32 `struc:"uint32"`
MoveID uint32 `struc:"uint32"`
Hp uint32 `struc:"uint32"`
MaxHp uint32 `struc:"uint32"`
MoveCnt uint32 `struc:"sizeof=MoveMap"`
MoveMap []legacyGroupSkillMoveInfo `struc:""`
Flag uint32 `struc:"uint32"`
SideEffectLen uint32 `struc:"uint32"`
}
type legacyGroupSkillMoveInfo struct {
MoveID uint32 `struc:"uint32"`
PP uint32 `struc:"uint32"`
}
type legacyGroupFightOverInfo struct {
IsGank uint8 `struc:"uint8"`
Reason uint32 `struc:"uint32"`
WinnerID uint32 `struc:"uint32"`
Reserve uint32 `struc:"uint32"`
TwoTimes uint32 `struc:"uint32"`
ThreeTimes uint32 `struc:"uint32"`
AutoFightTime uint32 `struc:"uint32"`
Reserve2 uint32 `struc:"uint32"`
EnergyTime uint32 `struc:"uint32"`
LearnTimes uint32 `struc:"uint32"`
}
type legacyGroupChangePetSuccessInfo struct {
Side uint8 `struc:"uint8"`
Pos uint8 `struc:"uint8"`
UserID uint32 `struc:"uint32"`
PetID uint32 `struc:"uint32"`
CatchTime uint32 `struc:"uint32"`
Level uint32 `struc:"uint32"`
Hp uint32 `struc:"uint32"`
MaxHp uint32 `struc:"uint32"`
SkinID uint32 `struc:"uint32"`
}
func groupModelByFight(f *FightC) uint32 {
if f == nil {
return groupModelBoss
}
switch {
case f.Info.Status == 0:
return groupModelNPC
case f.Info.Status == 1 && f.Info.Mode == 1:
return groupModelPlayerSingle
case f.Info.Status == 1:
return groupModelPlayerMulti
default:
return groupModelBoss
}
}
func (f *FightC) fightPacketCmd(kind fightPacketKind) uint32 {
switch kind {
case fightPacketReady:
return 2503
case fightPacketStart:
return 2504
case fightPacketSkillResult:
return 2505
case fightPacketOver:
return 2506
case fightPacketChangePetSuccess:
if f != nil && f.LegacyGroupProtocol {
return groupCmdChangePetSuc
}
return 2407
case fightPacketUseItem:
if f != nil && f.LegacyGroupProtocol {
return groupCmdUseItem
}
return 2406
case fightPacketChat:
if f != nil && f.LegacyGroupProtocol {
return groupCmdChat
}
return 50002
case fightPacketLoadPercentNotice:
if f != nil && f.LegacyGroupProtocol {
return groupCmdLoadPercentNotice
}
return 2441
default:
return 0
}
}
func (f *FightC) sendFightPacket(player common.PlayerI, kind fightPacketKind, payload any) {
if player == nil {
return
}
cmd := f.fightPacketCmd(kind)
if cmd == 0 {
return
}
player.SendPackCmd(cmd, payload)
}
func (f *FightC) sendLegacyGroupReady(player common.PlayerI) {
if f == nil || !f.LegacyGroupProtocol || player == nil {
return
}
player.SendPackCmd(groupCmdReadyToFight, f.buildLegacyGroupReadyInfo())
}
func (f *FightC) buildLegacyGroupReadyInfo() *legacyGroupReadyToFightInfo {
return &legacyGroupReadyToFightInfo{
Model: groupModelByFight(f),
GroupOneInfo: f.buildLegacyReadyTeam(f.OurPlayers, f.Our),
GroupTwoInfo: f.buildLegacyReadyTeam(f.OppPlayers, f.Opp),
}
}
func (f *FightC) buildLegacyReadyTeam(players []common.PlayerI, inputs []*input.Input) legacyReadyToFightTeam {
team := legacyReadyToFightTeam{InvitorID: 1}
users := make([]legacyReadyFightUser, 0, len(players))
for _, p := range players {
if p == nil || p.GetInfo() == nil {
continue
}
users = append(users, legacyReadyFightUser{
UserID: p.GetInfo().UserID,
Nick: p.GetInfo().Nick,
MonList: collectLegacyReadyPetsByController(inputs, p.GetInfo().UserID),
})
}
if len(users) == 0 {
if fallback := firstNonNilInput(inputs); fallback != nil && fallback.Player != nil && fallback.Player.GetInfo() != nil {
info := fallback.Player.GetInfo()
users = append(users, legacyReadyFightUser{
UserID: info.UserID,
Nick: info.Nick,
MonList: collectLegacyReadyPetsByController(inputs, info.UserID),
})
}
}
for idx := range users {
users[idx].MonCnt = uint32(len(users[idx].MonList))
}
if len(users) > 0 {
team.LeaderID = users[0].UserID
}
team.GroupList = users
return team
}
func collectLegacyReadyPetsByController(inputs []*input.Input, controllerID uint32) []legacyReadyFightPet {
pets := make([]legacyReadyFightPet, 0, 6)
for _, in := range inputs {
if in == nil || !in.ControlledBy(controllerID) {
continue
}
currentPet := in.CurrentPet()
if currentPet == nil {
continue
}
pets = append(pets, buildLegacyReadyFightPet(currentPet))
}
return pets
}
func buildLegacyReadyFightPet(pet *info.BattlePetEntity) legacyReadyFightPet {
result := legacyReadyFightPet{}
if pet == nil {
return result
}
moves := make([]uint32, 0, len(pet.Info.SkillList))
for _, skill := range pet.Info.SkillList {
if skill.ID == 0 {
continue
}
moves = append(moves, skill.ID)
}
result.ID = pet.Info.ID
result.MoveList = moves
return result
}
func firstNonNilInput(inputs []*input.Input) *input.Input {
for _, in := range inputs {
if in != nil {
return in
}
}
return nil
}
func (f *FightC) sendLegacyGroupStart(player common.PlayerI) {
if f == nil || !f.LegacyGroupProtocol || player == nil {
return
}
player.SendPackCmd(groupCmdStartFight, f.buildLegacyGroupStartInfo())
}
func (f *FightC) buildLegacyGroupStartInfo() *legacyGroupStartInfo {
return &legacyGroupStartInfo{
IsGank: 0,
GroupOne: f.collectLegacyGroupStartPets(f.Our, 1),
GroupTwo: f.collectLegacyGroupStartPets(f.Opp, 2),
}
}
func (f *FightC) collectLegacyGroupStartPets(inputs []*input.Input, side uint8) []legacyGroupStartPet {
ret := make([]legacyGroupStartPet, 0, len(inputs))
for pos, in := range inputs {
if in == nil {
continue
}
currentPet := in.CurrentPet()
if currentPet == nil {
continue
}
userID := uint32(0)
if in.Player != nil && in.Player.GetInfo() != nil {
userID = in.Player.GetInfo().UserID
}
ret = append(ret, legacyGroupStartPet{
Side: side,
Pos: uint8(pos),
UserID: userID,
IsChange: 0,
PetID: currentPet.Info.ID,
CatchTime: currentPet.Info.CatchTime,
Hp: currentPet.Info.Hp,
MaxHp: currentPet.Info.MaxHp,
Level: currentPet.Info.Level,
Reserve: 0,
Flag: 1,
})
}
return ret
}
func (f *FightC) sendLegacyGroupOver(player common.PlayerI, over *model.FightOverInfo) {
if f == nil || !f.LegacyGroupProtocol || player == nil {
return
}
player.SendPackCmd(groupCmdFightOver, f.buildLegacyGroupOverInfo(over))
}
func (f *FightC) buildLegacyGroupOverInfo(over *model.FightOverInfo) *legacyGroupFightOverInfo {
result := &legacyGroupFightOverInfo{}
if over != nil {
result.Reason = resolveLegacyGroupFightOverReason(over)
result.WinnerID = over.WinnerId
}
if our := f.primaryOurPlayer(); our != nil && our.GetInfo() != nil {
playerInfo := our.GetInfo()
result.TwoTimes = uint32(playerInfo.TwoTimes)
result.ThreeTimes = uint32(playerInfo.ThreeTimes)
result.AutoFightTime = playerInfo.AutoFightTime
result.EnergyTime = uint32(playerInfo.EnergyTime)
result.LearnTimes = playerInfo.LearnTimes
}
return result
}
func mapLegacyGroupFightOverReason(reason model.EnumBattleOverReason) uint32 {
switch reason {
case model.BattleOverReason.PlayerOffline:
return 2
case model.BattleOverReason.PlayerOVerTime:
return 3
case model.BattleOverReason.NOTwind:
return 4
case model.BattleOverReason.DefaultEnd:
return 1
case model.BattleOverReason.PlayerEscape:
return 6
default:
return 5
}
}
func resolveLegacyGroupFightOverReason(over *model.FightOverInfo) uint32 {
if over == nil {
return 5
}
switch over.Reason {
case model.BattleOverReason.PlayerOffline:
return 2
case model.BattleOverReason.PlayerOVerTime:
return 3
case model.BattleOverReason.PlayerEscape:
return 6
case model.BattleOverReason.NOTwind:
return 4
}
if over.WinnerId != 0 {
return 1
}
return mapLegacyGroupFightOverReason(over.Reason)
}
func (f *FightC) sendLegacyGroupChangePetSuccess(player common.PlayerI, in *input.Input, reason *info.ChangePetInfo) {
if f == nil || !f.LegacyGroupProtocol || player == nil || in == nil || reason == nil {
return
}
player.SendPackCmd(groupCmdChangePetSuc, f.buildLegacyGroupChangePetSuccessInfo(in, reason))
}
func (f *FightC) buildLegacyGroupChangePetSuccessInfo(in *input.Input, reason *info.ChangePetInfo) *legacyGroupChangePetSuccessInfo {
result := &legacyGroupChangePetSuccessInfo{}
if in == nil || reason == nil {
return result
}
if !f.isOurPlayerID(in.UserID) {
result.Side = 2
} else {
result.Side = 1
}
result.Pos = uint8(in.TeamSlotIndex())
result.UserID = reason.UserId
result.PetID = reason.ID
result.CatchTime = reason.CatchTime
result.Level = reason.Level
result.Hp = reason.Hp
result.MaxHp = reason.MaxHp
if currentPet := in.CurrentPet(); currentPet != nil {
result.SkinID = currentPet.Info.SkinID
}
return result
}
func (f *FightC) SendLegacyEscapeSuccess(player common.PlayerI, actorIndex int) {
if f == nil || !f.LegacyGroupProtocol || player == nil {
return
}
side := uint8(1)
if !f.isOurPlayerID(player.GetInfo().UserID) {
side = 2
}
payload := legacyEscapeSuccessInfo{
UserID: player.GetInfo().UserID,
Nick: player.GetInfo().Nick,
Side: side,
ActorIndex: uint8(actorIndex),
}
f.BroadcastPlayers(func(p common.PlayerI) {
p.SendPackCmd(groupCmdEscapeSuc, &payload)
})
}
func (f *FightC) sendLegacyRoundBroadcast(firstAttack, secondAttack *action.SelectSkillAction) {
if f == nil || !f.LegacyGroupProtocol {
return
}
if firstAttack != nil {
f.sendLegacyGroupSkillHurt(firstAttack)
}
if secondAttack != nil {
f.sendLegacyGroupSkillHurt(secondAttack)
}
f.sendLegacyGroupBoutDone()
}
func (f *FightC) sendLegacyGroupSkillHurt(skillAction *action.SelectSkillAction) {
if f == nil || !f.LegacyGroupProtocol || skillAction == nil {
return
}
packet := f.buildLegacyGroupSkillHurtPacket(skillAction)
if packet == nil {
return
}
f.BroadcastPlayers(func(p common.PlayerI) {
p.SendPackCmd(groupCmdSkillHurt, packet)
})
}
func (f *FightC) buildLegacyGroupSkillHurtPacket(skillAction *action.SelectSkillAction) *legacyGroupSkillHurtPacket {
attacker := f.GetInputByAction(skillAction, false)
defender := f.GetInputByAction(skillAction, true)
if attacker == nil || defender == nil {
return nil
}
return &legacyGroupSkillHurtPacket{
IsGank: 0,
Attack: f.buildLegacyGroupSkillAttackInfo(skillAction, attacker),
Attacked: f.buildLegacyGroupSkillDefendInfo(defender),
}
}
func (f *FightC) fillLegacyGroupSkillCommonFields(
self *input.Input,
isAttackor uint8,
statusList *[20]uint8,
batLvList *[6]uint8,
) (side uint8, pos uint8, userID uint32, petID uint32, hp uint32, maxHP uint32, moveMap []legacyGroupSkillMoveInfo, flag uint32) {
if self == nil {
return
}
if !f.isOurPlayerID(self.UserID) {
side = 2
} else {
side = 1
}
pos = uint8(self.TeamSlotIndex())
userID = self.UserID
attackValue := self.AttackValue
if attackValue == nil {
attackValue = info.NewAttackValue(self.UserID)
}
for i := 0; i < len(attackValue.Status) && i < 20; i++ {
statusList[i] = uint8(attackValue.Status[i])
}
for i := 0; i < len(attackValue.Prop) && i < len(batLvList); i++ {
batLvList[i] = uint8(attackValue.Prop[i])
}
currentPet := self.CurrentPet()
if currentPet != nil {
petID = currentPet.Info.ID
hp = currentPet.Info.Hp
maxHP = currentPet.Info.MaxHp
moveMap = collectLegacyGroupSkillMoves(currentPet.Info.SkillList)
} else {
hp = clampLegacyInt32ToUint32(attackValue.RemainHp)
maxHP = attackValue.MaxHp
moveMap = collectLegacyGroupSkillMoves(attackValue.SkillList)
}
flag = attackValue.State
return
}
func (f *FightC) buildLegacyGroupSkillAttackInfo(skillAction *action.SelectSkillAction, self *input.Input) legacyGroupSkillAttackInfo {
result := legacyGroupSkillAttackInfo{}
if self == nil {
return result
}
result.IsAttackor = 0
result.Side, result.Pos, result.UserID, result.PetID, result.Hp, result.MaxHp, result.MoveMap, result.Flag =
f.fillLegacyGroupSkillCommonFields(self, result.IsAttackor, &result.StatusList, &result.BatLvList)
attackValue := self.AttackValue
if attackValue == nil {
attackValue = info.NewAttackValue(self.UserID)
}
if skillAction != nil && skillAction.SkillEntity != nil {
result.MoveID = uint32(skillAction.SkillEntity.XML.ID)
} else {
result.MoveID = attackValue.SkillID
}
result.IsCrit = attackValue.IsCritical
result.EffectName = attackValue.State
result.AtkTimes = 1
result.Dmg = int32(attackValue.LostHp)
result.ChgHp = attackValue.GainHp
return result
}
func (f *FightC) buildLegacyGroupSkillDefendInfo(self *input.Input) legacyGroupSkillDefendInfo {
result := legacyGroupSkillDefendInfo{}
if self == nil {
return result
}
result.IsAttackor = 1
result.Side, result.Pos, result.UserID, result.PetID, result.Hp, result.MaxHp, result.MoveMap, result.Flag =
f.fillLegacyGroupSkillCommonFields(self, result.IsAttackor, &result.StatusList, &result.BatLvList)
result.MoveID = 0
return result
}
func collectLegacyGroupSkillMoves(skills []model.SkillInfo) []legacyGroupSkillMoveInfo {
moves := make([]legacyGroupSkillMoveInfo, 0, len(skills))
for _, skill := range skills {
if skill.ID == 0 {
continue
}
moves = append(moves, legacyGroupSkillMoveInfo{
MoveID: skill.ID,
PP: skill.PP,
})
}
return moves
}
func clampLegacyInt32ToUint32(v int32) uint32 {
if v < 0 {
return 0
}
return uint32(v)
}
func (f *FightC) sendLegacyGroupBoutDone() {
if f == nil || !f.LegacyGroupProtocol {
return
}
payload := legacyBoutDoneInfo{Round: f.Round}
f.BroadcastPlayers(func(p common.PlayerI) {
p.SendPackCmd(groupCmdBoutDone, &payload)
})
}
func (f *FightC) sendLegacySpriteDie(in *input.Input, hasBackup bool) {
if f == nil || !f.LegacyGroupProtocol || in == nil {
return
}
side := uint8(1)
if !f.isOurPlayerID(in.UserID) {
side = 2
}
var data uint32
if hasBackup {
data = 1
}
payload := legacySpriteDieInfo{
Count: 1,
Side: side,
ActorIndex: uint8(in.TeamSlotIndex()),
Flag: 1,
HasBackup: data,
}
f.BroadcastPlayers(func(p common.PlayerI) {
p.SendPackCmd(groupCmdSpriteDie, &payload)
})
}