package logic import ( "ecs/proto" "ecs/proto/pb" "ecs/servers/game/data" "github.com/oylshe1314/framework/client/db" "github.com/oylshe1314/framework/errors" "github.com/oylshe1314/framework/http" "github.com/oylshe1314/framework/net" "github.com/oylshe1314/framework/server" "github.com/oylshe1314/framework/util" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "sync" "time" ) type messageHandler interface { handle(player *Player, msg *net.Message) } type MessageHandler func(player *Player, msg *net.Message) func (handler MessageHandler) handle(player *Player, msg *net.Message) { handler(player, msg) } type innerHandler interface { handle(player *Player, msg *http.Message) } type InnerHandler func(player *Player, msg *http.Message) func (handler InnerHandler) handle(player *Player, msg *http.Message) { handler(player, msg) } const TableMail = "mail" type RegionMail struct { Id uint64 `bson:"_id"` Title string `bson:"title"` Content string `bson:"content"` ItemId []uint32 `bson:"itemId"` ItemNum []uint32 `bson:"itemNum"` CreateTime int64 `bson:"create_time"` Expiration int64 `bson:"expiration"` } type PlayerManager struct { manager closed bool waitGroup sync.WaitGroup tables *data.Tables mongoClient *db.MongoClient //eventManager *EventManager serverManager *ServerManager locker sync.Mutex players map[uint64]*Player mailsLocker sync.Mutex regionMails map[uint64]*RegionMail messageHandlers map[uint32]messageHandler innerHandlerFns map[string]func() (bool, innerHandler) } func NewPlayerManager(svr server.Server, tables *data.Tables, mongoClient *db.MongoClient /*eventManager *EventManager, */, serverManager *ServerManager) *PlayerManager { return &PlayerManager{ manager: manager{logger: svr.Logger(), server: svr}, tables: tables, mongoClient: mongoClient, //eventManager: eventManager, serverManager: serverManager, players: map[uint64]*Player{}, regionMails: map[uint64]*RegionMail{}, messageHandlers: map[uint32]messageHandler{}, innerHandlerFns: map[string]func() (bool, innerHandler){}, } } func (this *PlayerManager) Init() error { //if this.eventManager == nil { // return errors.Error("'eventManager' is nil") //} // //if this.serverManager == nil { // return errors.Error("'serverManager' is nil") //} // //if this.arenaManager == nil { // return errors.Error("'arenaManager' is nil") //} // //var err = this.RegionMailLoad() //if err != nil { // return err //} return nil } func (this *PlayerManager) Close() error { this.closed = true this.logger.Debug("Wait for player tickers closing") this.waitGroup.Wait() this.logger.Debug("All player tickers closed") return nil } func (this *PlayerManager) SetTables(tables *data.Tables) { this.tables = tables } func (this *PlayerManager) PutPlayer(player *Player) { this.locker.Lock() defer this.locker.Unlock() this.players[player.UserId] = player } func (this *PlayerManager) GetPlayer(userId uint64) *Player { this.locker.Lock() defer this.locker.Unlock() return this.players[userId] } func (this *PlayerManager) RemovePlayer(userId uint64) *Player { this.locker.Lock() defer this.locker.Unlock() if player, ok := this.players[userId]; ok { delete(this.players, userId) return player } return nil } func (this *PlayerManager) clearChanges(player *Player) { player.save = map[string]interface{}{} player.wipe = map[string]interface{}{} } func (this *PlayerManager) storeChanges(player *Player) { defer func() { player.save = nil player.wipe = nil }() if len(player.save) == 0 && len(player.wipe) == 0 { return } var m = bson.M{} if len(player.save) > 0 { m["$set"] = player.save } if len(player.wipe) > 0 { m["$unset"] = player.wipe } if this.logger.IsDebugEnabled() { this.logger.Debugf("[%s:%d] Save: %s", player.Temp.Address, player.RoleId, util.ToJsonString(player.save)) this.logger.Debugf("[%s:%d] Wipe: %s", player.Temp.Address, player.RoleId, util.ToJsonString(player.wipe)) } _, err := this.mongoClient.Collection(TablePlayer).UpdateByID(this.mongoClient.Context(), player.RoleId, m) if err != nil { this.logger.Error("Execute database update failed, ", err) } } func (this *PlayerManager) tick(player *Player, now, elapsed int64) int64 { player.lock() defer player.unlock() this.clearChanges(player) var b = util.UnixMilli() player.tick(now, elapsed) var e = util.UnixMilli() this.storeChanges(player) return e - b } func (this *PlayerManager) run(player *Player) { go func(player *Player) { this.waitGroup.Add(1) var ticker = time.NewTicker(time.Second) defer func() { ticker.Stop() this.waitGroup.Done() }() var spend int64 var elapsed int64 for { now := <-ticker.C elapsed++ spend = this.tick(player, now.Unix(), elapsed) if this.closed || player.exited { return } if spend >= 1000 { this.logger.Warnf("[%s:%d] The time spent executing the player loop function was too long, Time: %dms, userId: %d, roleId: %d", player.Temp.Address, player.RoleId, spend, player.UserId, player.RoleId) } } }(player) } func (this *PlayerManager) NewRoleId() (uint64, error) { counter, err := this.mongoClient.Counter("role_id", 1) if err != nil { return 0, err } return util.EncryptUid(counter), nil } func (this *PlayerManager) NewPlayer() *Player { return &Player{ manager: this, //BattlePassReward: []*PlayerBattlePassRewrad{}, Artifact: map[uint64]*PlayerArtifact{}, //Achievement: map[uint64]*PlayerAchievement{}, //BattlePass: map[uint32]*PlayerBattlePass{}, //CdKey: map[string]*PlayerCdKey{}, //CopyPassed: map[uint32]*PlayerCopyPassed{}, //CopySpeed: map[uint32]*PlayerCopySpeed{}, CopyStatus: map[uint64]*PlayerCopyStatus{}, Counter: map[uint32]map[uint64]*PlayerCounter{}, Hero: map[uint64]*PlayerHero{}, HeroBook: map[uint32]*PlayerHeroBook{}, Item: map[uint32]*PlayerItem{}, Equip: map[uint64]*PlayerEquip{}, //GiftPack: map[uint32]*PlayerGiftPack{}, Lineup: map[uint64]*PlayerLineup{}, Mail: map[uint64]*PlayerMail{}, Money: map[uint32]*PlayerMoney{}, //MonthlyCard: map[uint32]*PlayerMonthlyCard{}, //Planet: map[uint32]*PlayerPlanet{}, //RawStone: map[uint32]*PlayerRawStone{}, RewardActive: map[uint32]*PlayerRewardActive{}, RewardLogin: map[uint32]*PlayerRewardLogin{}, //RoleTalent: map[uint32]*PlayerRoleTalent{}, //StorePool: map[uint32]*PlayerStorePool{}, Task: map[uint32]*PlayerTask{}, Treasure: map[uint64]*PlayerTreasure{}, } } func (this *PlayerManager) SavePlayer(player *Player) error { _, err := this.mongoClient.Collection(TablePlayer).InsertOne(this.mongoClient.Context(), player) if err != nil { return err } //this.serverManager.AddCreations(1) return nil } func (this *PlayerManager) QueryPlayers(userId uint64) ([]*Player, error) { var opts = options.Find().SetProjection(bson.M{ "_id": 1, "platform": 1, "channel": 1, "user_id": 1, "server_id": 1, "username": 1, "language": 1, "avatar": 1, "role_name": 1, "role_gender": 1, }) cur, err := this.mongoClient.Collection(TablePlayer).Find(this.mongoClient.Context(), bson.M{"user_id": userId}, opts) if err != nil { if errors.Is(err, mongo.ErrNoDocuments) { return nil, nil } return nil, err } defer cur.Close(this.mongoClient.Context()) var players []*Player for cur.Next(this.mongoClient.Context()) { var player = new(Player) err := cur.Decode(player) if err != nil { return nil, err } players = append(players, player) } return players, nil } func (this *PlayerManager) QueryPlayer(roleId uint64) (*Player, error) { var res = this.mongoClient.Collection(TablePlayer).FindOne(this.mongoClient.Context(), bson.M{"_id": roleId}) var err = res.Err() if err != nil { if errors.Is(err, mongo.ErrNoDocuments) { return nil, nil } return nil, err } var player = this.NewPlayer() err = res.Decode(player) if err != nil { return nil, err } return player, nil } func (this *PlayerManager) ExistsRoleName(name string) (bool, error) { cur, err := this.mongoClient.Collection(TablePlayer).Find(this.mongoClient.Context(), bson.M{"role.name": name}) if err != nil { return false, err } return cur.Next(this.mongoClient.Context()), nil } //func (this *PlayerManager) NewChargeOrder(paymentType uint32, player *Player, chargeTable *data.Charge) (*PlayerChargeOrder, error) { // var counter, err = this.mongoClient.Counter("order_id", 1) // if err != nil { // return nil, err // } // // var id = fmt.Sprint("PC", util.EncryptUid(counter)) // // var order = &PlayerChargeOrder{ // Id: id, // PlayerId: player.PlayerId, // ChargeId: uint32(chargeTable.Id), // PaymentType: paymentType, // Quantity: uint32(chargeTable.Quantity), // Price: uint32(chargeTable.TradePrice), // CreateTime: util.Unix(), // Status: uint32(proto.OrderStatusNotPaid), // } // // order.Sign = this.SignChargeOrder(order) // // return order, nil //} //func (this *PlayerManager) SaveChargeOrder(order *PlayerChargeOrder) error { // _, err := this.mongoClient.Collection(TableOrder).InsertOne(context.Background(), order) // return err //} // //func (this *PlayerManager) SignChargeOrder(order *PlayerChargeOrder) string { // var sb strings.Builder // // sb.WriteString("playerId=") // sb.WriteString(util.IntegerToString(order.PlayerId)) // sb.WriteByte('&') // // sb.WriteString("chargeId=") // sb.WriteString(util.IntegerToString(order.ChargeId)) // sb.WriteByte('&') // // sb.WriteString("paymentType=") // sb.WriteString(util.IntegerToString(order.PaymentType)) // sb.WriteByte('&') // // sb.WriteString("quantity=") // sb.WriteString(util.IntegerToString(order.Quantity)) // sb.WriteByte('&') // // sb.WriteString("price=") // sb.WriteString(util.IntegerToString(order.Price)) // // return util.MD5(sb.String()) //} // //func (this *PlayerManager) QueryChargeOrder(id string) (*PlayerChargeOrder, error) { // var order = new(PlayerChargeOrder) // var err = this.mongoClient.Collection(TableOrder).FindOne(this.mongoClient.Context(), bson.M{"_id": id}).Decode(order) // if err != nil { // if err == mongo.ErrNoDocuments { // return nil, nil // } // return nil, err // } // return order, nil //} // //func (this *PlayerManager) ChangeChargeOrder(id string, status proto.OrderStatus, extOrderNo string) error { // var set = bson.M{} // set["status"] = uint32(status) // if extOrderNo != "" { // set["ext_order_no"] = extOrderNo // } // _, err := this.mongoClient.Collection(TableOrder).UpdateByID(this.mongoClient.Context(), id, bson.M{"$set": set}) // return err //} func (this *PlayerManager) Enter(player *Player, conn *net.Conn) { this.logger.Infof("[%s] The player enter game_1, userId: %d, roleId: %d", player.Temp.Address, player.UserId, player.RoleId) this.PutPlayer(player) player.lock() defer player.unlock() this.clearChanges(player) player.enter(conn) this.storeChanges(player) this.run(player) this.serverManager.AddOnline(1) } func (this *PlayerManager) Reenter(player *Player, conn *net.Conn) { this.logger.Infof("[%s] The player reenter game, userId: %d, roleId: %d", player.Temp.Address, player.UserId, player.RoleId) player.lock() defer player.unlock() player.reenter(conn) this.serverManager.AddOnline(1) } func (this *PlayerManager) Exit(player *Player) { this.logger.Infof("[%s] The player exit game, userId: %d, roleId: %d", player.Temp.Address, player.UserId, player.RoleId) this.RemovePlayer(player.UserId) player.lock() defer player.unlock() player.exit() this.serverManager.ReduceOnline(1) } func (this *PlayerManager) Lost(player *Player) { this.logger.Infof("[%s] The player lost, userId: %d, roleId: %d", player.Temp.Address, player.UserId, player.RoleId) player.lock() defer player.unlock() player.lost(func() { this.RemovePlayer(player.UserId) }) this.serverManager.ReduceOnline(1) } func (this *PlayerManager) kick(player *Player, message string) { player.lock() defer player.unlock() player.kick(message) this.serverManager.ReduceOnline(1) } func (this *PlayerManager) Kick(player *Player, message string) { this.logger.Infof("[%s] The player kicked, userId: %d, roleId: %d", player.Temp.Address, player.UserId, player.RoleId) this.RemovePlayer(player.UserId) this.kick(player, message) } func (this *PlayerManager) KickAll(message string) { this.logger.Info("Kick all player") this.locker.Lock() defer this.locker.Unlock() for _, player := range this.players { this.kick(player, message) } this.players = map[uint64]*Player{} } func (this *PlayerManager) Ban(player *Player, days int64) { if days == 0 { this.Unban(player) } else { player.lock() defer player.unlock() this.clearChanges(player) if days < 0 { player.ban(-1) } else { player.ban(util.TodayBeginTime() + util.DayTotalSeconds*days) } this.storeChanges(player) } } func (this *PlayerManager) Unban(player *Player) { player.lock() defer player.unlock() this.clearChanges(player) player.unban() this.storeChanges(player) } func (this *PlayerManager) Handler(modId pb.ModId, msgId pb.MsgId, handler MessageHandler) { this.messageHandlers[util.Compose2uint16(uint16(modId), uint16(msgId))] = handler } func (this *PlayerManager) Handle(player *Player, msg *net.Message) { handler, ok := this.messageHandlers[util.Compose2uint16(msg.ModId, msg.MsgId)] if !ok { this.logger.Warnf("[%s] Could not find the message handler, modId: %d, msgId: %d ", player.Temp.Address, msg.ModId, msg.MsgId) return } player.lock() defer player.unlock() this.clearChanges(player) handler.handle(player, msg) this.storeChanges(player) } func (this *PlayerManager) InnerHandler(pattern string, offline bool, handler InnerHandler) { this.innerHandlerFns[pattern] = func() (bool, innerHandler) { return offline, handler } } func (this *PlayerManager) HandleInner(playerId uint64, msg *http.Message) { var pattern = msg.R.URL.Path var fn, ok = this.innerHandlerFns[pattern] if !ok { this.logger.Error("Could not find the inner request handler, pattern: ", pattern) _ = msg.Reply(proto.ErrHandlerNotFound) return } offline, handler := fn() var err error var player = this.GetPlayer(playerId) if player == nil { if !offline { this.logger.Debug("Player not online, playerId: ", playerId) _ = msg.Reply(proto.ErrPlayerNotOnline) return } player, err = this.QueryPlayer(playerId) if err != nil { this.logger.Error("Player query failed, ", err) _ = msg.Reply(proto.ErrPlayerQueryFailed) return } if player == nil { this.logger.Debug("Player not found, playerId: ", playerId) _ = msg.Reply(proto.ErrPlayerNotFound) return } } player.lock() defer player.unlock() this.clearChanges(player) handler.handle(player, msg) this.storeChanges(player) } //func (this *PlayerManager) RegionMailLoad() error { // var now = util.Unix() // cur, err := this.mongoClient.Collection(TableMail).Find2(this.mongoClient.Context(), bson.M{"expiration": bson.M{"$gt": now}}) // if err != nil { // return err // } // // var mails []*RegionMail // err = cur.All(this.mongoClient.Context(), &mails) // if err != nil { // return err // } // // for _, mail := range mails { // this.regionMails[mail.Id] = mail // } // return nil //} // //func (this *PlayerManager) RegionMailList() ([]*proto.BackMail, error) { // var list []*RegionMail // cur, err := this.mongoClient.Collection(TableMail).Find2(this.mongoClient.Context(), bson.M{}) // if err != nil { // return nil, err // } // // err = cur.All(this.mongoClient.Context(), &list) // if err != nil { // return nil, err // } // // return util.SliceConvert(list, func(s *RegionMail) *proto.BackMail { // return &proto.BackMail{ // Id: s.Id, // Title: s.Title, // Content: s.Content, // ItemId: s.ItemId, // ItemNum: s.ItemNum, // CreateTime: s.CreateTime, // Expiration: s.Expiration, // } // }), nil //} // //func (this *PlayerManager) RegionMailSend(backgroudMail *proto.BackMail) error { // counter, err := this.mongoClient.Counter("mail_id", 1) // if err != nil { // return err // } // // var uid = util.EncryptUid(counter) // // var mail = &RegionMail{ // Id: uid, // Title: backgroundMail.Title, // Content: backgroundMail.Content, // ItemId: backgroundMail.ItemId, // ItemNum: backgroundMail.ItemNum, // CreateTime: backgroundMail.CreateTime, // Expiration: backgroundMail.Expiration, // } // // _, err = this.mongoClient.Collection(TableMail).InsertOne(this.mongoClient.Context(), mail) // if err != nil { // return err // } // // this.mailsLocker.Lock() // defer this.mailsLocker.Unlock() // // this.regionMails[mail.Id] = mail // return nil //} // //func (this *PlayerManager) RegionMailDelete(ids []uint64) error { // _, err := this.mongoClient.Collection(TableMail).DeleteMany(this.mongoClient.Context(), bson.M{"_id": bson.M{"$in": ids}}) // if err != nil { // return err // } // // this.mailsLocker.Lock() // defer this.mailsLocker.Unlock() // for _, id := range ids { // delete(this.regionMails, id) // } // // return nil //} // //func (this *PlayerManager) RegionMailRecv(player *Player) (dirty bool) { // var now = util.Unix() // // this.mailsLocker.Lock() // // //Delete the regional mails that were expired // for _, mail := range player.Mail { // if proto.MailType(mail.Type) != proto.MailTypeRegion { // continue // } // // if regoinMail, ok := this.regionMails[mail.Id]; ok { // if now < regoinMail.Expiration { // continue // } // delete(this.regionMails, regoinMail.Id) // } // // delete(player.Mail, mail.Id) // player.WipeModel(mail) // dirty = true // } // // //Add new region mails that the player did not have // var regionMails []*RegionMail // for _, regionMail := range this.regionMails { // if _, ok := player.Mail[regionMail.Id]; ok { // continue // } // // regionMails = append(regionMails, regionMail) // } // this.mailsLocker.Unlock() // // if len(regionMails) > 0 { // dirty = true // sort.Slice(regionMails, func(i, j int) bool { // return regionMails[i].CreateTime < regionMails[j].CreateTime // }) // for i, regionMail := range regionMails { // player.addMail(regionMail.Id, proto.MailTypeRegion, regionMail.Title, regionMail.Content, nil, regionMail.ItemId, regionMail.ItemNum, now+1+int64(i), now+(regionMail.Expiration-regionMail.CreateTime)) // } // } // return //} // //func (this *PlayerManager) PlayerMailSend(playerId uint64, offline bool, tipe proto.MailType, title, content string, args []string, itemId, itemNum []uint32, createTime int64, expiration ...int64) { // var err error // var player = this.GetPlayer(playerId) // if player == nil { // if !offline { // this.logger.Debug("Player not online, playerId: ", playerId) // return // } // // player, err = this.QueryPlayer(playerId) // if err != nil { // this.logger.Error("Player query failed, ", err) // return // } // // if player == nil { // this.logger.Debug("Player not found, playerId: ", playerId) // return // } // } // // this.logger.Debugf("Send player mail, playerId: %d, offline: %v, tipe: %d, title: %s, createTime: %d", playerId, offline, tipe, title, createTime) // // player.lock() // defer player.unlock() // // this.clearChanges(player) // player.AddMail(tipe, title, content, args, itemId, itemNum, createTime, expiration...) // this.storeChanges(player) //} //func (this *PlayerManager) PlayerDefendRecord(playerId uint64, offline bool, score int32, attacker *RankingPlayer) { // var err error // var player = this.GetPlayer(playerId) // if player == nil { // if !offline { // this.logger.Debug("Player not online, playerId: ", playerId) // return // } // // player, err = this.QueryPlayer(playerId) // if err != nil { // this.logger.Error("Player query failed, ", err) // return // } // // if player == nil { // this.logger.Debug("Player not found, playerId: ", playerId) // return // } // } // // player.lock() // defer player.unlock() // // this.clearChanges(player) // player.ArenaBattleRecord(false, score, attacker) // this.storeChanges(player) //}