package player import ( "blazing/common/data" "blazing/common/socket/errorcode" "blazing/cool" "blazing/logic/service/common" "blazing/logic/service/fight/info" "blazing/logic/service/space" "fmt" "sync/atomic" "time" "blazing/modules/base/service" config "blazing/modules/config/service" dictrvice "blazing/modules/dict/service" blservice "blazing/modules/player/service" "context" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" csmap "github.com/mhmtszr/concurrent-swiss-map" "github.com/panjf2000/gnet/v2" ) // Mainplayer 全局玩家数据存储映射 var Mainplayer = csmap.New[uint32, *Player]() type OgrePetInfo struct { Id uint32 ShinyLen uint32 `json:"-" struc:"sizeof=ShinyInfo"` ShinyInfo []data.GlowFilter `json:"ShinyInfo,omitempty"` Lv uint32 `struc:"skip"` //等级 Item uint32 `struc:"skip"` //奖励,如果有的话 Ext uint32 `struc:"skip"` //是否变尼尔尼奥 } func (o *OgrePetInfo) RandSHiny() { var co *data.GlowFilter if o.Ext == 0 { co = config.NewShinyService().RandShiny(o.Id) } if co != nil && len(o.ShinyInfo) == 0 { o.ShinyInfo = append(o.ShinyInfo, *co) } } func (o *OgrePetInfo) FixSHiny() { var co *data.GlowFilter if o.Ext == 0 { co = config.NewShinyService().FixShiny(o.Id) } if co != nil && len(o.ShinyInfo) == 0 { o.ShinyInfo = append(o.ShinyInfo, *co) } } type Player struct { MainConn gnet.Conn baseplayer IsLogin bool //是否登录 Done MapNPC *time.Timer context.Context Fightinfo info.Fightinfo // 当前邀请的玩家ID Logintime uint32 // 当前登录时间 OgreInfo OgrePet Service *blservice.UserService User *service.BaseSysUserService // PVP被邀请信息 HavePVPinfo []common.PlayerI monsters [3]int // 0 无,1可以刷怪,2是切换过地图 Canmon uint32 // 可以刷怪 CurDark uint32 } type OgrePet struct { Data [9]OgrePetInfo } // PlayerOption 定义配置 Player 的函数类型 type PlayerOption func(*Player) // WithConn 设置玩家连接的配置选项 func WithConn(c gnet.Conn) PlayerOption { return func(p *Player) { p.MainConn = c } } func (p *Player) GetCoins(amount uint32) bool { if p.Info.Coins < amount { return false } return true } func (p *Player) UseGold(amount uint32) bool { if p.User.GetGold(uint(p.Info.UserID)) < amount { return false } return true } func (p *Player) GetAction() { } // InvitePlayer 邀请玩家进行对战 func (f *Player) InvitePlayer(ff common.PlayerI) { f.HavePVPinfo = append(f.HavePVPinfo, ff) tt := common.NewTomeeHeader(2501, f.GetInfo().UserID) f.SendPack(tt.Pack(&info.NoteInviteToFightOutboundInfo{ UserID: ff.GetInfo().UserID, Nick: ff.GetInfo().Nick, Mode: ff.Getfightinfo().Mode, })) } // Getfightinfo 获取玩家的战斗信息 func (p *Player) Getfightinfo() info.Fightinfo { return p.Fightinfo } // QuitFight 退出战斗 func (p *Player) QuitFight() { p.FightC = nil atomic.StoreUint32(&p.Fightinfo.Mode, 0) } // GetSpace 获取玩家所在的空间 func (p *Player) GetSpace() *space.Space { return space.GetSpace(p.Info.MapID) } // CanFight 检查玩家是否可以进行战斗 // 0无战斗,1PVP,2,BOOS,3PVE func (p *Player) CanFight() bool { if len(p.Info.PetList) == 0 { atomic.StoreUint32(&p.Fightinfo.Mode, 0) return false } if p.FightC != nil { atomic.StoreUint32(&p.Fightinfo.Mode, 0) return false } for _, pet := range p.Info.PetList { if pet.Hp > 0 { // 只要找到一个血量大于0的宠物,就可以战斗 return true } } // 遍历完所有宠物,都没有血量大于0的,才不能战斗 atomic.StoreUint32(&p.Fightinfo.Mode, 0) return false } func (p *Player) SendPack(b []byte) error { if p.MainConn == nil { return nil } psocket, ok := p.MainConn.Context().(*ClientData) if ok { return psocket.SendPack(b) } return nil } // 添加物品 返回成功添加的物品 func (p *Player) ItemAdd(ItemId, ItemCnt uint32) (result bool) { switch ItemId { case 1: //塞尔豆 p.Info.Coins = p.Info.Coins + ItemCnt return true case 3: //累计经验 p.Info.ExpPool = p.Info.ExpPool + ItemCnt return true case 5: //金豆ItemAdd p.User.UpdateGold(p.Info.UserID, int64(ItemCnt*100)) return true case 9: //学习力 p.Info.EVPool = p.Info.EVPool + ItemCnt default: itemmax := dictrvice.NewDictInfoService().GetMax(ItemId) if itemmax == 0 { cool.Logger.Error(context.TODO(), "物品不存在", p.Info.UserID, ItemId) t1 := common.NewTomeeHeader(2601, p.Info.UserID) t1.Result = uint32(errorcode.ErrorCodes.ErrSystemError200007) p.SendPack(t1.Pack(nil)) //准备包由各自发,因为协议不一样 return false } if p.Service.Item.CheakItem(ItemId)+int32(ItemCnt) > int32(itemmax) { println(p.Info.UserID, "物品超过拥有最大限制", ItemId) t1 := common.NewTomeeHeader(2601, p.Info.UserID) t1.Result = uint32(errorcode.ErrorCodes.ErrTooManyOfItem) p.SendPack(t1.Pack(nil)) //准备包由各自发,因为协议不一样 return false } p.Service.Item.UPDATE(ItemId, gconv.Int(ItemCnt)) return true } return false } // Kick 是否热更退出 func (player1 *Player) Kick(isquit bool) { if player1.Info == nil { return } head := common.NewTomeeHeader(1001, player1.Info.UserID) head.Result = uint32(errorcode.ErrorCodes.ErrAccountLoggedInElsewhere) if isquit { head.Result = uint32(errorcode.ErrorCodes.ErrXinPlanSleepMode) } // 实际上这里有个问题,会造成重复保存问题 player1.SendPack(head.Pack(nil)) CloseChan := make(chan struct{}) player1.MainConn.CloseWithCallback(func(c gnet.Conn, err error) error { close(CloseChan) return nil }) // --- 新增超时机制核心代码 --- // 设定超时时间(可根据业务需求调整,这里以3秒为例) const kickTimeout = 5 * time.Second select { case <-CloseChan: // 正常流程:连接关闭回调已执行,CloseChan 被关闭 case <-time.After(kickTimeout): service.NewBaseSysLogService().RecordKick(uint32(player1.Info.UserID), fmt.Errorf("踢人操作超时(超时时间:%v)", kickTimeout).Error()) // 超时处理:避免永久阻塞,可添加日志便于排查问题 // 注意:这里不会中断 CloseWithCallback 的执行,仅解除当前协程的阻塞 } } // 1是延迟踢人,0是强制踢人 func (player1 *Player) KickMessage() { if player1.Info == nil { return } // 取成功,否则创建 // player1.Save() //先保存数据再返回 head := common.NewTomeeHeader(1001, player1.Info.UserID) head.Result = uint32(errorcode.ErrorCodes.ErrXinPlanSleepMode) // 实际上这里有个问题,会造成重复保存问题 player1.SendPack(head.Pack(nil)) } func (p *Player) Cheak(b error) { if b != nil { g.Log().Error(context.Background(), "出现错误", p.Info.UserID, b.Error()) } } func (p *Player) GiveTitle(id uint32) { p.Service.Title.Give(id) p.SendPackCmd(50005, &info.S2C_50005{ Title: id, }) }