package db import ( "context" "errors" capp "f1-game/internal/cherry_app" "f1-game/internal/component/db" dbFacade "f1-game/internal/component/db/facade" "f1-game/internal/component/redis" "f1-game/internal/data" "f1-game/internal/enum" nameDB "f1-game/internal/name/db" nameRedis "f1-game/internal/name/redis" "f1-game/internal/pb" "f1-game/internal/sessions" dbPlayer "f1-game/nodes/game/internal/db/data/player" "fmt" "slices" ctime "github.com/cherry-game/cherry/extend/time" cproto "github.com/cherry-game/cherry/net/proto" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" clog "github.com/cherry-game/cherry/logger" ) // PlayerTable 玩家表 type ( PlayerTable struct { PlayerID int64 `bson:"PlayerID,omitempty"` PID int32 `bson:"PID,omitempty"` OpenID string `bson:"OpenID,omitempty"` UID int64 `bson:"UID,omitempty"` NodeID string `bson:"NodeID,omitempty"` OriginNodeID string `bson:"OriginNodeID,omitempty"` CreateTime int64 `bson:"CreateTime,omitempty"` CreateIP string `bson:"CreateIP,omitempty"` dbPlayer.Data `bson:"Data,omitempty"` dbFacade.TableBase `bson:",inline"` } ) func (*PlayerTable) TableName() string { return nameDB.Table_Player } func (p *PlayerTable) PrimaryValue() any { return p.PlayerID } func (p *PlayerTable) CheckField() { p.Data.CheckField() } func (p *PlayerTable) Save2Queue() { save2Queue(p) } func (p *PlayerTable) ToPlayerProto() pb.Player { return pb.Player{ PlayerID: p.PlayerID, CreateTime: p.CreateTime, GameNodeID: p.NodeID, // TODO 这个为何要下发? HasMap: p.Map.MapNodeID != "", ChangeNameCount: p.Lord.ChangeNameCount, LastChangeNameTime: p.Lord.LastChangeNameTime, ClientSettings: p.Settings.List, } } func (p *PlayerTable) ToAOIWatcher() *pb.AOIObject { return &pb.AOIObject{ ObjectID: p.Map.ObjectID, ObjectType: int32(enum.ObjectType_Player), X: p.Map.Point.X, Y: p.Map.Point.Y, IsWatcher: true, IsMarker: false, OnwerID: p.PlayerID, } } // NewPlayer 创建新玩家 func NewPlayer(playerID int64, session *cproto.Session, name string) (*PlayerTable, bool) { var ( pid = sessions.GetPID(session) openID = sessions.GetOpenID(session) language = session.GetString(sessions.Language) uid = session.Uid ip = session.Ip ) if playerID < 1 { clog.Warnf("playerID error. [pid = %d, openID = %s, name = %s]", pid, openID, name) return nil, false } if uid < 1 || pid < 1 || openID == "" { clog.Warnf("params error. [pid = %d, openID = %s, name = %s]", pid, openID, name) return nil, false } if name == "" { name = fmt.Sprintf("%d", playerID) } player := newPlayerTable(playerID) player.PID = pid player.OpenID = openID player.UID = uid player.Lord.Name = name player.Lord.IconID = data.Const.PlayerInitIcon player.Lord.BgID = data.Const.PlayerInitBg player.CreateTime = ctime.Now().ToMillisecond() player.CreateIP = ip if language != "" { player.Settings.Language = language } player.Save2Queue() // 提交到db队列 uidCache.Put(uid, player.PlayerID) // 缓存 uid->playerID uidCount.Add(1) // 用户总数+1 playerNameCache.Put(name, player.PlayerID) // 缓存 playerName->playerID playerTableCache.Put(playerID, player) // 缓存 playerID->playerTable // 初始玩家相关表 initTables(playerID) return player, true } func GetPlayerTable(playerID int64) (*PlayerTable, bool) { if val, ok := playerTableCache.GetIfPresent(playerID); ok { return val.(*PlayerTable), true } if playerTable, found := GetPlayerTableFromDB(playerID); found { playerTableCache.Put(playerID, playerTable) return playerTable, true } return nil, false } func GetPlayerTableFromDB(playerID int64) (*PlayerTable, bool) { playerTable := newPlayerTable(playerID) filter := bson.M{ nameDB.Field_NodeID: playerTable.NodeID, nameDB.Field_PlayerID: playerTable.PlayerID, } err := dbComponent.FindOne(playerTable, filter) if err != nil { if !errors.Is(err, mongo.ErrNoDocuments) { clog.Errorf("[playerID = %d] db error = %v", playerID, err) } return nil, false } playerTable.CheckField() return playerTable, true } func newPlayerTable(playerID int64) *PlayerTable { return &PlayerTable{ PlayerID: playerID, NodeID: capp.NodeID(), OriginNodeID: capp.NodeID(), Data: dbPlayer.NewData(), } } func GetPlayerIDWithUID(uid int64) (int64, bool) { if val, ok := uidCache.GetIfPresent(uid); ok { return val.(int64), true } return 0, false } // PlayerNameIsExist 玩家名全局唯一 func PlayerNameIsExist(playerName string) (int64, bool) { if val, ok := playerNameCache.GetIfPresent(playerName); ok { return val.(int64), true } return 0, false } // GetPlayerIDList 批量查询玩家id(过滤不存在的) func GetPlayerIDList(ids []int64) []int64 { var list []int64 for _, playerID := range ids { if _, found := GetPlayerTable(playerID); found { list = append(list, playerID) } } return list } // preloadPlayerTable 预加载所有玩家部份信息 func preloadPlayerTable() { var ( collection = dbComponent.Collection(nameDB.Table_Player) ctx = context.TODO() pageNum int32 = 1 pageSize int32 = 100000 // 10W filter = bson.M{nameDB.Field_NodeID: capp.NodeID()} projection = bson.M{ nameDB.Field_PlayerID: 1, nameDB.Field_LordName: 1, nameDB.Field_UID: 1, } count int64 = 0 ) for { list, _, err := db.Pagination[*PlayerTable](collection, ctx, filter, projection, nil, pageNum, pageSize, false) if err != nil { clog.Errorf("[preloadPlayerTable] err = %v", err) break } // clog.Debugf("[preloadPlayerTable] pageNum = %d, count = %d, total = %d", pageNum, len(list), total) if len(list) < 1 { break } for _, playerTable := range list { // 加载UID uid-> playerID uidCache.Put(playerTable.UID, playerTable.PlayerID) // 加载玩家名 playerName -> playerID playerNameCache.Put(playerTable.Lord.Name, playerTable.PlayerID) } pageNum++ count += int64(len(list)) } clog.Infof("preload player info count = %d", count) } func (p *PlayerTable) ToPlayerInfo() *pb.PlayerInfo { return &pb.PlayerInfo{ GameNodeID: p.NodeID, PlayerID: p.PlayerID, PlayerName: p.Lord.Name, Level: p.Lord.Level, Power: p.Lord.Power, RegisterIP: p.CreateIP, RegisterTime: p.CreateTime, } } func (p *PlayerTable) UpdatePlayerName(newName string, curTime int64) { p.Lord.Name = newName // 删除原来名字 playerNameCache.Invalidate(p.Lord.Name) playerNameCache.Put(newName, p.PlayerID) p.Lord.ChangeNameCount += 1 p.Lord.LastChangeNameTime = curTime } // 检测外显过期 func (p *PlayerTable) CheckShowExpire(nowSecond int64) ([]int32, []*pb.I32I32) { if p.Shows.ShowMap.Empty() { return nil, nil } expireShowIDs := p.Shows.CheckExpire(nowSecond) if len(expireShowIDs) <= 0 { return nil, nil } var ( changeShows = []*pb.I32I32{} fields = map[string]any{} ) // 检测头像是否过期 // TODO 过期后默认的头像、头像框和背景是否使用默认的外显ID if slices.Contains(expireShowIDs, p.Lord.IconID) { p.Lord.IconID = 0 changeShows = append(changeShows, &pb.I32I32{ Key: enum.ShowType_Icon, Value: 0, }) fields[nameRedis.Player_IconID] = p.Lord.IconID } // 检测头像框是否过期 if slices.Contains(expireShowIDs, p.Lord.FrameID) { p.Lord.FrameID = 0 changeShows = append(changeShows, &pb.I32I32{ Key: enum.ShowType_Frame, Value: 0, }) fields[nameRedis.Player_FrameID] = p.Lord.FrameID } // 检测背景是否过期 if slices.Contains(expireShowIDs, p.Lord.BgID) { p.Lord.BgID = 0 changeShows = append(changeShows, &pb.I32I32{ Key: enum.ShowType_Bg, Value: 0, }) fields[nameRedis.Player_BgID] = p.Lord.BgID } // 更新redis数据 if len(fields) > 0 { redis.Game.SavePlayerDataFields(p.PlayerID, fields) } return expireShowIDs, changeShows }