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), ) } // ArrangePetsBySlotLimit 按站位上限切分宠物。 // 规则: // 1. 前 slotLimit 只存活宠物优先占据出战位。 // 2. 其余宠物按 1..slotLimit 轮转挂到对应站位作为后备。 // 3. 每个站位最多保留 6 只宠物。 func ArrangePetsBySlotLimit(pets []model.PetInfo, slotLimit int) [][]model.PetInfo { var ( alivePets []model.PetInfo slots [][]model.PetInfo idx int ) for _, pet := range pets { if pet.Hp == 0 { continue } alivePets = append(alivePets, pet) } if len(alivePets) == 0 { return nil } if slotLimit <= 0 { slotLimit = 1 } if slotLimit > len(alivePets) { slotLimit = len(alivePets) } slots = make([][]model.PetInfo, 0, slotLimit) for i := 0; i < slotLimit; i++ { slots = append(slots, []model.PetInfo{alivePets[i]}) } for _, pet := range alivePets[slotLimit:] { for step := 0; step < len(slots); step++ { slotIdx := (idx + step) % len(slots) if len(slots[slotIdx]) >= 6 { continue } slots[slotIdx] = append(slots[slotIdx], pet) idx = (slotIdx + 1) % len(slots) break } } return slots } // ExpandPlayersWithSlotLimit 将“每位玩家的宠物列表”按站位限制展开为扁平站位列表。 // 例如: // 1. slotLimit=1: 每位玩家占 1 个站位,其余为该站位后备。 // 2. slotLimit=3: 每位玩家最多展开 3 个站位,每个站位带各自后备。 func ExpandPlayersWithSlotLimit( players []common.PlayerI, petsByPlayer [][]model.PetInfo, slotLimit int, ) ([]common.PlayerI, [][]model.PetInfo, errorcode.ErrorCode) { var ( flatPlayers []common.PlayerI flatSlots [][]model.PetInfo ) if len(players) == 0 || len(players) != len(petsByPlayer) { return nil, nil, errorcode.ErrorCodes.ErrSystemBusyTryLater } for idx, p := range players { if p == nil { return nil, nil, errorcode.ErrorCodes.ErrSystemBusyTryLater } slots := ArrangePetsBySlotLimit(petsByPlayer[idx], slotLimit) for _, slotPets := range slots { flatPlayers = append(flatPlayers, p) flatSlots = append(flatSlots, slotPets) } } if len(flatPlayers) == 0 || len(flatPlayers) != len(flatSlots) { return nil, nil, errorcode.ErrorCodes.ErrSystemBusyTryLater } return flatPlayers, flatSlots, 0 } // NewFightSingleController 使用站位限制规则创建单人控制多站位战斗。 func NewFightSingleController( ourController common.PlayerI, oppController common.PlayerI, ourPets []model.PetInfo, oppPets []model.PetInfo, slotLimit int, fn func(model.FightOverInfo), ) (*FightC, errorcode.ErrorCode) { return NewFightSingleControllerN( ourController, oppController, ArrangePetsBySlotLimit(ourPets, slotLimit), ArrangePetsBySlotLimit(oppPets, slotLimit), fn, ) } // 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), ) } // NewLegacyGroupFightSingleController 使用站位限制规则创建旧组队协议战斗。 func NewLegacyGroupFightSingleController( ourController common.PlayerI, oppController common.PlayerI, ourPets []model.PetInfo, oppPets []model.PetInfo, slotLimit int, fn func(model.FightOverInfo), ) (*FightC, errorcode.ErrorCode) { return NewLegacyGroupFightSingleControllerN( ourController, oppController, ArrangePetsBySlotLimit(ourPets, slotLimit), ArrangePetsBySlotLimit(oppPets, slotLimit), fn, ) } // 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), ) } // NewFightPerPlayerControllers 使用“每位玩家 + 站位限制”创建多人战斗。 func NewFightPerPlayerControllers( ourPlayers []common.PlayerI, oppPlayers []common.PlayerI, ourPetsByPlayer [][]model.PetInfo, oppPetsByPlayer [][]model.PetInfo, slotLimit int, fn func(model.FightOverInfo), ) (*FightC, errorcode.ErrorCode) { flatOurPlayers, flatOurSlots, err := ExpandPlayersWithSlotLimit(ourPlayers, ourPetsByPlayer, slotLimit) if err > 0 { return nil, err } flatOppPlayers, flatOppSlots, err := ExpandPlayersWithSlotLimit(oppPlayers, oppPetsByPlayer, slotLimit) if err > 0 { return nil, err } return NewFightPerSlotControllerN(flatOurPlayers, flatOppPlayers, flatOurSlots, flatOppSlots, fn) } // 创建新战斗,邀请方和被邀请方,或者玩家和野怪方 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 } return NewFightSingleController(p1, p2, b1, b2, 1, fn) } // 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() f.BroadcastPlayers(func(p common.PlayerI) { if f.LegacyGroupProtocol { f.sendLegacyGroupReady(p) return } f.sendFightPacket(p, fightPacketReady, &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.BroadcastPlayers(func(p common.PlayerI) { if f.LegacyGroupProtocol { f.sendLegacyGroupOver(p, &f.FightOverInfo) } else { f.sendFightPacket(p, fightPacketOver, &f.FightOverInfo) } p.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 }