- 在多个战斗控制器方法中添加 defer 调用,确保战斗操作正确延迟执行 - 修改 ChangePet 方法返回值类型,增强接口一致性 - 修复战斗准备阶段逻辑,重构战斗开始信息构建过程 - 移除冗余广播调用,调整 PVE 战斗初始化流程 - 更新 README 中的 pprof 命令地址并完善项目介绍部分 fix(effect): 修复效果叠加逻辑与ID解析问题 - 效果叠加时默认增加一层,而非直接相加参数 - 修正 EffectIDCombiner 类型、CatchTime 的掩码偏移计算错误 - 添加重复效果日志输出,便于调试追踪 feat(boss): 完善BOSS特性实现逻辑 - 修正 NewSel17 特性
346 lines
8.3 KiB
Go
346 lines
8.3 KiB
Go
package fight
|
||
|
||
import (
|
||
"blazing/common/data/xmlres"
|
||
"blazing/common/socket/errorcode"
|
||
"blazing/common/utils"
|
||
"blazing/cool"
|
||
"blazing/modules/blazing/model"
|
||
"fmt"
|
||
|
||
"blazing/logic/service/common"
|
||
"blazing/logic/service/fight/action"
|
||
"blazing/logic/service/fight/info"
|
||
"blazing/logic/service/fight/input"
|
||
"blazing/logic/service/player"
|
||
"blazing/logic/service/user"
|
||
"math/rand"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/gogf/gf/v2/util/grand"
|
||
"github.com/jinzhu/copier"
|
||
)
|
||
|
||
type FightC struct {
|
||
//准备战斗信息
|
||
ReadyInfo info.NoteReadyToFightInfo
|
||
//开始战斗信息
|
||
info.FightStartOutboundInfo
|
||
Info info.Fightinfo
|
||
IsReady bool
|
||
ownerID uint32 // 战斗发起者ID
|
||
Our *input.Input //始终等于房主ID
|
||
Opp *input.Input //对手ID
|
||
Switch []*action.ActiveSwitchAction
|
||
startl sync.Once
|
||
rand *rand.Rand
|
||
StartTime time.Time
|
||
actionChan chan action.BattleActionI // 所有操作统一从这里进入
|
||
Round int //回合数
|
||
quit chan struct{}
|
||
over chan struct{}
|
||
First *input.Input
|
||
Second *input.Input
|
||
closefight bool
|
||
overl sync.Once
|
||
waittime int
|
||
info.FightOverInfo
|
||
//战斗结束的插装
|
||
callback func(*info.FightOverInfo)
|
||
}
|
||
|
||
func (f *FightC) Ownerid() uint32 {
|
||
|
||
return f.ownerID
|
||
}
|
||
func (f *FightC) GetInputByPlayer(c common.PlayerI, isOpposite bool) *input.Input {
|
||
// 判断当前玩家是否为我方玩家
|
||
isOurPlayer := c.GetInfo().UserID == f.ownerID
|
||
|
||
// 当isOurPlayer与isOpposite值不同时返回我方,相同时返回对方
|
||
if isOurPlayer != isOpposite {
|
||
return f.Our
|
||
}
|
||
return f.Opp
|
||
}
|
||
|
||
func (f *FightC) GetInputByAction(c action.BattleActionI, isOpposite bool) *input.Input {
|
||
// 判断动作所属玩家是否为我方
|
||
isOurAction := c.GetPlayerID() == f.Our.Player.GetInfo().UserID
|
||
|
||
// 根据isOpposite决定是否返回相反方向的输入
|
||
if isOurAction == !isOpposite {
|
||
return f.Our
|
||
}
|
||
return f.Opp
|
||
}
|
||
|
||
// 玩家使用技能
|
||
func (f *FightC) GetCurrPET(c common.PlayerI) *info.BattlePetEntity {
|
||
if f.Our.Player.GetInfo().UserID == c.GetInfo().UserID {
|
||
return f.Our.CurrentPet
|
||
} else {
|
||
return f.Opp.CurrentPet
|
||
}
|
||
|
||
}
|
||
func (f *FightC) GetOpp(c common.PlayerI) *input.Input {
|
||
return f.GetInputByPlayer(c, true)
|
||
}
|
||
|
||
// 获取随机数
|
||
func (f *FightC) GetRand() *rand.Rand {
|
||
|
||
return f.rand
|
||
|
||
}
|
||
|
||
// 获取随机数
|
||
func (f *FightC) IsFirst(play common.PlayerI) bool {
|
||
|
||
return f.First.Player == play
|
||
}
|
||
func (f *FightC) Chat(c common.PlayerI, msg string) {
|
||
|
||
f.GetInputByPlayer(c, true).Player.SendPackCmd(50002, &user.ChatOutboundInfo{
|
||
SenderId: c.GetInfo().UserID,
|
||
SenderNickname: c.GetInfo().Nick,
|
||
Message: utils.RemoveLast(msg),
|
||
})
|
||
}
|
||
|
||
// 加载进度
|
||
func (f *FightC) LoadPercent(c common.PlayerI, percent int32) {
|
||
if f.Info.Mode == info.BattleMode.PET_MELEE {
|
||
return
|
||
}
|
||
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
|
||
return
|
||
}
|
||
f.GetInputByPlayer(c, true).Player.SendPackCmd(2441, &info.LoadPercentOutboundInfo{
|
||
Id: c.GetInfo().UserID,
|
||
Percent: uint32(percent),
|
||
})
|
||
|
||
}
|
||
|
||
var meetpet = make(map[int]model.PetInfo)
|
||
|
||
func initmeetpet() {
|
||
meetpet = make(map[int]model.PetInfo)
|
||
for i, v := range xmlres.PetMAP {
|
||
if v.EvolvesTo == 0 && v.ID < 2000 {
|
||
r := model.GenPetInfo(int(v.ID), 24, -1, -1, -1, 100)
|
||
meetpet[i] = *r
|
||
}
|
||
|
||
}
|
||
}
|
||
func (f *FightC) initplayer(c common.PlayerI) (*input.Input, errorcode.ErrorCode) {
|
||
|
||
if !c.CanFight() {
|
||
return nil, errorcode.ErrorCodes.ErrNoEligiblePokemon
|
||
}
|
||
|
||
in := input.NewInput(f, c)
|
||
in.AllPet = make([]*info.BattlePetEntity, 0)
|
||
in.InitAttackValue()
|
||
for i := 0; i < len(c.GetInfo().PetList); i++ {
|
||
in.AllPet = append(in.AllPet, info.CreateBattlePetEntity(c.GetInfo().PetList[i], f.rand))
|
||
|
||
}
|
||
|
||
in.SortPet()
|
||
|
||
switch f.Info.Mode {
|
||
case info.BattleMode.SINGLE_MODE:
|
||
in.AllPet = in.AllPet[:1]
|
||
case info.BattleMode.PET_MELEE:
|
||
in.AllPet = make([]*info.BattlePetEntity, 0)
|
||
if len(meetpet) == 0 {
|
||
initmeetpet()
|
||
}
|
||
for i, v := range meetpet {
|
||
if len(in.AllPet) > 2 {
|
||
break
|
||
}
|
||
|
||
v.CatchTime = uint32(i)
|
||
in.AllPet = append(in.AllPet, info.CreateBattlePetEntity(v, f.rand))
|
||
}
|
||
|
||
//in.AllPet = in.AllPet[:3]
|
||
default:
|
||
}
|
||
|
||
in.CurrentPet = in.AllPet[0]
|
||
return in, 0
|
||
}
|
||
|
||
// RandomElfIDs 从1-2000中随机抽取n个不重复的精灵ID
|
||
func RandomElfIDs(n int) []int {
|
||
if n <= 0 || n > 2000 {
|
||
return nil
|
||
}
|
||
|
||
// 用map记录已抽取的ID,避免重复
|
||
used := make(map[int]struct{}, n)
|
||
ids := make([]int, 0, n)
|
||
|
||
for len(ids) < n {
|
||
// 生成1-2000的随机数
|
||
id := grand.Intn(2000) + 1 // rand.Intn(2000)生成0-1999,+1后为1-2000
|
||
|
||
// 检查是否已抽取
|
||
if _, exists := used[id]; !exists {
|
||
used[id] = struct{}{}
|
||
ids = append(ids, id)
|
||
}
|
||
}
|
||
|
||
return ids
|
||
}
|
||
func initfightready(in *input.Input) (info.FightUserInfo, []info.ReadyFightPetInfo) {
|
||
t := make([]info.ReadyFightPetInfo, len(in.AllPet))
|
||
userindo := info.FightUserInfo{
|
||
UserID: in.UserID,
|
||
Nick: in.Player.GetInfo().Nick,
|
||
}
|
||
|
||
for i := 0; i < len(in.AllPet); i++ {
|
||
|
||
err := copier.CopyWithOption(&t[i], &in.AllPet[i].Info, copier.Option{IgnoreEmpty: true, DeepCopy: true})
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
}
|
||
|
||
return userindo, t
|
||
}
|
||
|
||
// 创建新战斗,邀请方和被邀请方,或者玩家和野怪方
|
||
func NewFight(p1, p2 common.PlayerI, fn func(*info.FightOverInfo)) (*FightC, errorcode.ErrorCode) {
|
||
|
||
fmt.Println("NewFight", p1.GetInfo().UserID)
|
||
f := &FightC{}
|
||
f.ownerID = p1.GetInfo().UserID
|
||
// 1. 构建战斗开始信息(整理双方初始宠物信息)
|
||
|
||
f.callback = fn //战斗结束的回调
|
||
f.quit = make(chan struct{})
|
||
f.over = make(chan struct{})
|
||
f.StartTime = time.Now()
|
||
seed := f.StartTime.UnixNano() ^ int64(p1.GetInfo().UserID) ^ int64(p2.GetInfo().UserID) // ^ int64(f.Round) // 用异或运算混合多维度信息
|
||
f.rand = rand.New(rand.NewSource(seed))
|
||
f.Info = p1.Getfightinfo()
|
||
|
||
//这里应该挪到玩家初始化执行
|
||
|
||
f.ReadyInfo.Status = f.Info.Status
|
||
var err errorcode.ErrorCode
|
||
f.Our, err = f.initplayer(p1)
|
||
if err > 0 {
|
||
return nil, err
|
||
|
||
}
|
||
|
||
f.Opp, err = f.initplayer(p2)
|
||
if err > 0 {
|
||
return nil, err
|
||
|
||
}
|
||
|
||
f.ReadyInfo.OurInfo, f.ReadyInfo.OurPetList = initfightready(f.Our)
|
||
f.ReadyInfo.OpponentInfo, f.ReadyInfo.OpponentPetList = initfightready(f.Opp)
|
||
var loadtime time.Duration = 120 * time.Second
|
||
//说明是PVE
|
||
|
||
f.Broadcast(func(ff *input.Input) {
|
||
ff.SetOPP(f.GetInputByPlayer(ff.Player, true))
|
||
|
||
})
|
||
f.FightStartOutboundInfo = f.buildFightStartInfo()
|
||
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
|
||
|
||
f.Opp.Finished = true //PVE 默认boss数据直接加载完成
|
||
loadtime = 60 * time.Second
|
||
//f.handleNPCFightSpecial(&fightStartInfo)
|
||
|
||
if f.Opp.Player.(*player.AI_player).CanCapture > 0 {
|
||
f.Opp.CanCapture = f.Opp.Player.(*player.AI_player).CanCapture
|
||
f.FightStartOutboundInfo.Info2.Catchable = 1 //可以捕捉就置1
|
||
}
|
||
f.Opp.AttackValue.Prop = f.Opp.Player.(*player.AI_player).Prop
|
||
f.FightStartOutboundInfo.Info2.Prop = f.Opp.AttackValue.Prop
|
||
}
|
||
|
||
f.Broadcast(func(ff *input.Input) {
|
||
//ff.InitEFFect(&f.FightStartOutboundInfo)
|
||
ff.Player.SendPackCmd(2503, &f.ReadyInfo)
|
||
|
||
})
|
||
|
||
cool.Cron.AfterFunc(loadtime, func() {
|
||
fmt.Println(f.Our.UserID, "战斗超时结算")
|
||
if !f.Our.Finished || !f.Opp.Finished { //如果有任一没有加载完成
|
||
f.closefight = true //阻止继续添加action
|
||
f.Reason = info.BattleOverReason.PlayerOffline
|
||
switch {
|
||
case !f.Opp.Finished: //邀请方没加载完成 先判断邀请方,如果都没加载完成,就算做房主胜利
|
||
f.WinnerId = f.Our.Player.GetInfo().UserID
|
||
case !f.Our.Finished: //被邀请方没加载完成
|
||
f.WinnerId = f.Opp.Player.GetInfo().UserID
|
||
}
|
||
f.Broadcast(func(ff *input.Input) {
|
||
//todo 将血量和技能pp传回enterturn
|
||
|
||
ff.Player.SendPackCmd(2506, &f.FightOverInfo)
|
||
ff.Player.QuitFight()
|
||
})
|
||
}
|
||
|
||
})
|
||
|
||
return f, 0
|
||
}
|
||
|
||
// 被击败的ID
|
||
func (b *FightC) IsWin(c *input.Input, cache uint32) bool {
|
||
|
||
var tt []*info.BattlePetEntity
|
||
bbb := b.Our.AllPet
|
||
|
||
if c.Player.GetInfo().UserID == b.ownerID { //如果是房主
|
||
bbb = b.Opp.AllPet
|
||
}
|
||
|
||
for _, v := range bbb {
|
||
if v.Info.CatchTime == cache {
|
||
v.NotAlive = true
|
||
|
||
}
|
||
tt = append(tt, v)
|
||
}
|
||
|
||
for _, v := range tt {
|
||
if !v.NotAlive { //如果存活
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
// 广播,并是否结束回合
|
||
func (f *FightC) Broadcast(t func(ff *input.Input)) {
|
||
|
||
t(f.Our)
|
||
t(f.Opp)
|
||
|
||
}
|
||
|
||
func (f *FightC) GetOverChan() chan struct{} {
|
||
return f.over
|
||
|
||
}
|