package player import ( "blazing/common/data/share" "blazing/common/data/xmlres" "blazing/common/utils" "math/rand" "strings" "blazing/logic/service/common" "blazing/logic/service/fight/info" "blazing/logic/service/space" "blazing/modules/blazing/model" blservice "blazing/modules/blazing/service" "context" "fmt" "sync" "time" "github.com/antlabs/timer" "github.com/gobwas/ws" "github.com/gobwas/ws/wsutil" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/util/gconv" "github.com/panjf2000/gnet/pkg/logging" ) func ConutPlayer() int { count := 0 Mainplayer.Range(func(uint32, *Player) bool { count++ return true // 继续遍历 }) return count } type ClientData struct { IsCrossDomain bool //是否跨域过 Player *Player //客户实体 //UserID uint32 ERROR_CONNUT int Wsmsg *WsCodec } func NewClientData() *ClientData { cd := ClientData{ IsCrossDomain: false, Player: nil, Wsmsg: &WsCodec{}, } return &cd } var Mainplayer = &utils.SyncMap[uint32, *Player]{} //玩家数据 func (c *Conn) Close() { c.Mu.Lock() defer c.Mu.Unlock() c.MainConn.Close() } func (c *Conn) SendPack(bytes []byte) error { c.Mu.Lock() defer c.Mu.Unlock() if t, ok := c.MainConn.Context().(*ClientData); ok { if t.Wsmsg.Upgraded { // This is the echo server err := wsutil.WriteServerMessage(c.MainConn, ws.OpBinary, bytes) if err != nil { logging.Infof("conn[%v] [err=%v]", c.MainConn.RemoteAddr().String(), err.Error()) return err } } else { _, err := c.MainConn.Write(bytes) if err != nil { glog.Debug(context.Background(), err) } } } return nil } type OgreInfo struct { Data [9]OgrePetInfo } type OgrePetInfo struct { Id uint32 Shiny uint32 Lv uint32 `struc:"skip"` //等级 } type Player struct { MainConn *Conn baseplayer IsLogin bool //是否登录 mu sync.Mutex loginChan chan struct{} // 登录完成通知通道 StopChan timer.TimeNoder context.Context PVPinfo *info.PVPinfo //当前邀请的玩家ID Onlinetime uint32 //当前登录时间 OgreInfo OgreInfo Service *blservice.UserService HavePVPinfo []*Player monsters [3]int Canmon bool //可以刷怪 Changemap bool //是否切换过地图 } // PlayerOption 定义配置 Player 的函数类型 type PlayerOption func(*Player) func WithConn(c *Conn) PlayerOption { return func(p *Player) { p.MainConn = c } } func (p *Player) GetAction() { } // 刷怪具体实现 func (p *Player) SpawnMonsters() { // 获取当前地图的怪物配置 // 创建数据包 tt := NewTomeeHeader(2004, p.Info.UserID) p.genMonster(p.Info.MapID) //生成野怪 p.SendPack(tt.Pack(&p.OgreInfo)) } // 2. 从 string 类型 slice 随机选一个元素 func RandomStringFromSlice(s []string) string { r := rand.New(rand.NewSource(time.Now().UnixNano())) randomIdx := r.Intn(len(s)) return s[randomIdx] } // 应该根据怪物信息决定后端生成 func (p *Player) genMonster(mapid uint32) { var oldnum, newNum int p.monsters, oldnum, newNum = replaceOneNumber(p.monsters) // 设置怪物信息 t1 := OgreInfo{} mapss, ok := xmlres.MonsterMap[gconv.Int(mapid)] if ok && mapss.Monsters != nil { for i, m := range mapss.Monsters.Monsters { //这里是9个 id := strings.Split(m.ID, " ") lv := strings.Split(m.Lv, " ") ttt := OgrePetInfo{ Id: gconv.Uint32(RandomStringFromSlice(id)), } if ttt.Id != 0 { ttt.Shiny = 0 //待确认是否刷新异色 ttt.Lv = gconv.Uint32(RandomStringFromSlice(lv)) } t1.Data[i] = ttt } } if p.Changemap { p.Changemap = false p.OgreInfo = OgreInfo{} //切地图清空 for i := 0; i < 3; i++ { p.OgreInfo.Data[p.monsters[i]] = t1.Data[p.monsters[i]] } } else { p.OgreInfo.Data[oldnum] = OgrePetInfo{} p.OgreInfo.Data[newNum] = t1.Data[newNum] } } // 生成0-9之间三个不重复的随机数 进地图5s func generateThreeUniqueNumbers() [3]int { rand.Seed(time.Now().UnixNano()) selected := make(map[int]bool) var result [3]int index := 0 for index < 3 { num := rand.Intn(9) if !selected[num] { selected[num] = true result[index] = num index++ } } return result } // 从三个数字中移除一个,并从剩余6个数字中选一个补充 10s func replaceOneNumber(original [3]int) ([3]int, int, int) { // 随机选择要移除的索引(0-2) removeIndex := rand.Intn(3) removedNum := original[removeIndex] // 找出所有不在原始数组中的数字(候选数字) candidates := []int{} originalMap := make(map[int]bool) for _, num := range original { originalMap[num] = true } for i := 0; i < 8; i++ { if !originalMap[i] { candidates = append(candidates, i) } } // 从候选数字中随机选择一个 newNum := candidates[rand.Intn(len(candidates))] // 创建新数组并替换数字 newNumbers := original newNumbers[removeIndex] = newNum return newNumbers, removedNum, newNum } // 添加物品 func (p *Player) ItemAdd(t []model.SingleItemInfo) { var ttt []model.SingleItemInfo for _, v := range t { switch v.ItemId { case 1: //塞尔豆 p.Info.Coins = p.Info.Coins + v.ItemCnt case 3: //累计经验 p.Info.ExpPool = p.Info.ExpPool + v.ItemCnt default: ttt = append(ttt, v) } } p.Service.ItemAdd(ttt...) return } func (p *Player) SendPack(b []byte) error { // fmt.Println("发送数据包", len(b)) err := p.MainConn.SendPack(b) return err } func (p *Player) Cheak(b error) { if b != nil { g.Log().Error(context.Background(), "出现错误", p.Info.UserID, b.Error()) } } func LeaveMap(c common.PlayerI) { if c == nil { return } if c.GetInfo() == nil { return } if c.GetInfo().MapID == 0 { return } t := NewTomeeHeader(2002, c.GetInfo().UserID) for k, v := range space.GetSpace(c.GetInfo().MapID).User.Items() { if k != c.GetInfo().UserID { v.SendPack(t.Pack(&space.LeaveMapOutboundInfo{UserID: c.GetInfo().UserID})) } } space.GetSpace(c.GetInfo().MapID).User.Remove(c.GetInfo().UserID) } // Save 保存玩家数据 func (p *Player) Save() { if p.Info == nil { return } if p.FightC != nil { p.FightC.Over(p, info.BattleOverReason.PlayerOffline) //玩家逃跑 } p.Info.TimeToday = p.Info.TimeToday + uint32(time.Now().Unix()) - uint32(p.Onlinetime) //保存电池时间 p.Onlinetime = uint32(time.Now().Unix()) p.Service.Save(p.Info) LeaveMap(p) p.StopChan.Stop() //停止刷怪 p.IsLogin = false Mainplayer.Delete(p.Info.UserID) share.ShareManager.DeleteUserOnline(p.Info.UserID) //设置用户登录服务器 } // 是否可以获得经验 func (p *Player) CanGetExp() bool { ttt := p.Info.TimeLimit - p.Info.TimeToday return (uint32(time.Now().Unix()) - uint32(p.Onlinetime)) <= ttt } // IsLoggedIn 检查是否已登录 func (lw *Player) IsLoggedIn() bool { lw.mu.Lock() defer lw.mu.Unlock() return lw.IsLogin } // WaitForLogin 等待登录完成,无超时 func (lw *Player) WaitForLogin() error { if lw.IsLoggedIn() { return nil } // 阻塞等待登录完成 <-lw.loginChan return nil } // WaitForLoginWithTimeout 带超时的登录等待 func (lw *Player) WaitForLoginWithTimeout(timeout time.Duration) error { if lw.IsLoggedIn() { return nil } // 使用定时器实现超时 timer := time.NewTimer(timeout) defer timer.Stop() select { case <-lw.loginChan: return nil case <-timer.C: return fmt.Errorf("登录等待超时: %v", timeout) } } // WaitForLoginWithCtx 带上下文的登录等待 func (lw *Player) WaitForLoginWithCtx(ctx context.Context) error { if lw.IsLoggedIn() { return nil } select { case <-lw.loginChan: return nil case <-ctx.Done(): return ctx.Err() // 上下文取消或超时 } } // CompleteLogin 标记登录完成并通知等待者 func (lw *Player) CompleteLogin() { lw.mu.Lock() defer lw.mu.Unlock() if lw.Info.MapID > 10000 || lw.Info.MapID == 0 { //如果位于基地,就重置到传送仓 lw.Info.MapID = 1 } if lw.IsNewPlayer() { //重置新手地图 lw.Info.MapID = 515 } if !lw.IsLogin { lw.IsLogin = true close(lw.loginChan) // 关闭通道以通知所有等待者 } } // 定义检查函数:判断84-87索引中是否有任意一个元素不等于3 func (lw *Player) IsNewPlayer() bool { // 遍历84到87的索引 for i := 84; i <= 87; i++ { if lw.Info.TaskList[i] != 3 { return true // 只要有一个不等于3,就返回true } } return false // 全部等于3则返回false }