package player import ( actorRemote "f1-game/internal/actor_remote" "f1-game/internal/code" "f1-game/internal/component/redis" "f1-game/internal/constant" "f1-game/internal/data" "f1-game/internal/enum" "f1-game/internal/event" "f1-game/internal/extend/utils" nameEvent "f1-game/internal/name/event" nameLocal "f1-game/internal/name/local" nameRemote "f1-game/internal/name/remote" nameRoute "f1-game/internal/name/route" "f1-game/internal/pb" "f1-game/internal/sessions" "f1-game/nodes/game/internal/db" dbChat "f1-game/nodes/game/internal/db/data/chat" "f1-game/nodes/game/player/asset" ao "f1-game/nodes/game/player/asset/origin" "f1-game/nodes/game/player/chat" csnowflake "github.com/cherry-game/cherry/extend/snowflake" ctime "github.com/cherry-game/cherry/extend/time" cfacade "github.com/cherry-game/cherry/facade" cproto "github.com/cherry-game/cherry/net/proto" ) func (p *actorPlayer) initChat() { p.Local().Register(nameLocal.Player_ChatSend, p.chatSend) p.Local().Register(nameLocal.Player_ChatMsgList, p.chatMsgList) p.Local().Register(nameLocal.Player_ChatGroupCreate, p.chatGroupCreate) p.Local().Register(nameLocal.Player_ChatGroupPlayerList, p.chatGroupPlayerList) p.Local().Register(nameLocal.Player_ChatGroupPlayerChange, p.chatGroupPlayerChange) p.Local().Register(nameLocal.Player_ChatGroupDismiss, p.chatGroupDismiss) p.Local().Register(nameLocal.Player_ChatGroupInviteConfirm, p.chatGroupInviteConfirm) p.Local().Register(nameLocal.Player_ChatGroupQuit, p.chatGroupQuit) p.Local().Register(nameLocal.Player_ChatGroupMute, p.chatGroupMute) p.Local().Register(nameLocal.Player_ChatPrivateSend, p.chatPrivateSend) p.Local().Register(nameLocal.Player_ChatPrivateMsgList, p.chatPrivateMsgList) p.Local().Register(nameLocal.Player_ChatSetting, p.chatSetting) p.Local().Register(nameLocal.Player_Complaint, p.complaint) p.Remote().Register(nameRemote.Player_ChatGroupInvite, p.chatGroupInvite) p.Remote().Register(nameRemote.Player_ChatGroupMemberRemove, p.chatGroupMemberRemove) p.Remote().Register(nameRemote.Player_ChatPrivateMsgSend, p.chatPrivateMsgSend) p.Remote().Register(nameRemote.Player_ChatRoomUpdate, p.chatRoomUpdate) p.Remote().Register(nameRemote.Player_ChatMapSystemMsg, p.chatMapSystemMsg) p.EventRegister(nameEvent.Chat_SystemMsg, p.chatSystemMsg) } func (p *actorPlayer) chatSend(session *cproto.Session, req *pb.ChatSend) { if req.ChatID < 1 || req.ChatType < 1 { p.ResponseCode(session, code.IllegalArgument) return } if !enum.CheckChatType(req.ChatType) { p.ResponseCode(session, code.ChatSendTypeError) return } var ( chatTable = p.ChatTable() chatType = enum.ChatType(req.ChatType) ) chatRoom, found := chatTable.GetChatRoom(chatType, req.ChatID) if !found { p.ResponseCode(session, code.ChatChannelNotAvailable) return } var ( playerTable = p.PlayerTable() chatTypeConfig, _ = data.ChatType.GetByChatType(chatType) ) if chatTypeConfig.SendOpenLv > 0 && playerTable.Lord.Level < chatTypeConfig.SendOpenLv { p.ResponseCode(session, code.ChatSendLvNotEnough) return } // 过滤换行回车和屏蔽词 req.Text = data.MaskWord.Replace(req.Text) if len(req.Text) < 1 { p.ResponseCode(session, code.ChatSendTextIsEmpty) return } // 检查长度限制 if !utils.CheckStrLen(req.Text, chatTypeConfig.TextLen) { p.ResponseCode(session, code.ChatSendTextTooLong) return } var ( nowTime = ctime.Now() lastSendTime = ctime.NewSecond(chatRoom.LastSendTime) ) // 发言频率限制 if chatTypeConfig.SendRate > 0 && nowTime.DiffInSecondsWithAbs(&lastSendTime) < chatTypeConfig.SendRate { p.ResponseCode(session, code.ChatSendTextTooFast) return } // 检查免费发送次数是否已经用完 if chatTypeConfig.FreeSendCount > 0 && chatRoom.FreeSendCount() < 1 { // 消耗的道具 _, errCode := asset.Service().Sub(p.playerID, chatTypeConfig.SendCost.ID, chatTypeConfig.SendCost.Num, ao.ChatSendCost) if code.IsFail(errCode) { p.ResponseCode(session, errCode) return } } chatRoom.UpdateSendCount(nowTime.ToSecond()) chatTable.Save2Queue() p.Response(session, &pb.I32{ Value: chatRoom.FreeSendCount(), }) // 从redis获取玩家聊天消息数据 chatPlayer := redis.Game.GetChatPlayer(p.playerID) // 构建消息,推送到chat节点 msgListProto := chat.Service().BuildMsgListProto(chatPlayer, req, nowTime.ToSecond()) actorRemote.Chat.Send(msgListProto) } // 获取某个聊天室的消息列表 func (p *actorPlayer) chatMsgList(session *cproto.Session, req *pb.ChatMsgRequest) { if req.ChatID < 1 || req.ChatType < 1 || req.LastMsgID < 0 { p.ResponseCode(session, code.IllegalArgument) return } if !enum.CheckChatType(req.ChatType) { p.ResponseCode(session, code.IllegalArgument) return } var ( chatType = enum.ChatType(req.ChatType) chatID = req.ChatID lastMsgID = req.LastMsgID ) // 其他频道,检查频道聊天室是否存在 if !p.ChatTable().IsChatRoomExist(chatType, chatID) { p.ResponseCode(session, code.ChatGroupNotFound) return } rsp := redis.Chat.GetChatMsgList(chatType, chatID, lastMsgID) p.Response(session, rsp) } // 获取某个群组的玩家列表 func (p *actorPlayer) chatGroupPlayerList(session *cproto.Session, req *pb.I64) { if req.Value < 1 { p.ResponseCode(session, code.IllegalArgument) return } var ( chatID = req.Value // 聊天室ID chatTable = p.ChatTable() ) if !chatTable.IsChatRoomExist(enum.ChatType_Group, chatID) { p.ResponseCode(session, code.ChatGroupNotFound) return } // 获取群组的成员列表 playerIDList, found := redis.Chat.GetGroupMembers(chatID) if !found { p.ResponseCode(session, code.ChatGroupNotFound) return } chatPlayerMap := redis.Game.GetChatPlayerMap(playerIDList...) if chatPlayerMap == nil { p.ResponseCode(session, code.PlayerDetailNotFound) return } rsp := &pb.ChatPlayerList{ List: chatTable.ToPlayersProto(chatPlayerMap, playerIDList), } p.Response(session, rsp) } // 创建群组聊天室 func (p *actorPlayer) chatGroupCreate(session *cproto.Session, req *pb.GroupCreate) { if p.PlayerTable().Lord.Level < data.Const.FriendGroupCreateOpenLv { p.ResponseCode(session, code.ChatGroupCreateLvNotEnough) return } if len(req.Name) < 1 { p.ResponseCode(session, code.ChatGroupNameIsEmpty) return } // 检查群组名称长度 if !utils.CheckStrLen(req.Name, data.Const.FriendGroupNameMaxLength) { p.ResponseCode(session, code.ChatGroupNameTooLong) return } // 检查屏蔽词 if data.MaskWord.Check(req.Name) { p.ResponseCode(session, code.ChatGroupNameIllegal) return } chatTypeConfig, _ := data.ChatType.GetByChatType(enum.ChatType_Group) // 邀请人数超过上限 if len(req.PlayerIDList) > chatTypeConfig.PlayerLimit { p.ResponseCode(session, code.ChatGroupMemberLimit) return } chatTable := p.ChatTable() // 检查群组玩家邀请列表,不是好友不能邀请 for _, playerID := range req.PlayerIDList { if !chatTable.IsFriend(playerID) { p.ResponseCode(session, code.ChatGroupInviteNotFriend) return } } if chatTable.GetCreateGroupCount() >= data.Const.FriendGroupCreateLimit { p.ResponseCode(session, code.ChatGroupCreateLimit) return } // 添加群组到玩家身上 newChatID := redis.GUID.NewChatID() newChatRoom := dbChat.NewChatRoom(enum.ChatType_Group, newChatID, req.Name, p.playerID) chatTable.AddChatRoom(newChatRoom) chatTable.Save2Queue() // 创建成功就添加自己为群组成员 redis.Chat.AddGroupMember(newChatID, p.playerID) p.Debug("chatGroupCreate create success. newChatID = %d", newChatID) // 自己加入聊天室成员列表 retCode := actorRemote.Chat.GroupJoin(newChatRoom.ChatID, p.playerID) if code.IsFail(retCode) { p.ResponseCode(session, retCode) return } if len(req.PlayerIDList) > 0 { p.groupPlayerChangeSend(newChatRoom, req.PlayerIDList) } p.Response(session, newChatRoom.ToChatRoomProto()) } func (p *actorPlayer) chatGroupPlayerChange(session *cproto.Session, req *pb.GroupPlayerChange) { if req.ChatID < 1 { p.ResponseCode(session, code.IllegalArgument) return } var ( chatTable = p.ChatTable() chatRoom, found = chatTable.GetChatRoom(enum.ChatType_Group, req.ChatID) ) if !found { p.ResponseCode(session, code.ChatGroupNotFound) return } // 没有群组权限 if chatRoom.OwnerPlayerID != p.playerID { p.ResponseCode(session, code.ChatGroupPermissionDenied) return } chatTypeConfig, _ := data.ChatType.GetByChatType(enum.ChatType_Group) // 邀请人数超过上限 if len(req.PlayerIDList) > chatTypeConfig.PlayerLimit { p.ResponseCode(session, code.ChatGroupMemberLimit) return } var ( checkedIDList []int64 // 已经检测过的玩家ID列表 ) // 检测最新的成员列表是否为好友(不是好友不能邀请) for _, playerID := range req.PlayerIDList { if playerID == p.playerID { continue } if chatTable.IsFriend(playerID) { checkedIDList = append(checkedIDList, playerID) } } // checkedIDList len=0 可能移除所有人 p.groupPlayerChangeSend(chatRoom, checkedIDList) p.ResponseCode(session, code.OK) } // 发起群组玩家变更消息 func (p *actorPlayer) groupPlayerChangeSend(chatRoom *dbChat.ChatRoom, playerIDList []int64) { playerChangeReq := &pb.ChatGroupPlayerChange{ ChatID: chatRoom.ChatID, Name: chatRoom.Name, OwnerPlayerID: chatRoom.OwnerPlayerID, PlayerIDList: playerIDList, } actorRemote.Chat.GroupPlayerChange(playerChangeReq) } // 解散群组聊天室 func (p *actorPlayer) chatGroupDismiss(session *cproto.Session, req *pb.I64) { if req.Value < 1 { p.ResponseCode(session, code.IllegalArgument) return } var ( chatID = req.Value // 聊天室ID chatTable = p.ChatTable() ) chatRoom, found := chatTable.GetChatRoom(enum.ChatType_Group, chatID) if !found { p.ResponseCode(session, code.ChatGroupNotFound) return } if chatRoom.OwnerPlayerID != p.playerID { p.ResponseCode(session, code.ChatGroupPermissionDenied) return } actorRemote.Chat.Destory(enum.ChatType_Group, chatID) p.ResponseCode(session, code.OK) } // 群组邀请确认 func (p *actorPlayer) chatGroupInviteConfirm(session *cproto.Session, req *pb.GroupInviteConfirm) { chatTable := p.ChatTable() // 一键拒绝所有邀请 if req.IsRejectAll { chatTable.GroupInviteList.Clear() chatTable.Save2Queue() p.ResponseCode(session, code.OK) return } if req.ChatID < 1 { p.ResponseCode(session, code.IllegalArgument) return } // 验证邀请是否OK groupInvite, found := chatTable.GetGroupInvite(req.ChatID) if !found { p.ResponseCode(session, code.ChatGroupInviteNotFound) return } // 拒绝群组邀请 if !req.IsAccept { // 先删除邀请记录 chatTable.RemoveGroupInvite(req.ChatID) chatTable.Save2Queue() p.ResponseCode(session, code.OK) return } // 拥有的群组数量上限 if chatTable.GroupCount() >= data.Const.FriendGroupHaveCountLimit { p.ResponseCode(session, code.ChatGroupHaveCountLimit) return } // 先删除邀请记录 chatTable.RemoveGroupInvite(req.ChatID) chatTable.Save2Queue() // 已经在群里,则忽略 if chatRoom, found := chatTable.GetChatRoom(enum.ChatType_Group, req.ChatID); found { p.Response(session, chatRoom.ToChatRoomProto()) return } // 确认后,把自己加入聊天室成员列表 retCode := actorRemote.Chat.GroupJoin(req.ChatID, p.playerID) if code.IsFail(retCode) { p.ResponseCode(session, retCode) return } // 加入进自己的聊天室集合 newChatRoom := dbChat.NewChatRoom(enum.ChatType_Group, groupInvite.ChatID, groupInvite.Name, groupInvite.OwnerPlayerID) chatTable.AddChatRoom(newChatRoom) chatTable.Save2Queue() p.Response(session, newChatRoom.ToChatRoomProto()) } // 主动退出一个群组 func (p *actorPlayer) chatGroupQuit(session *cproto.Session, req *pb.I64) { if req.Value < 1 { p.ResponseCode(session, code.IllegalArgument) return } var ( chatID = req.Value // 聊天室ID chatTable = p.ChatTable() ) chatRoom, found := chatTable.GetChatRoom(enum.ChatType_Group, chatID) if !found { p.ResponseCode(session, code.ChatGroupNotFound) return } // 不允许退出自己创建的群组,只能解散群组 if chatRoom.OwnerPlayerID == p.playerID { p.ResponseCode(session, code.IllegalArgument) return } chatTable.RemoveChatRoom(enum.ChatType_Group, chatID) chatTable.Save2Queue() // 主动退出则只需要移出群组,不用通知其他人 actorRemote.Chat.GroupQuit(chatID, p.playerID) p.ResponseCode(session, code.OK) } // 群组免打扰设置 func (p *actorPlayer) chatGroupMute(session *cproto.Session, req *pb.GroupMute) { if req.ChatID < 1 { p.ResponseCode(session, code.IllegalArgument) return } chatTable := p.ChatTable() chatRoom, found := chatTable.GetChatRoom(enum.ChatType_Group, req.ChatID) if !found { p.ResponseCode(session, code.ChatGroupNotFound) return } chatRoom.SetMute(req.IsMute) chatTable.Save2Queue() p.ResponseCode(session, code.OK) } // 发送私聊消息 func (p *actorPlayer) chatPrivateSend(session *cproto.Session, req *pb.ChatPrivateSend) { if req.TargetID < 1 { p.ResponseCode(session, code.IllegalArgument) return } var ( playerTable = p.PlayerTable() toPlayerID = req.TargetID chatTypeConfig, _ = data.ChatType.GetByChatType(enum.ChatType_Private) ) if chatTypeConfig.SendOpenLv > 0 && playerTable.Lord.Level < chatTypeConfig.SendOpenLv { p.ResponseCode(session, code.ChatSendLvNotEnough) return } var ( chatTable = p.ChatTable() now = ctime.Now() ) // 发言频率限制 if chatTypeConfig.SendRate > 0 { var sendTime int64 if friend, found := chatTable.GetFriend(toPlayerID); found { sendTime = friend.LastSendTime } else if private, found := chatTable.GetPrivate(toPlayerID); found { sendTime = private.LastSendTime } if ctime.NewSecond(sendTime).DiffInSecondsWithAbs(&now) < chatTypeConfig.SendRate { p.ResponseCode(session, code.ChatSendTextTooFast) return } } // 拉黑不能私聊 if chatTable.IsBlack(toPlayerID) { p.ResponseCode(session, code.ChatPrivateBlackList) return } // 过滤换行回车和屏蔽词 req.Text = data.MaskWord.Replace(req.Text) if len(req.Text) < 1 { p.ResponseCode(session, code.ChatSendTextIsEmpty) return } if !utils.CheckStrLen(req.Text, chatTypeConfig.TextLen) { p.ResponseCode(session, code.ChatSendTextTooLong) return } // 检查玩家是否存在 chatPlayerMap := redis.Game.GetChatPlayerMap(p.playerID, toPlayerID) if len(chatPlayerMap) != 2 { p.ResponseCode(session, code.PlayerDetailNotFound) return } chatPlayer := chatPlayerMap[p.playerID] sendReq := chat.Service().BuildMsgProto(chatPlayer, enum.ChatMsgType_Text, req.Text, now.ToSecond(), nil) // 最近聊天内容 lastChatContent := dbChat.NewChatContent(sendReq.MsgID, sendReq.SendTime, sendReq.Type, sendReq.Text, sendReq.Params) // 判断是否是好友,更新好友或者私聊的最近一次聊天时间 if friendRelation, found := chatTable.GetFriend(toPlayerID); found { friendRelation.FriendUpdate(toPlayerID, lastChatContent, true) } else { chatTable.PrivateUpdate(toPlayerID, lastChatContent, true) } chatTable.Save2Queue() redis.Chat.AddPrivateMsg(p.playerID, toPlayerID, chatTypeConfig.MsgLimit, sendReq) toChatPlayer := chatPlayerMap[toPlayerID] // 通知对方,这里不用判断对方状态 actorRemote.GamePlayer.Call(toChatPlayer.GameNodeID, toPlayerID, nameRemote.Player_ChatPrivateMsgSend, sendReq) p.Response(session, sendReq) } // 获取某玩家的私聊消息列表 func (p *actorPlayer) chatPrivateMsgList(session *cproto.Session, req *pb.I64I64) { if req.Key < 1 || req.Value < 0 { p.ResponseCode(session, code.IllegalArgument) return } var ( targetID = req.Key // 私聊对象玩家ID lastMsgID = req.Value // 消息ID ) msgs := redis.Chat.GetPrivateMsgs(p.playerID, targetID, lastMsgID) if msgs == nil { p.ResponseCode(session, code.Error) return } p.Response(session, msgs) } // 聊天设置 func (p *actorPlayer) chatSetting(session *cproto.Session, req *pb.ChatSetting) { chatTable := p.ChatTable() chatTable.SetChatMute(req.IsFriendInviteMute, req.IsGroupInviteMute, req.IsPrivateChatMute) chatTable.Save2Queue() p.ResponseCode(session, code.OK) } // 举报玩家 func (p *actorPlayer) complaint(session *cproto.Session, req *pb.Complaint) { if req.TargetID < 1 { p.ResponseCode(session, code.IllegalArgument) return } complaintType := enum.ComplaintType(req.ComplaintType) if complaintType < enum.ComplaintType_SpamAd || complaintType > enum.ComplaintType_Other { p.ResponseCode(session, code.IllegalArgument) return } if complaintType == enum.ComplaintType_Other && len(req.Content) < 1 { p.ResponseCode(session, code.ComplaintContentEmpty) return } if !utils.CheckStrLen(req.Content, data.Const.ComplaintContentLimit) { p.ResponseCode(session, code.ComplaintContentTooLong) return } if data.MaskWord.Check(req.Content) { p.ResponseCode(session, code.ComplaintContextIllegal) return } newComplaintID := csnowflake.NextID() newComplaintTable := db.NewComplaintTable(newComplaintID, p.playerID, req.TargetID, complaintType, req.Content) newComplaintTable.Save2Queue() p.ResponseCode(session, code.OK) } // 接收群组邀请求 func (p *actorPlayer) chatGroupInvite(req *pb.GroupInvite) { chatTable := p.ChatTable() // 禁用群组邀请 if !chatTable.IsGroupInviteMute { return } if _, found := chatTable.GetGroupInvite(req.ChatID); found { // 已经存在了,则忽略 return } // 消息放入邀请列表中 groupInvite := chatTable.NewGroupInvite(req.ChatID, req.Name, req.InvitePlayer.PlayerID) chatTable.AddGroupInvite(groupInvite) chatTable.Save2Queue() sessions.PushPlayer(p.playerID, nameRoute.PushGamePlayer_ChatGroupInvite, req) } // 通知群组聊天室成员离开 func (p *actorPlayer) chatGroupMemberRemove(req *pb.I64Bool) { var ( chatTable = p.ChatTable() chatID = req.Key // 聊天室ID isDismiss = req.Value // 是否解散 ) chatRoom, found := chatTable.GetChatRoom(enum.ChatType_Group, chatID) if found { // 从玩家群组集合移除 chatTable.RemoveChatRoom(enum.ChatType_Group, chatID) chatTable.Save2Queue() } mailSendReq := &pb.SendMail{ ToPlayerID: p.playerID, TemplateID: constant.MailID_ChatGroupRemove, TemplateParams: []string{chatRoom.Name}, } // 解散群组 if isDismiss { mailSendReq.TemplateID = constant.MailID_ChatGroupDismissal } // 发送邮件通知 actorRemote.GameMail.Send(p.App().NodeID(), mailSendReq) sessions.PushPlayer(p.playerID, nameRoute.PushGamePlayer_ChatGroupLeave, req) } // 收到私聊消息 func (p *actorPlayer) chatPrivateMsgSend(req *pb.ChatMsg) { var ( chatTable = p.ChatTable() toPlayerID = req.Sender.PlayerID // 发送者ID ) // 拒绝私聊 if !chatTable.IsFriend(toPlayerID) && !chatTable.IsPrivateChatMute { return } // 黑名单 if chatTable.IsBlack(toPlayerID) { return } // 最近聊天内容 lastChatContent := dbChat.NewChatContent(req.MsgID, req.SendTime, req.Type, req.Text, req.Params) // 如果不是好友就放到最近私聊里面,如果在最近里面就更新时间 if friendRelation, found := chatTable.GetFriend(toPlayerID); found { friendRelation.FriendUpdate(toPlayerID, lastChatContent, false) } else { chatTable.PrivateUpdate(toPlayerID, lastChatContent, false) } chatTable.Save2Queue() chatTypeConfig, _ := data.ChatType.GetByChatType(enum.ChatType_Private) redis.Chat.AddPrivateMsg(p.playerID, toPlayerID, chatTypeConfig.MsgLimit, req) sessions.PushPlayer(p.playerID, nameRoute.PushGamePlayer_ChatPrivateNew, req) } // 系统消息事件 func (p *actorPlayer) chatSystemMsg(eventData cfacade.IEventData) { e, ok := eventData.(*event.ChatSystemMsg) if !ok { return } params := e.Params() if params == nil { return } textType := enum.ChatMsgType(params.Type) if !data.ChatMsgTemplate.ContainChatMsgType(textType) { // 跳过没有配置的类型 return } chatTable := db.GetChatTable(p.playerID) newSystemMsg := chatTable.AddSystemMsg(params, int(data.Const.SystemChatLimit)) chatTable.Save2Queue() if p.isOnline { sessions.PushPlayer(p.playerID, nameRoute.PushGamePlayer_ChatSystemNew, newSystemMsg.ToProto()) } } // 聊天室更新(联盟或者沙盘地图更新的时候触发) func (p *actorPlayer) chatRoomUpdate(req *pb.PlayerChatRoomUpdate) { var ( chatType = enum.ChatType(req.ChatType) // 聊天室类型 chatID = req.ChatID // 聊天室ID isExit = req.IsExit // 是否退出 chatTable = db.GetChatTable(p.playerID) ) chatRoom, found := chatTable.GetChatRoom(chatType, chatID) if isExit { // 如果已经不存在了,则忽略 if !found { return } chatTable.RemoveChatRoom(chatType, chatID) if chatType == enum.ChatType_League { // 删除redis的联盟聊天室成员 redis.Chat.DelLeagueMember(chatID, p.playerID) } } else { chatRoom = dbChat.NewChatRoom(chatType, chatID, "", 0) chatTable.AddChatRoom(chatRoom) // 新加入就推送最新的20条消息 actorRemote.Chat.Push(chatRoom.ChatType, p.playerID, chatID) if chatType == enum.ChatType_League { // 添加redis的联盟聊天室成员 redis.Chat.AddLeagueMember(chatID, p.playerID) } } // 保存 chatTable.Save2Queue() sessions.PushPlayer(p.playerID, nameRoute.PushGamePlayer_ChatRoomUpdate, &pb.ChatRoomUpdate{ ChatRoom: chatRoom.ToChatRoomProto(), IsExit: isExit, }) } // 收到地图节点的系统消息 func (p *actorPlayer) chatMapSystemMsg(req *pb.ParamsTemplate) { chatSystemEvent := event.NewChatSystemMsgByTemplate(p.playerID, req) p.PostEvent(chatSystemEvent) }