diff --git a/gs/game/command_controller.go b/gs/game/command_controller.go new file mode 100644 index 00000000..bab561cb --- /dev/null +++ b/gs/game/command_controller.go @@ -0,0 +1,17 @@ +package game + +import "fmt" + +// HelpCommand 帮助命令 +func (c *CommandManager) HelpCommand(cmd *Command) { + c.gameManager.SendPrivateChat(c.system, cmd.Executor, + "===== 帮助 / Help =====\n"+ + "以后再写awa\n", + ) +} + +// OpCommand 帮助命令 +func (c *CommandManager) OpCommand(cmd *Command) { + cmd.Executor.IsGM = 1 + c.gameManager.SendPrivateChat(c.system, cmd.Executor, fmt.Sprintf("权限修改完毕, 现在你是GM啦 %v", cmd.Args)) +} diff --git a/gs/game/command_manager.go b/gs/game/command_manager.go index 171b3aac..7e92d0ad 100644 --- a/gs/game/command_manager.go +++ b/gs/game/command_manager.go @@ -1,23 +1,35 @@ package game import ( + "fmt" "hk4e/gs/model" "hk4e/logger" "hk4e/protocol/proto" "strings" ) -type CommandFunc func(string, []string) +// CommandPerm 命令权限等级 +// 0 为普通玩家 数越大权限越大 +type CommandPerm uint8 +// CommandFunc 命令执行函数 +type CommandFunc func(*Command) + +// Command 命令结构体 +// 给下层执行命令时提供数据 type Command struct { - Name string // 命令前缀 - Args []string // 命令参数 + Executor *model.Player // 执行者 + Text string // 命令原始文本 + Name string // 命令前缀 + Args []string // 命令参数 } +// CommandManager 命令管理器 type CommandManager struct { system *model.Player // 机器人 目前负责收发命令 以及 大世界 commandFuncRouter map[string]CommandFunc // 记录命令处理函数 - commandTextInput chan string // 传输要处理的命令文本 + commandPermMap map[string]CommandPerm // 记录命令对应的权限 + commandTextInput chan *Command // 传输要处理的命令文本 gameManager *GameManager } @@ -35,11 +47,10 @@ func NewCommandManager(g *GameManager) *CommandManager { g.worldManager.InitBigWorld(r.system) // 初始化 - r.commandTextInput = make(chan string, 1000) + r.commandTextInput = make(chan *Command, 1000) r.InitRouter() // 初始化路由 - // 处理传入的命令 - go r.HandleCommand() + go r.HandleCommand() // 处理传入的命令 r.gameManager = g return r @@ -48,24 +59,39 @@ func NewCommandManager(g *GameManager) *CommandManager { // InitRouter 初始化命令路由 func (c *CommandManager) InitRouter() { c.commandFuncRouter = make(map[string]CommandFunc) - c.RegisterRouter("awa", c.FuncAwA) + c.commandPermMap = make(map[string]CommandPerm) + { + // 权限等级 0: 普通玩家 + c.RegisterRouter("help", 0, c.HelpCommand) + c.RegisterRouter("op", 0, c.OpCommand) + } + // GM命令 + { + // 权限等级 1: GM 1级 + c.RegisterRouter("nmsl", 1, c.HelpCommand) + } } // RegisterRouter 注册命令路由 -func (c *CommandManager) RegisterRouter(cmdName string, cmdFunc CommandFunc) { +func (c *CommandManager) RegisterRouter(cmdName string, cmdPerm CommandPerm, cmdFunc CommandFunc) { + // 命令名统一转为小写 + cmdName = strings.ToLower(cmdName) + // 记录命令 c.commandFuncRouter[cmdName] = cmdFunc -} - -func (c *CommandManager) FuncAwA(cmd string, args []string) { - logger.LOG.Info("awa命令执行啦, name: %v, args: %v", cmd, args) + c.commandPermMap[cmdName] = cmdPerm } // InputCommand 输入要处理的命令 -func (c *CommandManager) InputCommand(text string) { +func (c *CommandManager) InputCommand(executor *model.Player, text string) { // 机器人不会读命令所以写到了 PrivateChatReq - // 输入的命令将在其他协程中处理 - c.commandTextInput <- text + // 确保消息文本为 / 开头 + // 如果不为这个开头那接下来就毫无意义 + if strings.HasPrefix(text, "/") { + + // 输入的命令将在其他协程中处理 + c.commandTextInput <- c.NewCommand(executor, text) + } } // GetFriendList 获取包含系统的玩家好友列表 @@ -85,69 +111,69 @@ func (c *CommandManager) GetFriendList(friendList map[uint32]bool) map[uint32]bo return tempFriendList } +// NewCommand 创建命令结构 +func (c *CommandManager) NewCommand(executor *model.Player, text string) *Command { + // 将开头的 / 去掉 并 分割出命令的每个参数 + // 不区分命令的大小写 统一转为小写 + cmdSplit := strings.Split(strings.ToLower(text[1:]), " ") + + var cmdName string // 命令名 + var cmdArgs []string // 命令参数 + + // 分割出来啥也没有可能是个空的字符串 + // 此时将会返回的命令名和命令参数都为空 + if len(cmdSplit) != 0 { + // 首个参数必是命令名 + cmdName = cmdSplit[0] + // 命令名后当然是命令的参数喽 + cmdArgs = cmdSplit[1:] + } + + return &Command{executor, text, cmdName, cmdArgs} +} + // 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 - } + // 取出要执行的命令 + cmd := <-c.commandTextInput // 执行命令 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 - } + player := cmd.Executor - // 判断命令名是否注册 + // 判断命令是否注册 cmdFunc, ok := c.commandFuncRouter[cmd.Name] if !ok { - logger.LOG.Error("exec command not exist, name: %v", cmd.Name) + // 玩家可能会执行一些没有的命令仅做调试输出 + logger.LOG.Debug("exec command not exist, name: %v", cmd.Name) + c.gameManager.SendPrivateChat(c.system, player, "命令不存在,输入 /help 查看帮助。") + return + } + // 判断命令权限是否注册 + cmdPerm, ok := c.commandPermMap[cmd.Name] + if !ok { + // 一般命令权限都会注册 没注册则报error错误 + logger.LOG.Error("exec command permission 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) + // 判断玩家的权限是否符合要求 + if player.IsGM < uint8(cmdPerm) { + logger.LOG.Debug("exec command permission denied, uid: %v, isGM: %v", player.PlayerID, player.IsGM) + c.gameManager.SendPrivateChat(c.system, player, fmt.Sprintf("权限不足,该命令需要%v级权限。\n你目前的权限等级:%v", cmdPerm, player.IsGM)) + return + } + + logger.LOG.Debug("command start, uid: %v, text: %v, name: %v, args: %v", cmd.Executor.PlayerID, cmd.Text, cmd.Name, cmd.Args) + cmdFunc(cmd) // 执行命令 + logger.LOG.Debug("command done, uid: %v, text: %v, name: %v, args: %v", cmd.Executor.PlayerID, cmd.Text, cmd.Name, cmd.Args) } diff --git a/gs/game/user_chat.go b/gs/game/user_chat.go index 450648f8..b6d2c7d7 100644 --- a/gs/game/user_chat.go +++ b/gs/game/user_chat.go @@ -75,43 +75,28 @@ func (g *GameManager) PullPrivateChatReq(player *model.Player, payloadMsg pb.Mes g.SendMsg(cmd.PullPrivateChatRsp, player.PlayerID, 0, pullPrivateChatRsp) } -func (g *GameManager) PrivateChatReq(player *model.Player, payloadMsg pb.Message) { - logger.LOG.Debug("user send private chat, uid: %v", player.PlayerID) - req := payloadMsg.(*proto.PrivateChatReq) - targetUid := req.TargetUid - content := req.Content - - // TODO 同步阻塞待优化 - targetPlayer := g.userManager.LoadTempOfflineUserSync(targetUid) - if targetPlayer == nil { - return - } +// SendPrivateChat 发送私聊文本消息给玩家 +func (g *GameManager) SendPrivateChat(player, targetPlayer *model.Player, content any) { chatInfo := &proto.ChatInfo{ Time: uint32(time.Now().Unix()), Sequence: 101, ToUid: targetPlayer.PlayerID, Uid: player.PlayerID, IsRead: false, - Content: nil, } + + // 根据传入的值判断消息类型 switch content.(type) { - case *proto.PrivateChatReq_Text: - text := content.(*proto.PrivateChatReq_Text).Text - if len(text) == 0 { - return - } + case string: + // 文本消息 chatInfo.Content = &proto.ChatInfo_Text{ - Text: text, + Text: content.(string), } - // 输入命令 会检测是否为命令的 - g.commandManager.InputCommand(text) - case *proto.PrivateChatReq_Icon: - icon := content.(*proto.PrivateChatReq_Icon).Icon + case int, int32, uint32: + // 图标消息 chatInfo.Content = &proto.ChatInfo_Icon{ - Icon: icon, + Icon: content.(uint32), } - default: - return } // 消息加入自己的队列 @@ -121,6 +106,7 @@ func (g *GameManager) PrivateChatReq(player *model.Player, payloadMsg pb.Message } msgList = append(msgList, g.ConvChatInfoToChatMsg(chatInfo)) player.ChatMsgMap[targetPlayer.PlayerID] = msgList + // 消息加入目标玩家的队列 msgList, exist = targetPlayer.ChatMsgMap[player.PlayerID] if !exist { @@ -129,6 +115,7 @@ func (g *GameManager) PrivateChatReq(player *model.Player, payloadMsg pb.Message msgList = append(msgList, g.ConvChatInfoToChatMsg(chatInfo)) targetPlayer.ChatMsgMap[player.PlayerID] = msgList + // 如果目标玩家在线发送消息 if targetPlayer.Online { // PacketPrivateChatNotify privateChatNotify := new(proto.PrivateChatNotify) @@ -140,6 +127,43 @@ func (g *GameManager) PrivateChatReq(player *model.Player, payloadMsg pb.Message privateChatNotify := new(proto.PrivateChatNotify) privateChatNotify.ChatInfo = chatInfo g.SendMsg(cmd.PrivateChatNotify, player.PlayerID, 0, privateChatNotify) +} + +func (g *GameManager) PrivateChatReq(player *model.Player, payloadMsg pb.Message) { + logger.LOG.Debug("user send private chat, uid: %v", player.PlayerID) + req := payloadMsg.(*proto.PrivateChatReq) + targetUid := req.TargetUid + content := req.Content + + // TODO 同步阻塞待优化 + targetPlayer := g.userManager.LoadTempOfflineUserSync(targetUid) + if targetPlayer == nil { + return + } + + // 根据发送的类型发送消息 + switch content.(type) { + case *proto.PrivateChatReq_Text: + text := content.(*proto.PrivateChatReq_Text).Text + if len(text) == 0 { + return + } + + // 发送私聊文本消息 + g.SendPrivateChat(player, targetPlayer, text) + + // 输入命令 会检测是否为命令的 + g.commandManager.InputCommand(player, text) + + case *proto.PrivateChatReq_Icon: + icon := content.(*proto.PrivateChatReq_Icon).Icon + + // 发送私聊图标消息 + g.SendPrivateChat(player, targetPlayer, icon) + + default: + return + } // PacketPrivateChatRsp privateChatRsp := new(proto.PrivateChatRsp) diff --git a/gs/model/player.go b/gs/model/player.go index 530aa55d..00216898 100644 --- a/gs/model/player.go +++ b/gs/model/player.go @@ -50,6 +50,7 @@ type Player struct { DropInfo *DropInfo `bson:"dropInfo"` // 掉落信息 MainCharAvatarId uint32 `bson:"mainCharAvatarId"` // 主角id ChatMsgMap map[uint32][]*ChatMsg `bson:"chatMsgMap"` // 聊天信息 + IsGM uint8 `bson:"isGM"` // 管理员权限等级 // 在线数据 EnterSceneToken uint32 `bson:"-"` // 玩家的世界进入令牌 DbState int `bson:"-"` // 数据库存档状态