package fight import ( "blazing/common/data/xmlres" "blazing/common/socket/errorcode" "blazing/common/utils" "blazing/cool" "blazing/modules/player/model" "context" "sort" "sync/atomic" "blazing/logic/service/common" "blazing/logic/service/fight/action" "blazing/logic/service/fight/info" "blazing/logic/service/fight/input" "blazing/logic/service/player" "fmt" "time" "github.com/alpacahq/alpacadecimal" "github.com/gogf/gf/v2/util/gconv" "github.com/jinzhu/copier" ) func (f *FightC) battleLoop() { defer func() { if err := recover(); err != nil { // 恢复 panic,err 为 panic 错误值 // 1. 打印错误信息 var ctx = context.Background() cool.Logger.Error(ctx, f.ownerID, err) f.Broadcast(func(ff *input.Input) { if p, ok := ff.Player.(*player.Player); ok { head := common.NewTomeeHeader(1001, p.Info.UserID) head.Result = uint32(errorcode.ErrorCodes.ErrSystemBusyTryLater) p.SendPack(head.Pack(nil)) p.Service.Info.Save(*p.Info) } }) } }() //fmt.Println("战斗开始精灵", f.Our[0].Player.GetInfo().PetList[0].CatchTime) //fmt.Println("开始收集玩家动作", waitr) for !f.closefight { f.Round++ expectedSlots := f.expectedActionSlots() actions := f.collectPlayerActions(expectedSlots) if f.closefight { break } f.resolveRound(actions) } f.Broadcast(func(ff *input.Input) { //todo 将血量和技能pp传回enterturn ff.Exec(func(tt input.Effect) bool { tt.OnBattleEnd() tt.Alive(false) //将所有属性变化失效掉 return true }) if f.Info.Mode != info.BattleMode.PET_MELEE { //不是乱斗,传回血量 for i := 0; i < len(ff.AllPet); i++ { for j := 0; j < len(ff.Player.GetInfo().PetList); j++ { if ff.Player.GetInfo().PetList[j].CatchTime == ff.AllPet[i].Info.CatchTime { if ff.UserID == f.WinnerId { currentPet := ff.CurrentPet() if currentPet != nil && currentPet.Info.CatchTime == ff.Player.GetInfo().PetList[j].CatchTime { f.Winpet = &ff.Player.GetInfo().PetList[j] } } ff.Player.GetInfo().PetList[j].Hp = utils.Min(ff.Player.GetInfo().PetList[j].MaxHp, ff.AllPet[i].Info.Hp) ff.Player.GetInfo().PetList[j].SkillList = ff.AllPet[i].Info.SkillList } } } } }) if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC { addpet := f.Opp[0].Player.GetInfo().PetList[0] if f.Reason == model.BattleOverReason.Cacthok { f.WinnerId = f.ownerID addpet.EffectInfo = nil //清空特性信息 f.Our[0].Player.(*player.Player).Service.Pet.PetAdd(&addpet, 0) oppPet := f.Opp[0].CurrentPet() petID := uint32(0) if oppPet != nil { petID = uint32(oppPet.ID) } f.Our[0].Player.SendPackCmd(2409, &info.CatchMonsterOutboundInfo{ CatchTime: uint32(addpet.CatchTime), PetId: petID, }) defer f.Our[0].Player.(*player.Player).Service.Done.UpdatePet(addpet, 0, 1) //f.Reason = 0 //清空 } if f.Reason == 0 { defer f.Our[0].Player.(*player.Player).Service.Done.UpdatePet(addpet, 1, 0) } // f.Our[0].Player.(*player.Player).MapNPC.Reset(7 * time.Second) copier.Copy(&f.FightOverInfo, f.Our[0].Player.(*player.Player).Info) atomic.StoreUint32(&f.Our[0].Player.(*player.Player).Canmon, 2) f.Our[0].Player.(*player.Player).MapNPC.Reset(10 * time.Second) // f.Our[0].Player.(*player.Player).Info.FightTime = f.Our[0].Player.(*player.Player).Info.FightTime + time.Now().Unix() - f.StartTime.Unix() } //大乱斗,给个延迟 //<-time.After(1000) f.Broadcast(func(ff *input.Input) { ff.Player.SendPackCmd(2506, &f.FightOverInfo) ff.Player.QuitFight() //待退出玩家战斗状态 }) //f.Reason = info.BattleOverReason.PlayerCaptureSuccess //f.WinnerId = 0 //捕捉成功不算胜利 if f.callback != nil { f.callback(f.FightOverInfo) //先执行回调,再执行返回信息,在回调内修改战斗判断 } close(f.over) } // 收集玩家动作(含超时判定) func (f *FightC) collectPlayerActions(expectedSlots map[actionSlotKey]struct{}) []action.BattleActionI { actions := make(map[actionSlotKey]action.BattleActionI, len(expectedSlots)) f.openActionWindow() defer f.closeActionWindow() if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC { go f.Opp[0].GetAction() } waitr := time.Duration(f.waittime)*time.Millisecond*10 + 30*time.Second timeout := time.After(waitr) for len(actions) < len(expectedSlots) { select { case <-f.quit: f.closefight = true return flattenActionMap(actions) case <-f.actionNotify: paction := f.nextAction() if paction == nil { continue } key := actionSlotKeyFromAction(paction) if _, ok := expectedSlots[key]; !ok { continue } selfinput := f.GetInputByAction(paction, false) if selfinput == nil { continue } if ret, ok := paction.(*action.ActiveSwitchAction); ok { //正常结束可以切换,以及死切后还能再切一次 if selfinput.CanChange == 0 { currentPet := selfinput.PrimaryCurPet() if currentPet != nil && currentPet.Info.Hp > 0 { //非死亡切换 selfinput.CanChange = 1 f.Broadcast(func(ff *input.Input) { ff.Exec(func(t input.Effect) bool { t.SwitchOut(selfinput) return true }) }) } else { selfinput.CanChange = 2 } // oldpet := selfinput.CurPet[0] // InitAttackValue := *selfinput.AttackValue nextPet, reason := selfinput.GetPet(ret.Cid) if nextPet == nil { continue } selfinput.SetCurPetAt(0, nextPet) ret.Reason = reason ret.Reason.ActorIndex = uint32(ret.ActorIndex) selfinput.Player.SendPackCmd(2407, &ret.Reason) f.Switch[key] = ret selfinput.InitAttackValue() //切换精灵消除能力提升 //这时候精灵已经切换过了,可以直接给新精灵加效果 f.Broadcast(func(ff *input.Input) { ff.Exec(func(t input.Effect) bool { t.SwitchIn(selfinput) return true }) }) if selfinput.CanChange == 2 { selfinput.CanChange = 0 if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC && paction.GetPlayerID() == 0 { f.Switch = make(map[actionSlotKey]*action.ActiveSwitchAction) f.Our[0].Player.SendPackCmd(2407, &ret.Reason) //println("AI出手死切") go f.Opp[0].GetAction() //boss出手后获取出招 } continue } } else { continue } } else { // println("玩家执行释放技能动作:", pid, paction.(*action.SelectSkillAction).Info.ID) currentPet := selfinput.PrimaryCurPet() if currentPet == nil || currentPet.Info.Hp <= 0 { //0血执行非切换动作 //todo 记录异常操作 cool.Logger.Print(context.TODO(), "玩家执行了异常操作,当前精灵血量为0,不能执行非切换动作", paction.GetPlayerID()) continue } if selfinput.CanChange == 1 { //非被动死亡情况下,不能执行额外动作,0允许切,2是死亡,可以额外动作 cool.Logger.Print(context.TODO(), "玩家执行了异常操作,切换后二次释放技能,不能执行非切换动作", paction.GetPlayerID()) continue } } actions[key] = paction //fmt.Println("玩家执行动作:", pid, paction.Priority()) case <-timeout: r := f.handleTimeout(expectedSlots, actions) if r { return flattenActionMap(actions) } } } return flattenActionMap(actions) } // 超时处理逻辑 func (f *FightC) handleTimeout(expectedSlots map[actionSlotKey]struct{}, actions map[actionSlotKey]action.BattleActionI) bool { if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC { for key := range expectedSlots { if _, exists := actions[key]; exists || !f.isOurPlayerID(key.PlayerID) { continue } player := f.getPlayerByID(key.PlayerID) if player != nil { go f.UseSkillAt(player, 0, key.ActorIndex, 0) } } return false } else { missingOur := false missingOpp := false for key := range expectedSlots { if _, exists := actions[key]; exists { continue } if f.isOurPlayerID(key.PlayerID) { missingOur = true continue } missingOpp = true } switch { case missingOur && !missingOpp: if player := f.primaryOppPlayer(); player != nil { f.WinnerId = player.GetInfo().UserID } case missingOpp && !missingOur: if player := f.primaryOurPlayer(); player != nil { f.WinnerId = player.GetInfo().UserID } default: for _, act := range actions { f.WinnerId = act.GetPlayerID() break } if f.WinnerId == 0 { f.WinnerId = f.ownerID } } f.Reason = model.BattleOverReason.PlayerOVerTime f.closefight = true return true } } func flattenActionMap(actions map[actionSlotKey]action.BattleActionI) []action.BattleActionI { flattened := make([]action.BattleActionI, 0, len(actions)) for _, act := range actions { if act != nil { flattened = append(flattened, act) } } return flattened } func (f *FightC) actionSpeed(act action.BattleActionI) alpacadecimal.Decimal { if act == nil { return alpacadecimal.Zero } attacker := f.GetInputByAction(act, false) if attacker == nil { return alpacadecimal.Zero } return attacker.GetProp(4) } func (f *FightC) sortActions(actions []action.BattleActionI) { sort.SliceStable(actions, func(i, j int) bool { a, b := actions[i], actions[j] if a == nil || b == nil { return a != nil } if a.Priority() != b.Priority() { return a.Priority() > b.Priority() } if speedA, speedB := f.actionSpeed(a), f.actionSpeed(b); speedA.Cmp(speedB) != 0 { return speedA.Cmp(speedB) > 0 } if a.GetActorIndex() != b.GetActorIndex() { return a.GetActorIndex() < b.GetActorIndex() } if a.GetTargetIndex() != b.GetTargetIndex() { return a.GetTargetIndex() < b.GetTargetIndex() } return a.GetPlayerID() < b.GetPlayerID() }) } func sortActionsByActor(actions []action.BattleActionI) { sort.SliceStable(actions, func(i, j int) bool { a, b := actions[i], actions[j] if a == nil || b == nil { return a != nil } if a.GetActorIndex() != b.GetActorIndex() { return a.GetActorIndex() < b.GetActorIndex() } if a.GetTargetIndex() != b.GetTargetIndex() { return a.GetTargetIndex() < b.GetTargetIndex() } return a.GetPlayerID() < b.GetPlayerID() }) } func (f *FightC) splitRoundActions(actions []action.BattleActionI) ([]action.BattleActionI, []action.BattleActionI) { our := make([]action.BattleActionI, 0, len(actions)) opp := make([]action.BattleActionI, 0, len(actions)) for _, act := range actions { if act == nil { continue } if f.isOurPlayerID(act.GetPlayerID()) { our = append(our, act) } else { opp = append(opp, act) } } sortActionsByActor(our) sortActionsByActor(opp) return our, opp } // 根据动作类型执行一回合结算 func (f *FightC) resolveRound(actions []action.BattleActionI) { if len(actions) == 0 { cool.Logger.Debug(context.Background(), "当前回合没有可执行动作,自动跳过结算") return } ourActions, oppActions := f.splitRoundActions(actions) roundLen := len(ourActions) if len(oppActions) > roundLen { roundLen = len(oppActions) } for i := 0; i < roundLen; i++ { if f.closefight { return } var ourAct action.BattleActionI var oppAct action.BattleActionI if i < len(ourActions) { ourAct = ourActions[i] } if i < len(oppActions) { oppAct = oppActions[i] } f.resolveActionPair(ourAct, oppAct) } } func (f *FightC) resolveActionPair(p1Action, p2Action action.BattleActionI) { if p1Action == nil && p2Action == nil { return } // 动作优先级排序 b1, b2 := f.Compare(p1Action, p2Action) switch actionType := b1.(type) { case *action.ActiveSwitchAction: f.handleActiveSwitchAction(actionType, b2) case *action.UseItemAction: f.handleUseItemAction(actionType, b2) default: f.handleSkillActions(b1, b2) } } // handleActiveSwitchAction 处理主动切换精灵动作 func (f *FightC) handleActiveSwitchAction(_ *action.ActiveSwitchAction, otherAction action.BattleActionI) { if skillAction, ok := otherAction.(*action.SelectSkillAction); ok { if skillAction.SkillEntity != nil && skillAction.XML.CD != nil { f.waittime = *skillAction.XML.CD } f.enterturn(skillAction, nil) } else { f.enterturn(nil, nil) } } // handleUseItemAction 处理使用道具动作 func (f *FightC) handleUseItemAction(itemAction *action.UseItemAction, otherAction action.BattleActionI) { f.setActionAttackValue(itemAction) f.handleItemAction(itemAction) input := f.GetInputByAction(itemAction, false) if input == nil { return } if currentPet := input.PrimaryCurPet(); currentPet != nil && currentPet.Info.Hp <= 0 { currentPet.Info.Hp = 1 } if skillAction, ok := otherAction.(*action.SelectSkillAction); ok { if skillAction.SkillEntity != nil && skillAction.XML.CD != nil { f.waittime = *skillAction.XML.CD } f.enterturn(skillAction, nil) } else { if otherItemAction, ok := otherAction.(*action.UseItemAction); ok { f.handleItemAction(otherItemAction) } f.enterturn(nil, nil) } } // 使用道具的逻辑封装 func (f *FightC) handleItemAction(a *action.UseItemAction) { source := f.GetInputByAction(a, false) target := f.GetInputByAction(a, true) if source == nil { return } item, ok := xmlres.ItemsMAP[int(a.ItemID)] if !ok { return } r := source.Player.(*player.Player).Service.Item.CheakItem(uint32(a.ItemID)) if r <= 0 { return } source.Player.(*player.Player).Service.Item.UPDATE(a.ItemID, -1) switch { case gconv.Int(item.Bonus) != 0: if target != nil && target.CanCapture > 0 && target.PrimaryCurPet() != nil { //可以捕捉 target.PrimaryCurPet().CatchRate = target.CanCapture ok, _ := source.Capture(target.PrimaryCurPet(), a.ItemID, -1) our := source.Player.(*player.Player) if ok { r := input.GetFunc(int64(item.ID)) if r != nil { r.Exec(f, &target.Player.GetInfo().PetList[0]) } f.Reason = model.BattleOverReason.Cacthok f.closefight = true } else { our.SendPack(common.NewTomeeHeader(2409, f.ownerID).Pack(&info.CatchMonsterOutboundInfo{})) } } case gconv.Int(item.HP) != 0: addhp := item.HP source.Heal(source, a, alpacadecimal.NewFromInt(int64(addhp))) f.Broadcast(func(ff *input.Input) { currentPet := source.PrimaryCurPet() if currentPet == nil { return } ff.Player.SendPackCmd(2406, &info.UsePetIteminfo{ UserID: source.UserID, ChangeHp: int32(addhp), ItemID: uint32(item.ID), UserHp: uint32(currentPet.Info.Hp), }) }) case gconv.Int(item.PP) != 0: source.HealPP(item.PP) f.Broadcast(func(ff *input.Input) { currentPet := source.PrimaryCurPet() if currentPet == nil { return } ff.Player.SendPackCmd(2406, &info.UsePetIteminfo{ UserID: source.UserID, ItemID: uint32(item.ID), UserHp: uint32(currentPet.Info.Hp), }) }) default: fmt.Println(a.ItemID, "ItemID 不在指定范围内") } } // 双方都是技能时的结算逻辑 func (f *FightC) handleSkillActions(a1, a2 action.BattleActionI) { s1, _ := a1.(*action.SelectSkillAction) s2, _ := a2.(*action.SelectSkillAction) switch { case s1 == nil || s1.SkillEntity == nil: if s2.SkillEntity != nil { if s2.XML.CD != nil { f.waittime = *s2.XML.CD } } f.enterturn(s2, nil) // fmt.Println("1 空过 2玩家执行技能:", s2.PlayerID, s2.Info.ID) case s2 == nil || s2.SkillEntity == nil: if s1.SkillEntity != nil { if s1.XML.CD != nil { f.waittime = *s1.XML.CD } } f.enterturn(s1, nil) //fmt.Println("2 空过 玩家执行技能:", s1.PlayerID, s1.Info.ID) default: if s1.XML.CD != nil { f.waittime = *s1.XML.CD } if s2.XML.CD != nil { f.waittime += *s2.XML.CD } f.enterturn(s1, s2) } } // 根据玩家ID返回对应对象 func (f *FightC) getPlayerByID(id uint32) common.PlayerI { for _, player := range f.OurPlayers { if player != nil && player.GetInfo().UserID == id { return player } } for _, player := range f.OppPlayers { if player != nil && player.GetInfo().UserID == id { return player } } return nil }