package main import ( "encoding/json" "errors" "fmt" "sort" "strings" "time" "github.com/globalsign/mgo/bson" "mongo.games.com/goserver/core/basic" "mongo.games.com/goserver/core/i18n" "mongo.games.com/goserver/core/logger" "mongo.games.com/goserver/core/module" "mongo.games.com/goserver/core/task" "mongo.games.com/game/model" hall_proto "mongo.games.com/game/protocol/gamehall" "mongo.games.com/game/protocol/rankmatch" "mongo.games.com/game/srvdata" "mongo.games.com/game/worldsrv/internal" ) var RankMgrSingleton = &RankMatchMgr{ seasons: make(map[string]*RankSeasonId), playerSeasons: make(map[int32]*PlayerRankSeason), } // RankSeasonId 赛季配置 type RankSeasonId struct { Dirty bool *model.RankSeasonId } // PlayerRankSeason 玩家排位信息 type PlayerRankSeason struct { Dirty bool *model.PlayerRankSeason } func (this *PlayerRankSeason) sendEmailAward(rankType int32) { lv, _ := RankMgrSingleton.GetPlayerRankLV(rankType, this.SnId) for _, item := range RankMgrSingleton.GetRankAwardList(rankType) { if lv >= item.Lv { var has bool for _, v := range this.RankType[rankType].Awards { if v.Id == item.Id { has = true break } } if !has { this.RankType[rankType].Awards = append(this.RankType[rankType].Awards, &model.RankAward{ Id: item.Id, Ts: time.Now().Unix(), }) var coin int64 var diamond int64 var otherParams []int32 for _, v := range item.Awards { switch v.Id { case 1: // 金币 coin = int64(v.Num) case 2: // 钻石 diamond = int64(v.Num) default: // 道具 otherParams = []int32{v.Id, v.Num} } } // 邮件发送奖励 var newMsg *model.Message index := item.Id task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { var rankName []string for _, v := range []string{"zh", "vi", "en", "kh"} { rankName = append(rankName, i18n.Tr(v, fmt.Sprintf("RankReward_%d_%d", rankType, index))) } title := i18n.Tr("languages", "RankAwardTitle", rankName) content := i18n.Tr("languages", "RankAward") newMsg = model.NewMessage("", 0, "", this.SnId, model.MSGTYPE_RANK_REWARD, title, content, coin, diamond, model.MSGSTATE_UNREAD, time.Now().Unix(), 0, "", otherParams, this.Platform, model.HallTienlen) return model.InsertMessage(this.Platform, newMsg) }), task.CompleteNotifyWrapper(func(data interface{}, t task.Task) { if data == nil { p := PlayerMgrSington.GetPlayerBySnId(this.SnId) if p != nil { p.AddMessage(newMsg) } } }), "AddMailByItem").StartByFixExecutor("SendMail") } } } } // CheckNewSeason 检查赛季继承 func (this *PlayerRankSeason) CheckNewSeason() { var n int var f func() f = func() { cfg := RankMgrSingleton.GetSeasonConfig(this.Platform) if cfg.SeasonId > this.SeasonId { this.Dirty = true this.SeasonId++ // 领取的奖励发邮件 for k := range this.RankType { this.sendEmailAward(k) } old, err := json.Marshal(this.RankType) if err != nil { logger.Logger.Errorf("json.Marshal(this.RankType) err: %v", err) return } this.LastRankType = make(map[int32]*model.PlayerRankInfo) if err = json.Unmarshal(old, &this.LastRankType); err != nil { logger.Logger.Errorf("json.Unmarshal(old,&this.LastRankType) err: %v", err) return } for k, v := range this.RankType { this.RankType[k].Score = RankMgrSingleton.NewSeasonScore(v.Score) this.RankType[k].Awards = nil } f() n++ if n > 1000 { // 防止死循环 return } } } f() // 通知积分变更 if this.Dirty { if p := PlayerMgrSington.GetPlayerBySnId(this.SnId); p != nil { p.SendRankSeason() } for k := range this.RankType { this.rankLog(k) this.CheckShowRed(k) } } } func (this *PlayerRankSeason) Update(rankScore map[int32]int64) { this.CheckNewSeason() for k, v := range this.RankType { if v != nil && v.Score != rankScore[k] { v.Score = rankScore[k] this.Dirty = true this.CheckShowRed(k) this.rankLog(k) } } for k, v := range rankScore { info := this.RankType[k] if info == nil { info = &model.PlayerRankInfo{ Score: v, } this.RankType[k] = info this.Dirty = true this.CheckShowRed(k) this.rankLog(k) } } if this.Dirty { if p := PlayerMgrSington.GetPlayerBySnId(this.SnId); p != nil { p.SendRankSeason() } } } // CheckShowRed 检查是否显示红点 func (this *PlayerRankSeason) CheckShowRed(rankType int32) { lv, _ := RankMgrSingleton.GetPlayerRankLV(rankType, this.SnId) info := this.RankType[rankType] var has bool // 有未领取的 here: for _, item := range RankMgrSingleton.GetRankAwardList(rankType) { if lv >= item.Lv { // 可领取 if info == nil { has = true break } if len(info.Awards) == 0 { has = true break } for _, v := range info.Awards { if v != nil && v.Id == item.Id { // 领过了 continue here } } has = true break } } if has { p := PlayerMgrSington.GetPlayerBySnId(this.SnId) if p != nil { p.SendShowRed(hall_proto.ShowRedCode_RankReward, rankType, 1) } } } func (this *PlayerRankSeason) rankLog(rankType int32) { p := PlayerMgrSington.GetPlayerBySnId(this.SnId) if p == nil { return } lv, score := RankMgrSingleton.GetPlayerRankLV(rankType, this.SnId) // 排行榜 log := &model.PlayerRankScore{ Platform: this.Platform, SnId: this.SnId, RankType: rankType, SeasonId: this.SeasonId, Lv: lv, Score: score, Name: p.Name, Sex: p.Sex, HeadUrl: p.HeadUrl, Coin: p.Coin, UpdateTs: time.Now().Unix(), ModId: p.PlayerData.GetRoleId(), } LogChannelSingleton.WriteLog(log) } type RankMatchMgr struct { BaseClockSinker failSeason []string seasons map[string]*RankSeasonId // 当前赛季 key平台id playerSeasons map[int32]*PlayerRankSeason // 玩家排位信息 key玩家id } // 从数据库读取赛季配置 func (r *RankMatchMgr) load(platform string) { var ret *model.RankSeasonId var err error task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { ret, err = model.FindOneRankSeasonId(platform) if err != nil { logger.Logger.Errorf("RankMatchMgr.Load err: %v", err) return err } return nil }), task.CompleteNotifyWrapper(func(i interface{}, t task.Task) { if err == nil { if ret.SeasonId > 0 { r.SetSeasonConfig(&RankSeasonId{ Dirty: false, RankSeasonId: ret, }) } else { season := r.fromFile(platform) if season != nil { season.Dirty = true r.SetSeasonConfig(season) } } r.saveRankSeason(platform, true, true) } else { r.failSeason = append(r.failSeason, platform) } })).StartByFixExecutor(fmt.Sprintf("platform%s", platform)) } var ErrTimeStr = errors.New("time str err") func (r *RankMatchMgr) strToTime(s string) (time.Time, error) { t := time.Time{} ls := strings.Split(s, "/") if len(ls) != 3 { return t, ErrTimeStr } s = ls[0] + fmt.Sprintf("/%02s", ls[1]) + fmt.Sprintf("/%02s", ls[2]) if len(s) != 10 { return t, ErrTimeStr } return time.ParseInLocation("2006/01/02", s, time.Local) } // fromFile 从文件中读取赛季配置 func (r *RankMatchMgr) fromFile(platform string) *RankSeasonId { now := time.Now().Unix() for _, v := range srvdata.PBDB_RankCycleMgr.Datas.Arr { st, err := r.strToTime(v.Start) if err != nil { logger.Logger.Errorf("RankMatchMgr.fromFile start time err:%v", err) continue } et, err := r.strToTime(v.End) if err != nil { logger.Logger.Errorf("RankMatchMgr.fromFile end time err:%v", err) continue } if now >= st.Unix() && now < et.AddDate(0, 0, 1).Unix() { return &RankSeasonId{ Dirty: false, RankSeasonId: &model.RankSeasonId{ Platform: platform, SeasonId: v.Id, StartTs: st.Unix(), EndTs: et.Unix(), UpdateTs: now, }, } } } return nil } // saveRankSeason 保存赛季配置 func (r *RankMatchMgr) saveRankSeason(platform string, isSync bool, force bool) { ret, ok := r.seasons[platform] if !ok || ret == nil || (!ret.Dirty && !force) { return } //todo 需要拷贝一份数据再保存吗 var res bool f := func() { res = model.UpsertRankSeasonId(ret.RankSeasonId) } cf := func() { if res { ret.Dirty = false } } if isSync { f() cf() return } task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { f() return nil }), task.CompleteNotifyWrapper(func(i interface{}, t task.Task) { cf() })).StartByFixExecutor(fmt.Sprintf("platform%s", platform)) } // checkNewSeason 是否新赛季 func (r *RankMatchMgr) checkNewSeason(platform string) bool { newSeason := r.fromFile(platform) oldSeason := r.GetSeasonConfig(platform) var oldId, newId int32 if oldSeason != nil { oldId = oldSeason.SeasonId } if newSeason != nil { newId = newSeason.SeasonId } if newId > 0 && oldId != newId { // 新赛季 newSeason.Dirty = true r.SetSeasonConfig(newSeason) // 玩家段位继承 for _, v := range r.playerSeasons { v.CheckNewSeason() } return true } return false } // GetSeasonConfig 获取当前赛季配置 func (r *RankMatchMgr) GetSeasonConfig(platform string) *RankSeasonId { return r.seasons[platform] } // SetSeasonConfig 设置当前赛季配置 func (r *RankMatchMgr) SetSeasonConfig(config *RankSeasonId) { if config == nil { return } r.seasons[config.Platform] = config r.checkNewSeason(config.Platform) } func (r *RankMatchMgr) GetPlayerSeason(snid int32) *PlayerRankSeason { ret := r.playerSeasons[snid] if ret == nil { return nil } return ret } func (r *RankMatchMgr) GetPlayerRankScore(snid int32) map[int32]int64 { ret := make(map[int32]int64) if rank := r.GetPlayerSeason(snid); rank != nil && rank.PlayerRankSeason != nil { for k, v := range rank.RankType { if v != nil { ret[k] = v.Score } } } return ret } func (r *RankMatchMgr) GetPlayerRankScoreInt64(snid int32) map[int64]int64 { ret := make(map[int64]int64) if rank := r.GetPlayerSeason(snid); rank != nil && rank.PlayerRankSeason != nil { for k, v := range rank.RankType { if v != nil { ret[int64(k)] = v.Score } } } return ret } func (r *RankMatchMgr) CheckShowRed(snid int32) { p := r.GetPlayerSeason(snid) if p == nil { return } for k := range p.RankType { p.CheckShowRed(k) } } func (r *RankMatchMgr) UpdatePlayerSeason(snid int32, rankScore map[int32]int64) { season := r.GetPlayerSeason(snid) if season != nil { season.Update(rankScore) } } func (r *RankMatchMgr) UpdateRobotSeason(platform string, snid int32, rankType int32, score int64, name string, sex int32, headUrl string, coin int64, roleId int32) { log := &model.PlayerRankScore{ Platform: platform, SnId: snid, RankType: rankType, SeasonId: r.GetSeasonConfig(platform).SeasonId, Lv: srvdata.RankLevelMgr.GetRankLevel(rankType, score), Score: score, Name: name, Sex: sex, HeadUrl: headUrl, Coin: coin, IsRobot: true, UpdateTs: time.Now().Unix(), ModId: roleId, } LogChannelSingleton.WriteLog(log) } func (r *RankMatchMgr) SetPlayerSeason(config *PlayerRankSeason) { if config == nil { return } r.playerSeasons[config.SnId] = config // 段位继承 config.CheckNewSeason() } func (r *RankMatchMgr) ModuleName() string { return "RankMatchMgr" } func (r *RankMatchMgr) Init() { for _, platform := range PlatformMgrSingleton.GetPlatforms() { if platform.IdStr == DefaultPlatform { continue } r.load(platform.IdStr) } } func (r *RankMatchMgr) Update() { // 数据库查询失败重新查询 if len(r.failSeason) > 0 { ls := r.failSeason r.failSeason = make([]string, 0) for _, v := range ls { r.load(v) } } } func (r *RankMatchMgr) Shutdown() { for _, platform := range PlatformMgrSingleton.GetPlatforms() { if platform.IdStr == DefaultPlatform { continue } r.saveRankSeason(platform.IdStr, true, true) } module.UnregisteModule(r) } // NewSeasonScore 赛季继承积分 func (r *RankMatchMgr) NewSeasonScore(old int64) int64 { return srvdata.RankLevelMgr.GetNewScore(old) } func (r *RankMatchMgr) GetPlayerRankLV(rankType, snid int32) (int32, int64) { if rankType <= 0 { return 0, 0 } rank := r.GetPlayerSeason(snid) if rank == nil || rank.RankType == nil || rank.RankType[rankType] == nil { return 0, 0 } score := rank.RankType[rankType].Score return srvdata.RankLevelMgr.GetRankLevel(rankType, score), score } func (r *RankMatchMgr) GetPlayerLastRankLV(rankType, snid int32) (int32, int64) { if rankType <= 0 { return 0, 0 } rank := r.GetPlayerSeason(snid) if rank == nil || rank.LastRankType == nil || rank.LastRankType[rankType] == nil { return 0, 0 } score := rank.LastRankType[rankType].Score return srvdata.RankLevelMgr.GetRankLevel(rankType, score), score } func (r *RankMatchMgr) GetRankList(rankType int32) []*rankmatch.RankItem { var list []*rankmatch.RankItem // todo 优化 for _, v := range srvdata.PBDB_RankLevelMgr.Datas.Arr { if rankType < v.GetRankType() { continue } if rankType > v.GetRankType() { break } list = append(list, &rankmatch.RankItem{ Id: v.Id, Lv: v.Level, }) } sort.Slice(list, func(i, j int) bool { return list[i].Lv > list[j].Lv }) return list } func (r *RankMatchMgr) getRankScore(rankType int32, lv int32) int64 { for _, v := range srvdata.PBDB_RankLevelMgr.Datas.Arr { if rankType < v.GetRankType() { continue } if rankType > v.GetRankType() { break } if v.Level == lv { return v.Score } } return 0 } func (r *RankMatchMgr) GetRankAwardList(rankType int32) []*rankmatch.AwardItem { var list []*rankmatch.AwardItem for _, v := range srvdata.PBDB_RankRewardMgr.Datas.Arr { if rankType < v.GetRankType() { continue } if rankType > v.GetRankType() { break } item := &rankmatch.AwardItem{ Id: v.Id, Lv: v.Level, Score: r.getRankScore(rankType, v.Level), } item.Awards = []*rankmatch.Award{} if v.Award1Num > 0 { item.Awards = append(item.Awards, &rankmatch.Award{ Id: v.Award1Id, Num: v.Award1Num, }) } if v.Award2Num > 0 { item.Awards = append(item.Awards, &rankmatch.Award{ Id: v.Award2Id, Num: v.Award2Num, }) } if v.Award3Num > 0 { item.Awards = append(item.Awards, &rankmatch.Award{ Id: v.Award3Id, Num: v.Award3Num, }) } list = append(list, item) } sort.Slice(list, func(i, j int) bool { return list[i].Lv > list[j].Lv }) return list } //========================implement ClockSinker ============================== func (r *RankMatchMgr) InterestClockEvent() int { return 1 << CLOCK_EVENT_DAY } func (r *RankMatchMgr) OnDayTimer() { logger.Logger.Info("(this *RankMatchMgr) OnDayTimer") for _, platform := range PlatformMgrSingleton.GetPlatforms() { if platform.IdStr == DefaultPlatform { continue } // 检查新赛季 r.checkNewSeason(platform.IdStr) // 保存赛季配置 r.saveRankSeason(platform.IdStr, false, false) } } //========================implement IPlayerLoad ============================== func (r *RankMatchMgr) Load(platform string, snid int32, player any) *internal.PlayerLoadReplay { args := &model.FindPlayerRankSeasonArgs{ Platform: platform, Id: []int32{snid}, } ret, err := model.FindPlayerRankSeason(args) return &internal.PlayerLoadReplay{ Platform: platform, Snid: snid, Err: err, Data: ret.List, } } func (r *RankMatchMgr) Callback(player any, ret *internal.PlayerLoadReplay) { if ret == nil { return } if ret.Err != nil || ret.Data == nil { logger.Logger.Errorf("RankMatchMgr load player rankseason err:%v", ret.Err) return } ls := ret.Data.([]*model.PlayerRankSeason) if len(ls) == 0 { // 初始化 s := r.GetSeasonConfig(ret.Platform) if s == nil { return } r.SetPlayerSeason(&PlayerRankSeason{ Dirty: true, PlayerRankSeason: model.NewPlayerRankSeason(&model.NewPlayerRankSeasonArgs{ Platform: ret.Platform, SnId: ret.Snid, SeasonId: s.SeasonId, }), }) } else { r.SetPlayerSeason(&PlayerRankSeason{ Dirty: false, PlayerRankSeason: ls[0], }) } } func (r *RankMatchMgr) LoadAfter(platform string, snid int32) *internal.PlayerLoadReplay { return nil } func (r *RankMatchMgr) CallbackAfter(ret *internal.PlayerLoadReplay) { } func (r *RankMatchMgr) Save(platform string, snid int32, isSync, force bool) { ret, ok := r.playerSeasons[snid] if !ok || ret == nil || (!ret.Dirty && !force) { return } var res bool f := func() { if ret.Id == "" { ret.Id = bson.NewObjectId() } res = model.UpsertPlayerRankSeason(ret.PlayerRankSeason) } cf := func() { if res { ret.Dirty = false } } if isSync { f() cf() return } task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { f() return nil }), task.CompleteNotifyWrapper(func(i interface{}, t task.Task) { cf() })).StartByFixExecutor(fmt.Sprintf("platform%s", ret.Platform)) } func (r *RankMatchMgr) Release(platform string, snid int32) { delete(r.playerSeasons, snid) } func init() { module.RegisteModule(RankMgrSingleton, time.Minute, 0) ClockMgrSington.RegisteSinker(RankMgrSingleton) internal.RegisterPlayerLoad(RankMgrSingleton) }