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/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 Wsmsg *WsCodec } func NewClientData() *ClientData { cd := ClientData{ IsCrossDomain: false, Player: nil, Wsmsg: &WsCodec{}, } return &cd } var Mainplayer = &utils.SyncMap[uint32, *Player]{} //玩家数据 func (c *Conn) SendPack(bytes []byte) error { 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 chan struct{} //停止刷怪协程 context.Context PVPinfo *info.PVPinfo //当前邀请的玩家ID Onlinetime uint32 //当前登录时间 OgreInfo *OgreInfo Service *blservice.UserService HavePVPinfo []*Player monsters [3]int Canmon bool //可以刷怪 } // PlayerOption 定义配置 Player 的函数类型 type PlayerOption func(*Player) func WithConn(c *Conn) PlayerOption { return func(p *Player) { p.MainConn = c } } func (p *Player) GetAction() { return } // 刷怪具体实现 func (p *Player) SpawnMonsters() { // 获取当前地图的怪物配置 // 创建数据包 tt := NewTomeeHeader(2004, p.Info.UserID) p.monsters, _, _ = replaceOneNumber(p.monsters) 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) { // 设置怪物信息 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 } } t2 := OgreInfo{} for i := 0; i < 3; i++ { t2.Data[p.monsters[i]] = t1.Data[p.monsters[i]] } p.OgreInfo = &t2 } // 生成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 + int64(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) SendAttackValue(b info.AttackValueS) { t1 := NewTomeeHeader(2505, p.Info.UserID) p.SendPack(t1.Pack(&b)) //准备包由各自发,因为协议不一样 } func (p *Player) SendChangePet(b info.ChangePetInfo) { t1 := NewTomeeHeader(2407, p.Info.UserID) p.SendPack(t1.Pack(&b)) //准备包由各自发,因为协议不一样 } func (p *Player) Cheak(b error) { if b != nil { g.Log().Error(context.Background(), "出现错误", p.Info.UserID, b.Error()) } } func (p *Player) SendReadyToFightInfo(b info.FightStartOutboundInfo) { t1 := NewTomeeHeader(2504, p.Info.UserID) p.SendPack(t1.Pack(&b)) } func (p *Player) SendNoteReadyToFightInfo(b info.NoteReadyToFightInfo) { t1 := NewTomeeHeader(2503, p.Info.UserID) p.SendPack(t1.Pack(&b)) //准备包由各自发,因为协议不一样 } func (p *Player) SendFightEndInfo(b info.FightOverInfo) { t1 := NewTomeeHeader(2506, p.Info.UserID) p.SendPack(t1.Pack(&b)) p.FightC = nil } func (p *Player) CatchPetInfo(b info.CatchMonsterOutboundInfo) { t1 := NewTomeeHeader(2409, p.Info.UserID) p.SendPack(t1.Pack(&b)) } func LeaveMap(c common.PlayerI) { t := NewTomeeHeader(2002, c.GetInfo().UserID) space.GetSpace(c.GetInfo().MapID).Range(func(playerID uint32, player common.PlayerI) bool { if playerID != c.GetInfo().UserID { player.SendPack(t.Pack(&space.LeaveMapOutboundInfo{UserID: c.GetInfo().UserID})) } return true }) space.GetSpace(c.GetInfo().MapID).Delete(c.GetInfo().UserID) } // Save 保存玩家数据 func (p *Player) Save() { if p.Info == nil { return } LeaveMap(p) close(p.StopChan) //停止刷怪 p.IsLogin = false Mainplayer.Delete(p.Info.UserID) share.ShareManager.DeleteUserOnline(p.Info.UserID) //设置用户登录服务器 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) } // 是否可以获得经验 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 = 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 } // 邀请玩家加入战斗 邀请者,被邀请者,邀请模式 func (lw *Player) InvitePlayerToBattle(pinfo *info.PVPinfo) { lw.PVPinfo = pinfo Mainplayer.Range(func(key uint32, value *Player) bool { if key == uint32(lw.PVPinfo.PlayerID) { value.HavePVPinfo = append([]*Player{value}, value.HavePVPinfo...) t1 := NewTomeeHeader(2501, value.Info.UserID) t := info.NoteInviteToFightOutboundInfo{ UserID: lw.Info.UserID, Nick: lw.Info.Nick, Mode: pinfo.Mode, } value.SendPack(t1.Pack(&t)) return false } return true }) } // 取消对战邀请 func (lw *Player) CancelBattle() { if lw.PVPinfo == nil { return } Mainplayer.Range(func(key uint32, value *Player) bool { if key == uint32(lw.PVPinfo.PlayerID) { for idx, v := range value.HavePVPinfo { if v != nil && v.GetInfo().UserID == lw.PVPinfo.PlayerID { value.HavePVPinfo = append(value.HavePVPinfo[:idx], value.HavePVPinfo[idx+1:]...) } } return false } return true }) lw.PVPinfo = nil } func (lw *Player) CanBattle() bool { for _, v := range lw.Info.PetList { if v.Hp > 0 { return true } } return false } // 同意对战 func (lw *Player) AgreeBattle(userid, flag, mode uint32) (bool, common.PlayerI) { defer func(p *Player) { p.HavePVPinfo = make([]*Player, 0) }(lw) //删除对方的邀请信息 for _, v := range lw.HavePVPinfo { if v == nil || v.Info.UserID != userid || v.PVPinfo == nil { continue } t1 := NewTomeeHeader(2502, v.Info.UserID) ret := &info.S2C_NOTE_HANDLE_FIGHT_INVITE{ UserID: lw.Info.UserID, Nick: lw.Info.Nick, } if flag == 0 { //拒绝对战 v.SendPack(t1.Pack(ret)) return false, nil } if !lw.IsLogin { //玩家未登录 ret.Result = 4 v.SendPack(t1.Pack(ret)) return false, nil } if v.PVPinfo.PlayerID == userid && uint32(v.PVPinfo.Mode) == mode { //成功找到,同意对战 if lw.CanBattle() { ret.Result = 1 v.SendPack(t1.Pack(ret)) return true, v } else { ret.Result = 3 v.SendPack(t1.Pack(ret)) return false, nil } } return false, nil } //如果对方掉线 return false, nil }