game_sync/worldsrv/rankmatch.go

750 lines
17 KiB
Go

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)
}