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

- 实现了旧组队协议相关功能,包括GroupReadyFightFinish、GroupUseSkill、
  GroupUseItem、GroupChangePet和GroupEscape方法
- 新增组队战斗相关的入站信息结构体定义
- 实现了组队BOSS战斗逻辑,添加groupBossSlotLimit常量
- 重构宠物技能设置逻辑,调整金币消耗时机
- 优化战斗循环逻辑,添加对无行动槽位的处理
- 改进AI行动逻辑,增加多位置目标选择
2026-04-08 01:28:55 +08:00

327 lines
9.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package fight
import (
"blazing/common/socket/errorcode"
"blazing/cool"
"blazing/logic/service/common"
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/player"
"blazing/modules/player/model"
"time"
)
// NewFightSingleControllerN 创建 N 打战斗(单人控制多站位)。
// ourPetsBySlot/oppPetsBySlot 的每个元素代表一个站位携带的宠物列表。
func NewFightSingleControllerN(
ourController common.PlayerI,
oppController common.PlayerI,
ourPetsBySlot [][]model.PetInfo,
oppPetsBySlot [][]model.PetInfo,
fn func(model.FightOverInfo),
) (*FightC, errorcode.ErrorCode) {
if ourController == nil || oppController == nil {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
fightInfo := ourController.Getfightinfo()
ourInputs, err := buildSideInputsByController(ourController, ourPetsBySlot, fightInfo.Mode)
if err > 0 {
return nil, err
}
oppInputs, err := buildSideInputsByController(oppController, oppPetsBySlot, fightInfo.Mode)
if err > 0 {
return nil, err
}
return NewFightWithOptions(
WithFightInputs(ourInputs, oppInputs),
WithFightPlayersOnSide(
[]common.PlayerI{ourController},
[]common.PlayerI{oppController},
),
WithInputControllerBinding(InputControllerBindingSingle),
WithFightCallback(fn),
WithFightInfo(fightInfo),
)
}
// NewLegacyGroupFightSingleControllerN 创建旧组队协议的单人控制多站位战斗。
func NewLegacyGroupFightSingleControllerN(
ourController common.PlayerI,
oppController common.PlayerI,
ourPetsBySlot [][]model.PetInfo,
oppPetsBySlot [][]model.PetInfo,
fn func(model.FightOverInfo),
) (*FightC, errorcode.ErrorCode) {
if ourController == nil || oppController == nil {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
fightInfo := ourController.Getfightinfo()
ourInputs, err := buildSideInputsByController(ourController, ourPetsBySlot, fightInfo.Mode)
if err > 0 {
return nil, err
}
oppInputs, err := buildSideInputsByController(oppController, oppPetsBySlot, fightInfo.Mode)
if err > 0 {
return nil, err
}
return NewFightWithOptions(
WithFightInputs(ourInputs, oppInputs),
WithFightPlayersOnSide(
[]common.PlayerI{ourController},
[]common.PlayerI{oppController},
),
WithInputControllerBinding(InputControllerBindingSingle),
WithLegacyGroupProtocol(true),
WithFightCallback(fn),
WithFightInfo(fightInfo),
)
}
// NewFightPerSlotControllerN 创建 N 打战斗(多人各控制一个站位)。
// ourPlayers/oppPlayers 与 ourPetsBySlot/oppPetsBySlot 按站位一一对应。
func NewFightPerSlotControllerN(
ourPlayers []common.PlayerI,
oppPlayers []common.PlayerI,
ourPetsBySlot [][]model.PetInfo,
oppPetsBySlot [][]model.PetInfo,
fn func(model.FightOverInfo),
) (*FightC, errorcode.ErrorCode) {
if len(ourPlayers) == 0 || len(oppPlayers) == 0 {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
if len(ourPlayers) != len(ourPetsBySlot) || len(oppPlayers) != len(oppPetsBySlot) {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
fightInfo := ourPlayers[0].Getfightinfo()
ourInputs, err := buildSideInputsByPlayers(ourPlayers, ourPetsBySlot, fightInfo.Mode)
if err > 0 {
return nil, err
}
oppInputs, err := buildSideInputsByPlayers(oppPlayers, oppPetsBySlot, fightInfo.Mode)
if err > 0 {
return nil, err
}
return NewFightWithOptions(
WithFightInputs(ourInputs, oppInputs),
WithFightPlayersOnSide(ourPlayers, oppPlayers),
WithInputControllerBinding(InputControllerBindingPerSlot),
WithFightCallback(fn),
WithFightInfo(fightInfo),
)
}
// 创建新战斗,邀请方和被邀请方,或者玩家和野怪方
func NewFight(p1, p2 common.PlayerI, b1, b2 []model.PetInfo, fn func(model.FightOverInfo)) (*FightC, errorcode.ErrorCode) {
if p1 == nil || p2 == nil {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
fightInfo := p1.Getfightinfo()
ourInput, err := buildInputFromPets(p1, b1, fightInfo.Mode)
if err > 0 {
return nil, err
}
oppInput, err := buildInputFromPets(p2, b2, fightInfo.Mode)
if err > 0 {
return nil, err
}
return NewFightWithOptions(
WithFightInputs([]*input.Input{ourInput}, []*input.Input{oppInput}),
WithFightCallback(fn),
WithFightInfo(fightInfo),
)
}
// buildFight 基于已准备好的双方 Inputs 构建战斗实例。
// 约束opts.ourInputs/opts.oppInputs 必须非空。
func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) {
if opts == nil {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
opts.normalizePlayers()
if opts.owner == nil || opts.opponent == nil {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
f := &FightC{}
f.ownerID = opts.owner.GetInfo().UserID
f.LegacyGroupProtocol = opts.legacyGroupProtocol
f.OurPlayers = opts.ourPlayers
f.OppPlayers = opts.oppPlayers
f.Switch = make(map[actionSlotKey]*action.ActiveSwitchAction)
f.callback = opts.callback
f.quit = make(chan struct{})
f.over = make(chan struct{})
f.actionNotify = make(chan struct{}, 1)
f.pendingActions = make([]action.BattleActionI, 0, 4)
f.StartTime = opts.startTime
if opts.fightInfo != nil {
f.Info = *opts.fightInfo
} else {
f.Info = opts.owner.Getfightinfo()
}
f.ReadyInfo.Status = f.Info.Status
if len(opts.ourInputs) == 0 || len(opts.oppInputs) == 0 {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
f.Our = opts.ourInputs
f.Opp = opts.oppInputs
f.bindInputControllers(f.Our, f.OurPlayers, opts.controllerBinding)
f.bindInputControllers(f.Opp, f.OppPlayers, opts.controllerBinding)
f.bindInputFightContext(f.Our, f.Opp)
f.linkTeamViews()
f.ReadyInfo.OurInfo, f.ReadyInfo.OurPetList = initfightready(f.primaryOur())
f.ReadyInfo.OpponentInfo, f.ReadyInfo.OpponentPetList = initfightready(f.primaryOpp())
loadtime := 120 * time.Second
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
if opp := f.primaryOpp(); opp != nil {
opp.Finished = true
loadtime = 60 * time.Second
if ai, ok := opp.Player.(*player.AI_player); ok {
if ai.CanCapture > 0 {
opp.CanCapture = ai.CanCapture
}
ai.ApplyBattleProps(opp.AttackValue)
}
}
}
f.FightStartOutboundInfo = f.buildFightStartInfo()
if f.LegacyGroupProtocol {
f.sendLegacyGroupReady()
} else {
f.Broadcast(func(ff *input.Input) {
ff.Player.SendPackCmd(2503, &f.ReadyInfo)
})
}
cool.Cron.AfterFunc(loadtime, func() {
our := f.primaryOur()
opp := f.primaryOpp()
if our == nil || opp == nil {
return
}
if !our.Finished || !opp.Finished {
f.closefight = true
f.Reason = model.BattleOverReason.PlayerOffline
switch {
case !opp.Finished:
f.WinnerId = our.Player.GetInfo().UserID
case !our.Finished:
f.WinnerId = opp.Player.GetInfo().UserID
}
f.Broadcast(func(ff *input.Input) {
if f.LegacyGroupProtocol {
f.sendLegacyGroupOver(ff.Player, &f.FightOverInfo)
} else {
ff.Player.SendPackCmd(2506, &f.FightOverInfo)
}
ff.Player.QuitFight()
})
}
})
return f, 0
}
// bindInputControllers 按配置模式重绑站位控制者Input.Player
// Keep: 不改Single: 全部绑定 players[0]PerSlot: 按下标绑定 players[i]。
func (f *FightC) bindInputControllers(inputs []*input.Input, players []common.PlayerI, mode int) {
if len(inputs) == 0 || len(players) == 0 {
return
}
switch mode {
case InputControllerBindingSingle:
controller := players[0]
for _, in := range inputs {
if in == nil {
continue
}
in.Player = controller
}
case InputControllerBindingPerSlot:
for idx, in := range inputs {
if in == nil {
continue
}
if idx < len(players) && players[idx] != nil {
in.Player = players[idx]
continue
}
in.Player = players[0]
}
default:
// keep existing input player binding
}
}
// buildInputFromPets 根据玩家与宠物列表构建一个站位 Input。
func buildInputFromPets(c common.PlayerI, pets []model.PetInfo, mode uint32) (*input.Input, errorcode.ErrorCode) {
if c == nil {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
if r := c.CanFight(); r != 0 {
return nil, r
}
in := input.NewInput(nil, c)
in.AllPet = make([]*info.BattlePetEntity, 0, len(pets))
in.InitAttackValue()
for _, pet := range pets {
entity := info.CreateBattlePetEntity(pet)
entity.BindController(c.GetInfo().UserID)
in.AllPet = append(in.AllPet, entity)
}
in.SortPet()
if len(in.AllPet) == 0 {
return nil, errorcode.ErrorCodes.ErrNoEligiblePokemon
}
if mode == info.BattleMode.SINGLE_MODE {
in.AllPet = in.AllPet[:1]
}
in.SetCurPetAt(0, in.AllPet[0])
return in, 0
}
// buildSideInputsByController 用同一控制者构建多个站位输入(单人多站位)。
func buildSideInputsByController(controller common.PlayerI, petsBySlot [][]model.PetInfo, mode uint32) ([]*input.Input, errorcode.ErrorCode) {
if controller == nil || len(petsBySlot) == 0 {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
inputs := make([]*input.Input, 0, len(petsBySlot))
for _, slotPets := range petsBySlot {
in, err := buildInputFromPets(controller, slotPets, mode)
if err > 0 {
return nil, err
}
inputs = append(inputs, in)
}
return inputs, 0
}
// buildSideInputsByPlayers 按站位玩家一一对应构建输入(多人分站位)。
func buildSideInputsByPlayers(players []common.PlayerI, petsBySlot [][]model.PetInfo, mode uint32) ([]*input.Input, errorcode.ErrorCode) {
if len(players) == 0 || len(players) != len(petsBySlot) {
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
inputs := make([]*input.Input, 0, len(players))
for idx := range players {
in, err := buildInputFromPets(players[idx], petsBySlot[idx], mode)
if err > 0 {
return nil, err
}
inputs = append(inputs, in)
}
return inputs, 0
}