ecs/servers/game/logic/manager_player.go

777 lines
20 KiB
Go
Raw Normal View History

2025-06-04 18:17:39 +08:00
package logic
import (
"ecs/proto"
"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()
2025-06-05 17:48:23 +08:00
defer this.locker.Unlock()
2025-06-04 18:17:39 +08:00
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()
var player = this.players[userId]
if player != nil {
delete(this.players, userId)
}
return player
}
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() {
2025-06-05 17:48:23 +08:00
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))
2025-06-04 18:17:39 +08:00
}
_, 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) 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 b, e int64
var elapsed int64 = 0
for {
now := <-ticker.C
elapsed++
player.lock() // lock
this.clearChanges(player)
b = util.UnixMilli()
player.tick(now.Unix(), elapsed)
e = util.UnixMilli()
this.storeChanges(player)
if this.closed || player.exited {
player.unlock() // unlock
return
}
player.unlock() // unlock
if e-b >= 1000 {
2025-06-05 17:48:23 +08:00
this.logger.Warnf("[%s:%d] The executing time of the player loop function is too long, Time: %dms, userId: %d, roleId: %d",
player.Temp.Address,
player.RoleId,
e-b,
player.UserId,
player.RoleId)
2025-06-04 18:17:39 +08:00
}
}
}(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{},
2025-06-04 18:31:07 +08:00
Artifact: map[uint64]*PlayerArtifact{},
2025-06-04 18:17:39 +08:00
//Achievement: map[uint64]*PlayerAchievement{},
//BattlePass: map[uint32]*PlayerBattlePass{},
//Cdkey: map[string]*PlayerCdkey{},
//CopyPassed: map[uint32]*PlayerCopyPassed{},
//CopySpeed: map[uint32]*PlayerCopySpeed{},
//CopyStatus: map[uint64]*PlayerCopyStatus{},
2025-06-05 17:48:23 +08:00
Counter: map[uint32]map[uint64]*PlayerCounter{},
Hero: map[uint64]*PlayerHero{},
HeroBook: map[uint32]*PlayerHeroBook{},
Item: map[uint32]*PlayerItem{},
Equip: map[uint64]*PlayerEquip{},
2025-06-04 18:17:39 +08:00
//GiftPack: map[uint32]*PlayerGiftPack{},
//Mail: map[uint64]*PlayerMail{},
Money: map[uint32]*PlayerMoney{},
//MonthlyCard: map[uint32]*PlayerMonthlyCard{},
//Planet: map[uint32]*PlayerPlanet{},
//RawStone: map[uint32]*PlayerRawStone{},
//RigCharacter: map[uint32]*PlayerRigCharacter{},
//RigComponent: map[uint32]*PlayerRigComponent{},
//RigCore: map[uint32]*PlayerRigCore{},
//RigEquip: map[uint32]*PlayerRigEquip{},
//RigPendant: map[uint32]*PlayerRigPendant{},
//RigRemains: map[uint32]*PlayerRigRemains{},
//RigSmelter: map[uint32]*PlayerRigSmelter{},
//RigTrammels: map[uint32]*PlayerRigTrammels{},
//RigWarship: map[uint32]*PlayerRigWarship{},
//RoleTalent: map[uint32]*PlayerRoleTalent{},
//StorePool: map[uint32]*PlayerStorePool{},
//Task: map[uint32]*PlayerTask{},
//TaskActive: map[uint32]*PlayerTaskActive{},
2025-06-04 18:31:07 +08:00
Treasure: map[uint64]*PlayerTreasure{},
2025-06-04 18:17:39 +08:00
}
}
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) {
2025-06-05 17:48:23 +08:00
this.logger.Infof("[%s] The player enter game_1, userId: %d, roleId: %d", player.Temp.Address, player.UserId, player.RoleId)
2025-06-04 18:17:39 +08:00
this.PutPlayer(player)
player.lock()
this.clearChanges(player)
player.enter(conn)
this.storeChanges(player)
player.unlock()
this.run(player)
this.serverManager.AddOnline(1)
}
func (this *PlayerManager) Reenter(player *Player, conn *net.Conn) {
2025-06-05 17:48:23 +08:00
this.logger.Infof("[%s] The player reenter game, userId: %d, roleId: %d", player.Temp.Address, player.UserId, player.RoleId)
2025-06-04 18:17:39 +08:00
player.lock()
player.reenter(conn)
player.unlock()
this.serverManager.AddOnline(1)
}
func (this *PlayerManager) Exit(player *Player) {
2025-06-05 17:48:23 +08:00
this.logger.Infof("[%s] The player exit game, userId: %d, roleId: %d", player.Temp.Address, player.UserId, player.RoleId)
2025-06-04 18:17:39 +08:00
this.RemovePlayer(player.UserId)
player.lock()
player.exit()
player.lock()
this.serverManager.ReduceOnline(1)
}
func (this *PlayerManager) Lost(player *Player) {
2025-06-05 17:48:23 +08:00
this.logger.Infof("[%s] The player lost, userId: %d, roleId: %d", player.Temp.Address, player.UserId, player.RoleId)
2025-06-04 18:17:39 +08:00
player.lock()
player.lost(func() {
this.RemovePlayer(player.UserId)
})
player.unlock()
this.serverManager.ReduceOnline(1)
}
func (this *PlayerManager) Kick(player *Player, message string) {
2025-06-05 17:48:23 +08:00
this.logger.Infof("[%s] The player kicked, userId: %d, roleId: %d", player.Temp.Address, player.UserId, player.RoleId)
2025-06-04 18:17:39 +08:00
this.RemovePlayer(player.UserId)
player.lock()
player.kick(message)
player.unlock()
this.serverManager.ReduceOnline(1)
}
func (this *PlayerManager) KickAll(message string) {
this.logger.Info("Kick all player")
this.locker.Lock()
defer this.locker.Unlock()
for _, player := range this.players {
player.lock()
player.kick(message)
player.unlock()
this.serverManager.ReduceOnline(1)
}
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.Unix() + 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, msgId uint16, handler MessageHandler) {
this.messageHandlers[util.Compose2uint16(modId, msgId)] = handler
}
func (this *PlayerManager) Handle(player *Player, msg *net.Message) {
handler, ok := this.messageHandlers[util.Compose2uint16(msg.ModId, msg.MsgId)]
if !ok {
2025-06-05 17:48:23 +08:00
this.logger.Warnf("[%s] Could not find the message handler, modId: %d, msgId: %d ", player.Temp.Address, msg.ModId, msg.MsgId)
2025-06-04 18:17:39 +08:00
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.RawErrHandlerNotFound)
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.RawErrPlayerNotOnline)
return
}
player, err = this.QueryPlayer(playerId)
if err != nil {
this.logger.Error("Player query failed, ", err)
_ = msg.Reply(proto.RawErrPlayerQueryFailed)
return
}
if player == nil {
this.logger.Debug("Player not found, playerId: ", playerId)
_ = msg.Reply(proto.RawErrPlayerNotFound)
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: backgroudMail.Title,
// Content: backgroudMail.Content,
// ItemId: backgroudMail.ItemId,
// ItemNum: backgroudMail.ItemNum,
// CreateTime: backgroudMail.CreateTime,
// Expiration: backgroudMail.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)
//}