package player import ( actorRemote "f1-game/internal/actor_remote" capp "f1-game/internal/cherry_app" "f1-game/internal/code" "f1-game/internal/component/rate" "f1-game/internal/constant" "f1-game/internal/data" "f1-game/internal/event" nameLocal "f1-game/internal/name/local" nameRemote "f1-game/internal/name/remote" "f1-game/internal/pb" "f1-game/internal/sessions" "f1-game/nodes/game/internal/db" gameFacade "f1-game/nodes/game/internal/facade" ao "f1-game/nodes/game/player/asset/origin" "fmt" "math/rand/v2" ctime "github.com/cherry-game/cherry/extend/time" cfacade "github.com/cherry-game/cherry/facade" cproto "github.com/cherry-game/cherry/net/proto" ) var ( kickRsp = &pb.I32{ Value: code.AccountLoginTimeout, } ) // actorPlayer 每个玩家一个PlayerActor type actorPlayer struct { capp.ActorLogger playerID int64 // 当前玩家id isOnline bool // 玩家是否在线 now *ctime.CherryTime // 玩家当前时间 loggerPrefix string // logger prefix funcLimiter rate.FuncLimiter // 函数访问限速 funcErrCode int32 // 函数访问的错误码 } func newActorPlayer(playerID int64) actorPlayer { return actorPlayer{ ActorLogger: capp.NewLoggerPrefix("playerID", playerID), playerID: playerID, isOnline: false, now: nil, loggerPrefix: fmt.Sprintf("[playerID = %d] ", playerID), funcLimiter: rate.NewFuncLimiter(30, 3, code.NodeRateLimiter), funcErrCode: code.NodeRateLimiter, } } func (p *actorPlayer) OnInit() { p.updateNowTime() p.initRateLimits() p.initRemote() p.initPlayer() p.initGM() p.initCharge() p.initItem() p.initQuest() p.initHero() p.initEquip() p.initRecruit() p.initLineup() p.initAsset() p.initFriend() p.initChat() p.initChapter() p.initShop() p.initLord() p.initMarquee() p.initVip() } func (p *actorPlayer) initRemote() { p.Remote().Register(nameRemote.Player_OnEnter, p.enter) p.Remote().Register(nameRemote.Player_OnSessionClosed, p.onSessionClosed) p.Remote().Register(nameRemote.Player_OnCheckExit, p.onCheckExit) } func (p *actorPlayer) OnStop() { p.Info("[actorPlayer] OnStop() playerID = %d", p.playerID) p.Timer().RemoveAll() p.ActorLogger.Destory() } func (p *actorPlayer) OnLocalReceived(m *cfacade.Message) (bool, bool) { if !p.isOnline { p.Kick(m.Session, kickRsp, true) return false, false } p.funcErrCode = p.funcLimiter.Check(m.BuildTime, m.FuncName) if code.IsFail(p.funcErrCode) { p.ResponseCode(m.Session, p.funcErrCode) return false, false } return true, false } func (p *actorPlayer) updateNowTime() { now := ctime.Now() p.now = &now } func (p *actorPlayer) initRateLimits() { //p.limiter.Add(playerName.ChatChannelList, chatConst.SendFrequency, code.ChatGetListTooFast) //p.limiter.Add(playerName.ChatSend, chatConst.SendFrequency, code.ChatSendTextTooFast) // 举报频率限制 p.funcLimiter.Add(nameLocal.Player_Complaint, constant.ComplaintFrequency, 1, code.ComplaintTooFast) } // enter 玩家进入游戏 func (p *actorPlayer) enter(session *cproto.Session) { p.updateNowTime() p.playerID = sessions.GetPlayerID(session) isNewPlayer := session.Contains(sessions.IsNewPlayer) if isNewPlayer { // TODO 随机名字功能应该由客户端实现 isMale := rand.Float32() < 0.5 name := data.LordName.RandomName(isMale) playerTable, ok := db.NewPlayer(p.playerID, session, name) if !ok { p.ResponseCode(session, code.PlayerCreateFail) return } // 抛出玩家创建事件 p.PostEvent(event.NewPlayerCreate(playerTable.PlayerID, playerTable.Lord.Name)) } else { _, found := db.GetPlayerTable(p.playerID) if !found { p.ResponseCode(session, code.PlayerIDError) return } } p.Debug("[onEnter] sid = %s, isNewPlayer = %v", session.Sid, isNewPlayer) playerTable := p.PlayerTable() // TODO 先放这里,后面再看看放哪里合理 if isNewPlayer { p.onNewPlayer() } p.onReset(playerTable, true) gameFacade.ServiceOnLogin(playerTable, session) p.onEnterResponse(playerTable, session) gameFacade.ServiceOnLogined(playerTable, session) } func (p *actorPlayer) onNewPlayer() { // 添加初始道具 for _, asset := range data.Const.PlayerInitItems { gameFacade.Asset.Add(p.playerID, asset.ID, asset.Num, ao.CreatePlayer, false) } // 添加初始英雄 for _, asset := range data.Const.PlayerInitHeros { gameFacade.Hero.AddOne(p.playerID, asset.ID, int32(asset.Num), ao.CreatePlayer, false) } // 添加初始装备 for _, asset := range data.Const.PlayerInitEquips { gameFacade.Equip.Add(p.playerID, asset.ID, asset.Num, ao.CreatePlayer, false) } } // logout gate发送网络断开请求 func (p *actorPlayer) onSessionClosed() { p.Info("[onLogout] playerID = %d", p.playerID) // 不在线则忽略 if !p.isOnline { return } // 设置为离线状态 // 必需设置为 false,如果玩家被顶号,actor 不会被销毁,不设置会导致玩家无法登录 p.isOnline = false //移除当前玩家所有定时器 p.Timer().RemoveAll() if playerTable := p.PlayerTable(); playerTable != nil { gameFacade.ServiceOnLogout(playerTable) } p.Info("[onLogout] finished! playerID = %d", p.playerID) } // 父actor检测到当前子actor很久收到消息,确认是否要退出 func (p *actorPlayer) onCheckExit() { // 不在线则销毁当前actor if !p.isOnline { p.Exit() } } // setSession 设置gate的session属性 func (p *actorPlayer) setSession(playerTable *db.PlayerTable, session *cproto.Session) { // 更新gate节点的session sessionProto := pb.StrStrList{} sessionProto.Add(sessions.GameNodeID, p.App().NodeID()) sessionProto.Add(sessions.PlayerID, p.playerID) if playerTable.Map.MapNodeID != "" { sessionProto.Add(sessions.MapNodeID, playerTable.Map.MapNodeID) } actorRemote.GateUser.SetSession(session.ActorPath(), &sessionProto) } // onReset 重置处理(每次登录、跨天触发) func (p *actorPlayer) onReset(playerTable *db.PlayerTable, byLogin bool) { playerTable.Stats.CheckReset(p.now) // 无需重置,则跳过 if !playerTable.Stats.IsCanReset() { return } p.Debug("[onReset] playerID = %d", p.playerID) // 各系统模块执行重置 gameFacade.ServiceOnReset(playerTable, p.now, byLogin) // 发送跨天重置事件 p.PostEvent(event.NewPlayerReset(p.playerID)) p.Warn("daily reset finish. playerID=%d", p.playerID) } // onEnterEnd 推送各模块数据给客户端 func (p *actorPlayer) onEnterResponse(playerTable *db.PlayerTable, session *cproto.Session) { // 推送数据列表 gameFacade.ServicePush(playerTable, session) // 推送完各个模块数据后再设置session p.setSession(playerTable, session) // 推送 玩家进入游戏响应结果 客户端收到表示登录数据推送完成 playerPB := playerTable.ToPlayerProto() response := &pb.PlayerEnterResponse{ Player: &playerPB, } p.Response(session, response) // 登录完成后,设置玩家在线状态 p.isOnline = true // 在线状态设置后,再启动定时器 p.startTimer() // 提交 玩家登录事件 p.PostEvent(event.NewPlayerLogin(p.playerID, session)) p.Debug("[onEnterResponse]") }