package main import ( "fmt" "math/rand" "os" "time" "mongo.games.com/goserver/core/basic" "mongo.games.com/goserver/core/logger" "mongo.games.com/goserver/core/netlib" "mongo.games.com/goserver/core/task" "mongo.games.com/goserver/core/utils" "mongo.games.com/goserver/srvlib" "mongo.games.com/goserver/srvlib/action" srvproto "mongo.games.com/goserver/srvlib/protocol" "mongo.games.com/game/common" "mongo.games.com/game/model" "mongo.games.com/game/proto" playerproto "mongo.games.com/game/protocol/player" serverproto "mongo.games.com/game/protocol/server" "mongo.games.com/game/worldsrv/internal" ) var PlayerMgrSington = &PlayerMgr{ sidMap: make(map[int64]*Player), snidMap: make(map[int32]*Player), accountMap: make(map[string]*Player), players: make([]*Player, 0, 1024), playerOfPlatform: make(map[string]map[int32]*Player), loading: make(map[string]*PlayerPendingData), } type PlayerPendingData struct { sid int64 // 连接标识 ts int64 // 加载数据的开始时间 } type PlayerMgr struct { common.BaseClockSinker // 以连接标识为索引 sidMap map[int64]*Player // 以snid为索引 snidMap map[int32]*Player // 以账号为索引 accountMap map[string]*Player // 只有真实玩家,不包括机器人 players []*Player // 平台id:snid:真实玩家 playerOfPlatform map[string]map[int32]*Player loading map[string]*PlayerPendingData // accountid,控制访问频率;登录状态标记 } // PlayerStatics 在线统计 type PlayerStatics struct { PlayerCnt int // 真人数量 RobotCnt int // 机器人数量 PlayerGamingCnt int // 游戏中的真人数量 RobotGamingCnt int // 游戏中的机器人数量 GamingCnt map[int]int // gameid:玩家人数(包含机器人) } // GetOnlineCount 在线人数统计 func (this *PlayerMgr) GetOnlineCount() *PlayerStatics { ps := &PlayerStatics{ GamingCnt: make(map[int]int), } for _, player := range this.sidMap { if player != nil && player.IsOnLine() { if !player.IsRob { ps.PlayerCnt++ if player.scene != nil { ps.PlayerGamingCnt++ } } else { ps.RobotCnt++ if player.scene != nil { ps.RobotGamingCnt++ } } if player.scene != nil { ps.GamingCnt[player.scene.gameId] = ps.GamingCnt[player.scene.gameId] + 1 } } } return ps } // IsOnline 判断玩家是否在线 func (this *PlayerMgr) IsOnline(snId int32) bool { player, ok := this.snidMap[snId] if ok { return player.IsOnLine() } else { return false } } // AddPlayer 缓存玩家数据,并添加到持久化管理器中 func (this *PlayerMgr) AddPlayer(sid int64, playerInfo *model.PlayerData, s *netlib.Session) bool { player := NewPlayer(sid, playerInfo, s) if player == nil { return false } logger.Logger.Trace("(this *PlayerMgr) AddPlayer Set player ip:", player.Ip) this.sidMap[sid] = player var old *Player if p, exist := this.snidMap[player.SnId]; exist { old = p } this.snidMap[player.SnId] = player this.accountMap[player.AccountId] = player if !player.IsRob { var found bool for i, p := range this.players { if p.SnId == player.SnId { found = true logger.Logger.Warnf("(this *PlayerMgr) AddPlayer [this.players] found player exist snid=%v", player.SnId) this.players[i] = player break } } if !found { this.players = append(this.players, player) } //平台玩家管理器 if pp, exist := this.playerOfPlatform[player.Platform]; exist { pp[player.SnId] = player } else { pp = make(map[int32]*Player) pp[player.SnId] = player this.playerOfPlatform[player.Platform] = pp } logger.Logger.Tracef("###%v mount to DBSaver[AddPlayer]", player.SnId) if old != nil { //删除旧的玩家 DbSaveInst.UnregisterDbSaveTask(old) } DbSaveInst.RegisterDbSaverTask(player) } return true } func (this *PlayerMgr) SavePlayer(p *Player, isCopy, force bool) { if p == nil || p.isDelete { return } if p.IsRob { return } if !p.dirty && !force { return } var pd *model.PlayerData if isCopy { pd = model.ClonePlayerData(p.PlayerData) } else { pd = p.PlayerData } if pd == nil { logger.Logger.Errorf("Player Time2Save() %v pd is nil", p.SnId) return } t1 := time.Now() // 跨天任务依赖LastLogoutTime的准确性,跨天任务是定时器common.ClockMgrSington触发的,所以这里要用定时器的触发时间 p.LastLogoutTime = common.ClockMgrSingleton.LastTickTime pd.LastLogoutTime = common.ClockMgrSingleton.LastTickTime ok := true t := task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { if !model.SavePlayerData(pd) { // save 失败先写到json里面 model.BackupPlayerData(pd) ok = false } for _, v := range internal.GetPlayerLoads() { v.Save(pd.Platform, pd.SnId, true, force) } return ok }), task.CompleteNotifyWrapper(func(i interface{}, t task.Task) { if saved, ok := i.(bool); ok && saved { p.dirty = false bak := fmt.Sprintf("%v.json", pd.AccountId) if exist, _ := common.PathExists(bak); exist { os.Remove(bak) } } logger.Logger.Infof("Player Time2Save() %v take:%v isSuccess:%v", p.SnId, time.Now().Sub(t1), p.dirty == false) }), "SavePlayerTask") if b := t.StartByExecutor(fmt.Sprintf("Player%v", p.SnId)); b { p.lastSaved = time.Now() } } // DelPlayer 清除玩家缓存数据 // 真人数据持久化后删除,机器人不用持久化(机器人数据没有主动删除) // needSave 是否需要保存数据; 自动删除玩家机制已经保存过数据,不需要再保存;手动删除玩家需要保存数据 func (this *PlayerMgr) DelPlayer(snid int32) bool { player, ok := this.snidMap[snid] if !ok || player == nil { return false } if player.sid != 0 { delete(this.sidMap, player.sid) } delete(this.snidMap, player.SnId) delete(this.accountMap, player.AccountId) if !player.IsRob { index := -1 for i, p := range this.players { if p.SnId == snid { index = i break } } if index != -1 { this.players = append(this.players[:index], this.players[index+1:]...) } //平台玩家管理器 if pp, exist := this.playerOfPlatform[player.Platform]; exist { delete(pp, player.SnId) } } if !player.IsCacheState() { player.OnLogoutFinish() internal.FirePlayerLogouted[*Player, *Scene](player) } DbSaveInst.UnregisterDbSaveTask(player) // 再保存一次,防止数据丢失,可能脏标记没有设置 this.SavePlayer(player, true, true) for _, v := range internal.GetPlayerLoads() { v.Release(player.Platform, player.SnId) } return true } // DropPlayer 玩家掉线或登出 // 1.玩家登出 // 2.玩家网络断开 // 3.被踢掉线 func (this *PlayerMgr) DropPlayer(p *Player) { p.SetOffline() p.sid = 0 p.gateSess = nil delete(this.sidMap, p.sid) } // ReholdPlayer 玩家重连 // 1.登录获取玩家数据 func (this *PlayerMgr) ReholdPlayer(p *Player, newSid int64, newSess *netlib.Session) { if p.sid != 0 { delete(this.sidMap, p.sid) } if newSid == 0 { logger.Logger.Errorf("(this *PlayerMgr) ReholdPlayer(snid=%v, new=%v)", p.SnId, newSid) } p.SetOnline() p.sid = newSid p.gateSess = newSess this.sidMap[newSid] = p } func (this *PlayerMgr) KickByPlatform(name string) { for _, p := range this.players { if name == "" || p.Platform == name { p.Kick(common.KickReason_Disconnection) } } } // GetOnlinePlayer 获取玩家数据(玩家在线) func (this *PlayerMgr) GetOnlinePlayer(id int64) *Player { if pi, ok := this.sidMap[id]; ok { return pi } return nil } func (this *PlayerMgr) GetPlayerBySnId(id int32) *Player { if p, ok := this.snidMap[id]; ok { return p } return nil } func (this *PlayerMgr) GetPlatformPlayerBySnId(platform string, id int32) *Player { if players, exit := this.playerOfPlatform[platform]; exit { if p, ok := players[id]; ok { return p } } return nil } // GetPlayersBySnIds 批量取出玩家信息 func (this *PlayerMgr) GetPlayersBySnIds(ids []int32) []*Player { var retPlayers []*Player for _, v := range ids { if p, ok := this.snidMap[v]; ok { retPlayers = append(retPlayers, p) } } return retPlayers } func (this *PlayerMgr) GetPlayerByAccount(acc string) *Player { if p, ok := this.accountMap[acc]; ok { return p } return nil } // BroadcastMessage 给所有玩家发消息 func (this *PlayerMgr) BroadcastMessage(packetid int, rawpack interface{}) bool { sc := &srvproto.BCSessionUnion{} action.BroadcastMessage(common.GetSelfAreaId(), srvlib.GateServerType, packetid, rawpack, sc) return false } // BroadcastMessageToPlatform 给某个平台所有玩家发消息 func (this *PlayerMgr) BroadcastMessageToPlatform(platform string, packetid int, rawpack interface{}) { if platform == "" { this.BroadcastMessage(packetid, rawpack) } else { players := this.playerOfPlatform[platform] mgs := make(map[*netlib.Session][]*srvproto.MCSessionUnion) for _, p := range players { if p != nil && p.gateSess != nil && p.IsOnLine() /*&& p.Platform == platform*/ { mgs[p.gateSess] = append(mgs[p.gateSess], &srvproto.MCSessionUnion{ Mccs: &srvproto.MCClientSession{ SId: proto.Int64(p.sid), }, }) } } for gateSess, v := range mgs { if gateSess != nil && len(v) != 0 { action.MulticastMessageToServer(gateSess, packetid, rawpack, v...) } } } } // BroadcastDataConfigToPlatform 广播配置数据更新 func (this *PlayerMgr) BroadcastDataConfigToPlatform(platform string, tp int) { packetId := int(playerproto.PlayerPacketID_PACKET_SCDataConfig) pack := &playerproto.SCDataConfig{} f, ok := DataConfigFuncMap[tp] if ok { d := f(platform, nil) if d != nil { pack.Cfg = append(pack.Cfg, d) } } if len(pack.Cfg) > 0 { this.BroadcastMessageToPlatform(platform, packetId, pack) } } func (this *PlayerMgr) BroadcastMessageToPlatformByFunc(platform string, packetid int, rawpack interface{}, f func(p *Player) bool) { if platform == "" { this.BroadcastMessage(packetid, rawpack) } else { players := this.playerOfPlatform[platform] mgs := make(map[*netlib.Session][]*srvproto.MCSessionUnion) for _, p := range players { if p != nil && p.gateSess != nil && p.IsOnLine() && f(p) { mgs[p.gateSess] = append(mgs[p.gateSess], &srvproto.MCSessionUnion{ Mccs: &srvproto.MCClientSession{ SId: proto.Int64(p.sid), }, }) } } for gateSess, v := range mgs { if gateSess != nil && len(v) != 0 { action.MulticastMessageToServer(gateSess, packetid, rawpack, v...) } } } } // BroadcastMessageToPlatformWithHall 发送大厅消息,不包括在游戏中的玩家 // 如果是好友,支持消息屏蔽 func (this *PlayerMgr) BroadcastMessageToPlatformWithHall(platform string, snid int32, packetid int, rawpack interface{}) { if platform == "" { this.BroadcastMessage(packetid, rawpack) } else { player := this.GetPlayerBySnId(snid) if player != nil { players := this.playerOfPlatform[platform] mgs := make(map[*netlib.Session][]*srvproto.MCSessionUnion) for _, p := range players { if p != nil && p.gateSess != nil && p.IsOnLine() && p.scene == nil { if FriendMgrSingleton.IsShield(p.Platform, p.SnId, snid) { continue } mgs[p.gateSess] = append(mgs[p.gateSess], &srvproto.MCSessionUnion{ Mccs: &srvproto.MCClientSession{ SId: proto.Int64(p.sid), }, }) } } for gateSess, v := range mgs { if gateSess != nil && len(v) != 0 { action.MulticastMessageToServer(gateSess, packetid, rawpack, v...) } } } } } // BroadcastMessageToGroup 发送群组消息 func (this *PlayerMgr) BroadcastMessageToGroup(packetid int, rawpack interface{}, tags []string) bool { pack := &serverproto.SSCustomTagMulticast{ Tags: tags, } if byteData, ok := rawpack.([]byte); ok { pack.RawData = byteData } else { byteData, err := netlib.MarshalPacket(packetid, rawpack) if err == nil { pack.RawData = byteData } else { logger.Logger.Info("PlayerMgr.BroadcastMessageToGroup err:", err) return false } } srvlib.ServerSessionMgrSington.Broadcast(int(serverproto.SSPacketID_PACKET_SS_CUSTOMTAG_MULTICAST), pack, common.GetSelfAreaId(), srvlib.GateServerType) return true } // BroadcastMessageToTarget 给某些玩家发消息 func (this *PlayerMgr) BroadcastMessageToTarget(snIds []int32, packetid int, rawpack interface{}) { mgs := make(map[*netlib.Session][]*srvproto.MCSessionUnion) for _, v := range snIds { d := this.snidMap[v] if d != nil && d.gateSess != nil && d.IsOnLine() { mgs[d.gateSess] = append(mgs[d.gateSess], &srvproto.MCSessionUnion{ Mccs: &srvproto.MCClientSession{ SId: proto.Int64(d.sid), }, }) } } for gateSess, v := range mgs { if gateSess != nil && len(v) != 0 { action.MulticastMessageToServer(gateSess, packetid, rawpack, v...) } } } // SaveAll 保存所有数据,dirty=true func (this *PlayerMgr) SaveAll() { count := len(this.players) start := time.Now() saveCnt := 0 failCnt := 0 logger.Logger.Info("===@SaveAllPlayerBEGIN@=== TotalCount:", count) for i, p := range this.players { idx := i + 1 if model.SavePlayerData(p.PlayerData) { logger.Logger.Infof("===@SavePlayerData %v/%v snid:%v coin:%v safebox:%v coinpayts:%v safeboxts:%v gamets:%v save [ok] @=", idx, count, p.SnId, p.Coin, p.SafeBoxCoin, p.CoinPayTs, p.SafeBoxCoinTs, p.GameCoinTs) for _, v := range internal.GetPlayerLoads() { v.Save(p.Platform, p.SnId, true, true) } saveCnt++ } else { logger.Logger.Warnf("===@SavePlayerData %v/%v snid:%v coin:%v safebox:%v coinpayts:%v safeboxts:%v gamets:%v save [error]@=", idx, count, p.SnId, p.Coin, p.SafeBoxCoin, p.CoinPayTs, p.SafeBoxCoinTs, p.GameCoinTs) failCnt++ } } logger.Logger.Infof("===@SaveAllPlayerEND@===, total:%v saveCnt:%v failCnt:%v take:%v", count, saveCnt, failCnt, time.Now().Sub(start)) } // LoadRobots 预加载机器人数据 func (this *PlayerMgr) LoadRobots() { if model.GameParamData.PreLoadRobotCount > 0 { task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { tsBeg := time.Now() robots := model.GetRobotPlayers(model.GameParamData.PreLoadRobotCount) tsEnd := time.Now() logger.Logger.Tracef("GetRobotPlayers take:%v total:%v", tsEnd.Sub(tsBeg), len(robots)) return robots }), task.CompleteNotifyWrapper(func(data interface{}, t task.Task) { if robots, ok := data.([]*model.PlayerData); ok { if robots != nil { for i := 0; i < len(robots); i++ { if this.GetPlayerBySnId(robots[i].SnId) == nil { player := NewPlayer(0, robots[i], nil) if player != nil { this.snidMap[player.SnId] = player this.accountMap[player.AccountId] = player } } } } } }), "GetRobotPlayers").Start() } } func (this *PlayerMgr) StartLoading(accid string, sid int64) bool { ts := time.Now().Unix() if d, exist := this.loading[accid]; exist { d.sid = sid if ts-d.ts > 300 { d.ts = ts return false } return true } this.loading[accid] = &PlayerPendingData{sid: sid, ts: ts} return false } func (this *PlayerMgr) EndPlayerLoading(accid string) int64 { if d, exist := this.loading[accid]; exist { delete(this.loading, accid) return d.sid } return 0 } func (this *PlayerMgr) StatsOnline() model.PlayerOLStats { stats := model.PlayerOLStats{ PlatformStats: make(map[string]*model.PlayerStats), RobotStats: model.PlayerStats{ InGameCnt: make(map[int32]map[int32]int32), }, } for _, p := range this.sidMap { if p != nil { if p.IsRob { pps := &stats.RobotStats if pps != nil { if p.scene == nil { pps.InHallCnt++ } else { if g, exist := pps.InGameCnt[int32(p.scene.gameId)]; exist { g[p.scene.dbGameFree.GetId()]++ } else { g := make(map[int32]int32) pps.InGameCnt[int32(p.scene.gameId)] = g g[p.scene.dbGameFree.GetId()]++ } } } } else { var pps *model.PlayerStats var exist bool if pps, exist = stats.PlatformStats[p.Platform]; !exist { pps = &model.PlayerStats{InGameCnt: make(map[int32]map[int32]int32)} stats.PlatformStats[p.Platform] = pps } if pps != nil { if p.scene == nil { pps.InHallCnt++ } else { if g, exist := pps.InGameCnt[int32(p.scene.gameId)]; exist { g[p.scene.dbGameFree.GetId()]++ } else { g := make(map[int32]int32) pps.InGameCnt[int32(p.scene.gameId)] = g g[p.scene.dbGameFree.GetId()]++ } } } } } } return stats } func (p *PlayerMgr) UpdateName(snId int32, name string) { player := p.GetPlayerBySnId(snId) if player == nil { return } player.setName(name) player.dirty = true } func (p *PlayerMgr) UpdateHead(snId, head int32) { player := p.GetPlayerBySnId(snId) if player == nil { return } player.Head = head //0:男 1:女 player.Sex = (player.Head%2 + 1) % 2 player.dirty = true player.changeIconTime = time.Now() } func (p *PlayerMgr) UpdateHeadOutline(snId, outline int32) { player := p.GetPlayerBySnId(snId) if player == nil { return } player.HeadOutLine = outline player.dirty = true } func (p *PlayerMgr) UpdateHeadUrl(snId int32, url string) { player := p.GetPlayerBySnId(snId) if player == nil { return } if player.HeadUrl != url { player.HeadUrl = url player.dirty = true } } /* 推荐好友规则 1.优先判断在线玩家人数N (1)N≥20;每次刷新,从在线玩家中随机6个 (2)N<20;则填充机器人,保证N=20,每次填充的机器人头像和昵称随机;然后从N中随机6个 2.刷新有CD(暂定20s),刷新过后进入cd */ type RecommendFriend struct { Snid int32 Name string Head int32 HeadUrl string RoleId int32 } // RecommendFriendRule 推荐好友 func (this *PlayerMgr) RecommendFriendRule(platform string, snid int32) []RecommendFriend { if platform == "" { return nil } else { rets := []RecommendFriend{} players := this.playerOfPlatform[platform] for _, player := range players { //优先真人 if player.SnId != snid && !FriendMgrSingleton.IsFriend(platform, snid, player.SnId) { roleId := common.DefaultRoleId if player.Roles != nil { roleId = int(player.Roles.ModId) } ret := RecommendFriend{ Snid: player.SnId, Name: player.Name, Head: player.Head, HeadUrl: player.HeadUrl, RoleId: int32(roleId), } rets = append(rets, ret) if len(rets) >= 20 { break } } } if len(rets) < 20 { for _, player := range this.snidMap { //其次机器人 if player.IsRob { roleId := common.DefaultRoleId if player.Roles != nil { roleId = int(player.Roles.ModId) } ret := RecommendFriend{ Snid: player.SnId, Name: player.Name, Head: player.Head, HeadUrl: player.HeadUrl, RoleId: int32(roleId), } rets = append(rets, ret) if len(rets) >= 20 { break } } } } needIdxs := []int{} if rets != nil { if len(rets) >= 6 { for { if len(needIdxs) >= 6 { break } randIdx := rand.Intn(len(rets)) if !common.InSliceInt(needIdxs, randIdx) { needIdxs = append(needIdxs, randIdx) } } } else { for i := 0; i < len(rets); i++ { needIdxs = append(needIdxs, i) } } } ret := []RecommendFriend{} for _, idx := range needIdxs { ret = append(ret, rets[idx]) } return ret } } func init() { PlayerSubjectSign.AttachName(PlayerMgrSington) PlayerSubjectSign.AttachHead(PlayerMgrSington) PlayerSubjectSign.AttachHeadOutline(PlayerMgrSington) PlayerSubjectSign.AttachHeadUrl(PlayerMgrSington) PlayerSubjectSign.AttachName(FriendMgrSingleton) PlayerSubjectSign.AttachHead(FriendMgrSingleton) // 定时器 common.RegisterClockFunc(&common.ClockFunc{ OnSecTimerFunc: func() { for _, player := range PlayerMgrSington.players { utils.CatchPanic(func() { player.OnSecTimer() }) } }, OnMiniTimerFunc: func() { for _, player := range PlayerMgrSington.players { utils.CatchPanic(func() { player.OnMiniTimer() }) } }, OnHourTimerFunc: func() { for _, player := range PlayerMgrSington.players { utils.CatchPanic(func() { player.OnHourTimer() }) } }, OnDayTimerFunc: func() { for _, player := range PlayerMgrSington.players { utils.CatchPanic(func() { player.OnDayTimer(false, true, 1) }) } }, OnWeekTimerFunc: func() { for _, player := range PlayerMgrSington.players { utils.CatchPanic(func() { player.OnWeekTimer() }) } }, OnMonthTimerFunc: func() { for _, player := range PlayerMgrSington.players { utils.CatchPanic(func() { player.OnMonthTimer() }) } }, OnShutdownFunc: func() { PlayerMgrSington.SaveAll() }, }) }