diff --git a/gs/game/command_manager.go b/gs/game/command_manager.go new file mode 100644 index 00000000..171b3aac --- /dev/null +++ b/gs/game/command_manager.go @@ -0,0 +1,153 @@ +package game + +import ( + "hk4e/gs/model" + "hk4e/logger" + "hk4e/protocol/proto" + "strings" +) + +type CommandFunc func(string, []string) + +type Command struct { + Name string // 命令前缀 + Args []string // 命令参数 +} + +type CommandManager struct { + system *model.Player // 机器人 目前负责收发命令 以及 大世界 + commandFuncRouter map[string]CommandFunc // 记录命令处理函数 + commandTextInput chan string // 传输要处理的命令文本 + + gameManager *GameManager +} + +// NewCommandManager 新建命令管理器 +func NewCommandManager(g *GameManager) *CommandManager { + r := new(CommandManager) + + // 创建一个公共的开放世界的AI + g.OnRegOk(false, &proto.SetPlayerBornDataReq{AvatarId: 10000007, NickName: "System"}, 1, 0) + r.system = g.userManager.GetOnlineUser(1) + // 开放大世界 + r.system.SceneLoadState = model.SceneEnterDone + r.system.DbState = model.DbNormal + g.worldManager.InitBigWorld(r.system) + + // 初始化 + r.commandTextInput = make(chan string, 1000) + r.InitRouter() // 初始化路由 + + // 处理传入的命令 + go r.HandleCommand() + + r.gameManager = g + return r +} + +// InitRouter 初始化命令路由 +func (c *CommandManager) InitRouter() { + c.commandFuncRouter = make(map[string]CommandFunc) + c.RegisterRouter("awa", c.FuncAwA) +} + +// RegisterRouter 注册命令路由 +func (c *CommandManager) RegisterRouter(cmdName string, cmdFunc CommandFunc) { + c.commandFuncRouter[cmdName] = cmdFunc +} + +func (c *CommandManager) FuncAwA(cmd string, args []string) { + logger.LOG.Info("awa命令执行啦, name: %v, args: %v", cmd, args) +} + +// InputCommand 输入要处理的命令 +func (c *CommandManager) InputCommand(text string) { + // 机器人不会读命令所以写到了 PrivateChatReq + + // 输入的命令将在其他协程中处理 + c.commandTextInput <- text +} + +// GetFriendList 获取包含系统的玩家好友列表 +func (c *CommandManager) GetFriendList(friendList map[uint32]bool) map[uint32]bool { + // 可能还有更好的方法实现这功能 + // 但我想不出来awa + + // 临时好友列表 + tempFriendList := make(map[uint32]bool, len(friendList)) + // 复制玩家的好友列表 + for userId, b := range friendList { + tempFriendList[userId] = b + } + // 添加系统 + tempFriendList[c.system.PlayerID] = true + + return tempFriendList +} + +// HandleCommand 处理命令 +func (c *CommandManager) HandleCommand() { + // 处理传入 commandTextInput 的所有命令文本 + // 为了避免主协程阻塞搞了个channel + + for { + // 取出要处理的命令文本 + text := <-c.commandTextInput + + // 读取并创建命令 + cmd := c.NewCommand(text) + if cmd == nil { + logger.LOG.Error("handle command is nil, text: %v", text) + continue + } + + // 执行命令 + c.ExecCommand(cmd) + } +} + +// NewCommand 创建命令结构 +func (c *CommandManager) NewCommand(text string) *Command { + // 命令必须以 / 为开头 + if !strings.HasPrefix(text, "/") { + return nil + } + // 将开头的 / 去掉 + text = text[1:] + + // 分割出命令的每个参数 + cmdSplit := strings.Split(text, " ") + + // 分割出来啥也没有可能是个空的字符串 + // 处理个寂寞直接return + if len(cmdSplit) == 0 { + return nil + } + + // 首个参数必是命令名 + cmdName := cmdSplit[0] + // 命令名后当然是命令的参数喽 + cmdArgs := cmdSplit[1:] + + return &Command{cmdName, cmdArgs} +} + +// ExecCommand 执行命令 +func (c *CommandManager) ExecCommand(cmd *Command) { + // 理论上执行前已经校验过不会出现nil 但以免万一 + if cmd == nil { + logger.LOG.Error("exec command is nil") + return + } + + // 判断命令名是否注册 + cmdFunc, ok := c.commandFuncRouter[cmd.Name] + if !ok { + logger.LOG.Error("exec command not exist, name: %v", cmd.Name) + return + } + + logger.LOG.Debug("command start, name: %v args: %v", cmd.Name, cmd.Args) + cmdFunc(cmd.Name, cmd.Args) // 执行命令 + logger.LOG.Debug("command done, name: %v args: %v", cmd.Name, cmd.Args) +} diff --git a/gs/game/game_manager.go b/gs/game/game_manager.go index a0dd4b55..46f8341a 100644 --- a/gs/game/game_manager.go +++ b/gs/game/game_manager.go @@ -6,7 +6,6 @@ import ( "hk4e/gate/entity/gm" "hk4e/gate/kcp" "hk4e/gs/dao" - "hk4e/gs/model" "hk4e/logger" "hk4e/protocol/cmd" "hk4e/protocol/proto" @@ -27,6 +26,8 @@ type GameManager struct { worldManager *WorldManager // 游戏服务器定时帧管理器 tickManager *TickManager + // 命令管理器 + commandManager *CommandManager } func NewGameManager(dao *dao.Dao, netMsgInput chan *cmd.NetMsg, netMsgOutput chan *cmd.NetMsg) (r *GameManager) { @@ -40,13 +41,7 @@ func NewGameManager(dao *dao.Dao, netMsgInput chan *cmd.NetMsg, netMsgOutput cha r.userManager = NewUserManager(dao, r.localEventManager.localEventChan) r.worldManager = NewWorldManager(r.snowflake) r.tickManager = NewTickManager(r) - - // 创建一个公共的开放世界的AI - r.OnRegOk(false, &proto.SetPlayerBornDataReq{AvatarId: 10000007, NickName: "大世界的主人"}, 1, 0) - bigWorldOwner := r.userManager.GetOnlineUser(1) - bigWorldOwner.SceneLoadState = model.SceneEnterDone - bigWorldOwner.DbState = model.DbNormal - r.worldManager.InitBigWorld(bigWorldOwner) + r.commandManager = NewCommandManager(r) return r } @@ -72,7 +67,14 @@ func (g *GameManager) Start() { } func (g *GameManager) Stop() { - g.worldManager.worldStatic.SaveTerrain() + // 踢出所有在线玩家 + for userId := range g.userManager.GetAllOnlineUserList() { + g.DisconnectPlayer(userId) + } + // 保存玩家数据 + g.userManager.SaveUser() + + //g.worldManager.worldStatic.SaveTerrain() } // 发送消息给客户端 diff --git a/gs/game/user_chat.go b/gs/game/user_chat.go index 8e044171..450648f8 100644 --- a/gs/game/user_chat.go +++ b/gs/game/user_chat.go @@ -103,6 +103,8 @@ func (g *GameManager) PrivateChatReq(player *model.Player, payloadMsg pb.Message chatInfo.Content = &proto.ChatInfo_Text{ Text: text, } + // 输入命令 会检测是否为命令的 + g.commandManager.InputCommand(text) case *proto.PrivateChatReq_Icon: icon := content.(*proto.PrivateChatReq_Icon).Icon chatInfo.Content = &proto.ChatInfo_Icon{ diff --git a/gs/game/user_manager.go b/gs/game/user_manager.go index 67f714f7..299dea09 100644 --- a/gs/game/user_manager.go +++ b/gs/game/user_manager.go @@ -241,60 +241,64 @@ func (u *UserManager) StartAutoSaveUser() { go func() { ticker := time.NewTicker(time.Minute * 5) for { - logger.LOG.Info("auto save user start") - playerMapTemp := make(map[uint32]*model.Player) - u.playerMapLock.RLock() - for k, v := range u.playerMap { - playerMapTemp[k] = v - } - u.playerMapLock.RUnlock() - logger.LOG.Info("copy user map finish") - insertList := make([]*model.Player, 0) - deleteList := make([]uint32, 0) - updateList := make([]*model.Player, 0) - for k, v := range playerMapTemp { - switch v.DbState { - case model.DbInsert: - insertList = append(insertList, v) - playerMapTemp[k].DbState = model.DbNormal - case model.DbDelete: - deleteList = append(deleteList, v.PlayerID) - delete(playerMapTemp, k) - case model.DbUpdate: - updateList = append(updateList, v) - playerMapTemp[k].DbState = model.DbNormal - case model.DbNormal: - continue - case model.DbOffline: - updateList = append(updateList, v) - delete(playerMapTemp, k) - } - } - insertListJson, err := json.Marshal(insertList) - logger.LOG.Debug("insertList: %v", string(insertListJson)) - deleteListJson, err := json.Marshal(deleteList) - logger.LOG.Debug("deleteList: %v", string(deleteListJson)) - updateListJson, err := json.Marshal(updateList) - logger.LOG.Debug("updateList: %v", string(updateListJson)) - logger.LOG.Info("db state init finish") - err = u.dao.InsertPlayerList(insertList) - if err != nil { - logger.LOG.Error("insert player list error: %v", err) - } - err = u.dao.DeletePlayerList(deleteList) - if err != nil { - logger.LOG.Error("delete player error: %v", err) - } - err = u.dao.UpdatePlayerList(updateList) - if err != nil { - logger.LOG.Error("update player error: %v", err) - } - logger.LOG.Info("db write finish") - u.playerMapLock.Lock() - u.playerMap = playerMapTemp - u.playerMapLock.Unlock() - logger.LOG.Info("auto save user finish") + u.SaveUser() <-ticker.C } }() } + +func (u *UserManager) SaveUser() { + logger.LOG.Info("auto save user start") + playerMapTemp := make(map[uint32]*model.Player) + u.playerMapLock.RLock() + for k, v := range u.playerMap { + playerMapTemp[k] = v + } + u.playerMapLock.RUnlock() + logger.LOG.Info("copy user map finish") + insertList := make([]*model.Player, 0) + deleteList := make([]uint32, 0) + updateList := make([]*model.Player, 0) + for k, v := range playerMapTemp { + switch v.DbState { + case model.DbInsert: + insertList = append(insertList, v) + playerMapTemp[k].DbState = model.DbNormal + case model.DbDelete: + deleteList = append(deleteList, v.PlayerID) + delete(playerMapTemp, k) + case model.DbUpdate: + updateList = append(updateList, v) + playerMapTemp[k].DbState = model.DbNormal + case model.DbNormal: + continue + case model.DbOffline: + updateList = append(updateList, v) + delete(playerMapTemp, k) + } + } + insertListJson, err := json.Marshal(insertList) + logger.LOG.Debug("insertList: %v", string(insertListJson)) + deleteListJson, err := json.Marshal(deleteList) + logger.LOG.Debug("deleteList: %v", string(deleteListJson)) + updateListJson, err := json.Marshal(updateList) + logger.LOG.Debug("updateList: %v", string(updateListJson)) + logger.LOG.Info("db state init finish") + err = u.dao.InsertPlayerList(insertList) + if err != nil { + logger.LOG.Error("insert player list error: %v", err) + } + err = u.dao.DeletePlayerList(deleteList) + if err != nil { + logger.LOG.Error("delete player error: %v", err) + } + err = u.dao.UpdatePlayerList(updateList) + if err != nil { + logger.LOG.Error("update player error: %v", err) + } + logger.LOG.Info("db write finish") + u.playerMapLock.Lock() + u.playerMap = playerMapTemp + u.playerMapLock.Unlock() + logger.LOG.Info("auto save user finish") +} diff --git a/gs/game/user_social.go b/gs/game/user_social.go index 05754e68..ab9405af 100644 --- a/gs/game/user_social.go +++ b/gs/game/user_social.go @@ -143,7 +143,12 @@ func (g *GameManager) GetPlayerFriendListReq(player *model.Player, payloadMsg pb // PacketGetPlayerFriendListRsp getPlayerFriendListRsp := new(proto.GetPlayerFriendListRsp) getPlayerFriendListRsp.FriendList = make([]*proto.FriendBrief, 0) - for uid := range player.FriendList { + + // 获取包含系统的临时好友列表 + // 用于实现好友列表内的系统且不更改原先的内容 + tempFriendList := g.commandManager.GetFriendList(player.FriendList) + + for uid := range tempFriendList { // TODO 同步阻塞待优化 var onlineState proto.FriendOnlineState online := g.userManager.GetUserOnlineState(uid) @@ -265,6 +270,17 @@ func (g *GameManager) AskAddFriendReq(player *model.Player, payloadMsg pb.Messag g.SendMsg(cmd.AskAddFriendRsp, player.PlayerID, player.ClientSeq, askAddFriendRsp) } +func (g *GameManager) AddFriend(player *model.Player, targetUid uint32) { + player.FriendList[targetUid] = true + // TODO 同步阻塞待优化 + targetPlayer := g.userManager.LoadTempOfflineUserSync(targetUid) + if targetPlayer == nil { + logger.LOG.Error("agree friend apply target player is nil, uid: %v", player.PlayerID) + return + } + targetPlayer.FriendList[player.PlayerID] = true +} + func (g *GameManager) DealAddFriendReq(player *model.Player, payloadMsg pb.Message) { logger.LOG.Debug("user deal friend apply, uid: %v", player.PlayerID) req := payloadMsg.(*proto.DealAddFriendReq) @@ -272,14 +288,7 @@ func (g *GameManager) DealAddFriendReq(player *model.Player, payloadMsg pb.Messa result := req.DealAddFriendResult if result == proto.DealAddFriendResultType_DEAL_ADD_FRIEND_RESULT_TYPE_ACCEPT { - player.FriendList[targetUid] = true - // TODO 同步阻塞待优化 - targetPlayer := g.userManager.LoadTempOfflineUserSync(targetUid) - if targetPlayer == nil { - logger.LOG.Error("agree friend apply target player is nil, uid: %v", player.PlayerID) - return - } - targetPlayer.FriendList[player.PlayerID] = true + g.AddFriend(player, targetUid) } delete(player.FriendApplyList, targetUid)