color game

This commit is contained in:
liuxiaobo 2025-06-07 11:57:56 +08:00
parent a213ea01b9
commit 5db1090d7d
8 changed files with 125 additions and 224 deletions

View File

@ -3,34 +3,37 @@ package baseroom
import (
"fmt"
"game/common/proto/pb"
"github.com/fox/fox/ipb"
"github.com/fox/fox/log"
"github.com/fox/fox/processor"
"github.com/fox/fox/service"
"github.com/golang/protobuf/proto"
"time"
)
type BaseRoom[Seat ISeat] struct {
id int
roomType int // 房间配置id 初级,中级,高级
playType int // 玩法配置id color玩法id
gameNo string
Seats []Seat
timeTypes map[TimerType]uint32
id int
roomType int // 房间配置id 初级,中级,高级
playType int // 玩法配置id color玩法id
gameNo string
Seats []Seat
processor *processor.Processor
timeProcessor *processor.Processor
subRoom IRoom
sTimer ITimer
sender ISender
timeTypes map[TimerType]uint32
srv service.IService
}
func NewBaseRoom[Seat ISeat](id, roomType, playType int, subRoom IRoom, sTimer ITimer, sender ISender) (*BaseRoom[Seat], pb.ErrCode) {
func NewBaseRoom[Seat ISeat](id, roomType, playType int, srv service.IService) (*BaseRoom[Seat], pb.ErrCode) {
room := &BaseRoom[Seat]{
id: id,
roomType: roomType,
playType: playType,
gameNo: "",
timeTypes: make(map[TimerType]uint32),
subRoom: subRoom,
sTimer: sTimer,
sender: sender,
id: id,
roomType: roomType,
playType: playType,
gameNo: "",
timeTypes: make(map[TimerType]uint32),
srv: srv,
processor: processor.NewProcessor(),
timeProcessor: processor.NewProcessor(),
}
return room, pb.ErrCode_OK
@ -44,10 +47,6 @@ func (r *BaseRoom[Seat]) RoomType() int {
return r.roomType
}
func (r *BaseRoom[Seat]) SetTruthRoom(rm IRoom) {
r.subRoom = rm
}
func (r *BaseRoom[Seat]) PlayType() int {
return r.playType
}
@ -121,14 +120,6 @@ func (r *BaseRoom[Seat]) RemovePlayer(player IPlayer) {
}
}
func (r *BaseRoom[Seat]) OnMessage(cmd int32, params ...any) {
if r.subRoom == nil {
log.Error(r.Log("sub room is nil"))
return
}
r.subRoom.OnMessage(cmd, params...)
}
func (r *BaseRoom[Seat]) DebugSendMsg(user IPlayer, msgId pb.MsgId, msg proto.Message) {
log.Debug(r.UserLog(user.Id(), "send msg:%v %v", msgId, msg.String()))
}
@ -140,7 +131,8 @@ func (r *BaseRoom[Seat]) SendMsg(user IPlayer, msgId pb.MsgId, msg proto.Message
} else {
for _, seat := range r.Seats {
if !seat.Empty() && seat.Player().Id() == user.Id() {
r.sender.SendMsg(user, msgId, msg)
iMsg := ipb.MakeMsgEx(r.srv.Name(), 0, user.Id(), int32(msgId), msg)
_ = r.srv.Send(user.GateTopicName(), iMsg)
break
}
}
@ -165,11 +157,7 @@ func (r *BaseRoom[Seat]) Broadcast(msgId pb.MsgId, msg proto.Message, exclude ..
}
}
func (r *BaseRoom[Seat]) NewTimer(timerType TimerType, duration time.Duration, args ...interface{}) {
if r.sTimer == nil {
log.Error(r.Log("timer handler is nil"))
return
}
func (r *BaseRoom[Seat]) NewTimer(timerType TimerType, duration time.Duration, args ...any) {
if _, ok := r.timeTypes[timerType]; ok {
log.Error(r.Log("timer type:%v is exist.can not new timer", timerType.String()))
// if timerType == TtPlayerAct {
@ -177,8 +165,10 @@ func (r *BaseRoom[Seat]) NewTimer(timerType TimerType, duration time.Duration, a
// }
return
}
tid := r.sTimer.NewTimer(duration+time.Duration(10)*time.Millisecond, func() {
r.sTimer.OnTimer(timerType, args...)
tid := r.srv.NewTimer(duration+time.Duration(10)*time.Millisecond, func() {
if err := r.timeProcessor.Dispatch(int32(timerType), args...); err != nil {
log.ErrorF(r.Log("timer dispatch err:%v", err))
}
}, true, r.Log("start type:%v timer", timerType.String()))
r.timeTypes[timerType] = tid
// log.Debug(r.Log("start type:%v timer", timerType.String()))
@ -186,7 +176,7 @@ func (r *BaseRoom[Seat]) NewTimer(timerType TimerType, duration time.Duration, a
func (r *BaseRoom[Seat]) CancelTimer(timerType TimerType) {
if tid, ok := r.timeTypes[timerType]; ok {
r.sTimer.CancelTimer(tid)
r.srv.CancelTimer(tid)
delete(r.timeTypes, timerType)
// log.Debug(r.Log("stop type:%v timer, rest timer:%+v", timerType.String(), r.timeTypes))
}
@ -194,7 +184,7 @@ func (r *BaseRoom[Seat]) CancelTimer(timerType TimerType) {
func (r *BaseRoom[Seat]) CancelAllTimer() {
for _, tid := range r.timeTypes {
r.sTimer.CancelTimer(tid)
r.srv.CancelTimer(tid)
// log.Debug(r.Log("start type:%v timer", timerType.String()))
}
r.timeTypes = make(map[TimerType]uint32)
@ -233,12 +223,34 @@ func (r *BaseRoom[Seat]) UserLog(uid int64, format string, a ...any) string {
}
}
// 玩家俱乐部房间玩游戏
func (r *BaseRoom[Seat]) GameStart() {
func (r *BaseRoom[Seat]) Unmarshal(cmd int32, data []byte) (any, error) {
return r.processor.Unmarshal(cmd, data)
}
// 玩家俱乐部房间玩游戏
func (r *BaseRoom[Seat]) GameEnd() {
func (r *BaseRoom[Seat]) Dispatch(user IPlayer, cmd int32, params ...any) error {
inp := make([]any, len(params)+1)
inp = append(inp, user)
inp = append(inp, params...)
return r.processor.Dispatch(cmd, inp...)
}
func (r *BaseRoom[Seat]) RegisterMessages(metas processor.RegisterMetas) {
r.processor.RegisterMessages(metas)
}
func (r *BaseRoom[Seat]) timerDispatch(user IPlayer, cmd int32, params ...any) error {
inp := make([]any, len(params)+1)
inp = append(inp, user)
inp = append(inp, params...)
return r.timeProcessor.Dispatch(cmd, inp...)
}
// 注册时间事件及处理
func (r *BaseRoom[Seat]) RegisterTimerMessages(metas processor.RegisterMetas) {
r.timeProcessor.RegisterMessages(metas)
}
// 初始化房间
func (r *BaseRoom[Seat]) OnInit() {
}

View File

@ -2,18 +2,18 @@ package baseroom
import (
"game/common/proto/pb"
"github.com/golang/protobuf/proto"
"time"
)
type IRoom interface {
Id() int
RoomType() int // 房间配置id
PlayType() int
OnInit()
// SeatPlayerNum() int
// HasEmptySeat() bool
// HasPlayer(uid int64) bool
OnMessage(cmd int32, params ...any)
Unmarshal(cmd int32, data []byte) (any, error)
Dispatch(user IPlayer, cmd int32, params ...any) error
// ReleaseRoom()
}
@ -29,22 +29,14 @@ type ISeat interface {
type IPlayer interface {
Id() int64
Robot() IRobot
GateTopicName() string
RoomId() int
}
type IRobot interface {
OnMessage(cmd pb.MsgId, params ...any)
}
type ITimer interface {
OnTimer(timerType TimerType, args ...interface{})
NewTimer(duration time.Duration, cb func(), needLog bool, desc ...string) uint32
CancelTimer(timerId uint32)
}
type ISender interface {
SendMsg(user IPlayer, msgId pb.MsgId, msg proto.Message)
}
type ICreateRoom interface {
CreateRoom(id, roomType int) (IRoom, pb.ErrCode)
}

View File

@ -8,6 +8,7 @@ import (
"game/common/utils"
"github.com/fox/fox/etcd"
"github.com/fox/fox/log"
"github.com/fox/fox/service"
"github.com/fox/fox/xrand"
"github.com/go-redis/redis/v8"
"time"
@ -113,7 +114,7 @@ func (m *UserBindService) RandServiceNode(typeId pb.ServiceTypeId) (*etcd.Servic
}
// 根据服务类型,路由到对应的服务节点
func (m *UserBindService) FindServiceName(userId int64, typeId pb.ServiceTypeId) (string, error) {
func (m *UserBindService) findServiceName(userId int64, typeId pb.ServiceTypeId) (string, error) {
if userId > 0 {
// 向redis中查询。redis中保留的服务节点不一定是可用的还需要向etcd中验证
sName := m.LoadFromRedis(userId, typeId)
@ -134,3 +135,16 @@ func (m *UserBindService) FindServiceName(userId int64, typeId pb.ServiceTypeId)
m.rdb.Set(context.Background(), m.makeRedisKey(userId, typeId), node.Name, 2*24*time.Hour)
return node.Name, nil
}
/*
查找topic,根据serviceTypeId以及玩家id查找玩家过往访问该服务的节点优先使用原节点
*/
func (m *UserBindService) FindTopic(userId int64, serviceTypeId pb.ServiceTypeId) (topic, sName string) {
var err error
if sName, err = m.findServiceName(userId, serviceTypeId); err == nil {
return service.TopicEx(sName), sName
} else {
log.Error(err.Error())
}
return "", sName
}

View File

@ -18,12 +18,11 @@ func (s *ChatService) initProcessor() {
func (s *ChatService) onChat(uid int64, msg *pb.C2SChat) {
switch msg.Type {
case pb.ChatType_CT_Private:
sName, err := s.bindService.FindServiceName(msg.DstUser.UserId, pb.ServiceTypeId_STI_Gate)
if err != nil {
log.DebugF("find user:%v in gate err: %v", uid, err)
return
tName, _ := s.bindService.FindTopic(msg.DstUser.UserId, pb.ServiceTypeId_STI_Gate)
if tName == "" {
log.DebugF("user:%v find gate failed", uid)
}
s.SendServiceMsg(service.TopicEx(sName), msg.DstUser.UserId, int32(pb.MsgId_S2CChatId), msg)
s.SendServiceMsg(service.TopicEx(tName), msg.DstUser.UserId, int32(pb.MsgId_S2CChatId), msg)
default:
s.SendServiceMsg(service.TopicEx(topicName.WorldMessage), uid, int32(pb.MsgId_S2CChatId), msg)
}

View File

@ -4,19 +4,26 @@ import (
"game/common/baseroom"
"game/common/proto/pb"
"github.com/fox/fox/log"
"github.com/fox/fox/service"
)
type ColorRoom struct {
*baseroom.BaseRoom[ColorSeat]
}
func newColorRoom(id, roomType int) (baseroom.IRoom, pb.ErrCode) {
func newColorRoom(id, roomType int, srv service.IService) (baseroom.IRoom, pb.ErrCode) {
rm := &ColorRoom{}
playType := 0
code := pb.ErrCode_OK
rm.BaseRoom, code = baseroom.NewBaseRoom[ColorSeat](id, roomType, playType, rm, rm, rm)
rm.BaseRoom, code = baseroom.NewBaseRoom[ColorSeat](id, roomType, playType, srv)
if code != pb.ErrCode_OK {
log.ErrorF("new color room err code:%v", code)
return nil, code
}
rm.OnInit()
return rm, code
}
func (rm *ColorRoom) OnInit() {
return
}

View File

@ -1,156 +1,15 @@
package server
import (
"encoding/json"
"game/common/model/user"
"game/common/proto/pb"
"game/common/rpcName"
"game/common/utils"
"github.com/fox/fox/etcd"
"github.com/fox/fox/ipb"
"github.com/fox/fox/ksync"
"github.com/fox/fox/log"
"github.com/fox/fox/processor"
"github.com/fox/fox/service"
"time"
)
const (
timeout = time.Second * 30
// timeout = time.Second * 30
)
func (s *ColorService) initProcessor() {
s.processor.RegisterMessages(processor.RegisterMetas{
pb.MsgId_C2SUserLoginId: {pb.C2SUserLogin{}, s.onLoginOrRegister},
//pb.MsgId_C2SUserLoginId: {pb.C2SUserLogin{}, s.onLoginOrRegister},
})
}
func (s *ColorService) checkLoginOrRegister(req *pb.C2SUserLogin) (us *user.UserAccount, code pb.ErrCode, node *etcd.ServiceNode) {
var err error
node, err = s.bindService.RandServiceNode(pb.ServiceTypeId_STI_DB)
if err != nil {
log.ErrorF(s.Log("not find db service.err:%s ", err.Error()))
return nil, pb.ErrCode_SystemErr, node
}
if req.Version < "20250601123030" {
return nil, pb.ErrCode_VersionTooLow, node
}
us = &user.UserAccount{
Username: req.Username,
Password: req.Password,
DeviceID: req.DeviceId,
LastLoginIP: req.Ip,
}
rpcMsg := ipb.MakeRpcMsg[user.UserAccount](rpcName.GetUserAccount, 0, us)
rspMsg, err := s.Call(service.RpcTopicEx(node.Name), timeout, rpcMsg)
if err != nil {
log.ErrorF(s.Log("call rpc:%v err:%s ", rpcMsg.RpcMsgId, err.Error()))
return nil, pb.ErrCode_SystemErr, node
}
_ = json.Unmarshal(rspMsg.Msg, us)
//log.DebugF("收到rpc:%v返回数据:%v", rpcName.GetUserAccount, string(rspMsg.Msg))
if us.ID == 0 {
// 没有帐号,创建帐号
us = &user.UserAccount{
Username: req.Username,
Password: req.Password,
DeviceID: req.DeviceId,
LastLoginIP: req.Ip,
LastLoginTime: time.Now(),
RegisterIP: req.Ip,
RegisterTime: time.Now(),
}
rpcMsg = ipb.MakeRpcMsg[user.UserAccount](rpcName.CreateUserAccount, 0, us)
rspMsg, err = s.Call(service.RpcTopicEx(node.Name), timeout, rpcMsg)
if err != nil {
log.ErrorF(s.Log("call rpc:%v err:%s ", rpcMsg.RpcMsgId, err.Error()))
return nil, pb.ErrCode_SystemErr, node
}
_ = json.Unmarshal(rspMsg.Msg, us)
if us.ID == 0 {
log.ErrorF(s.Log("call rpc:%v err", rpcMsg.RpcMsgId))
return nil, pb.ErrCode_SystemErr, node
}
log.DebugF("收到rcp:%v返回数据:%v", rpcName.CreateUserAccount, string(rspMsg.Msg))
}
if !utils.CheckPassword(req.Password, us.Password) {
log.ErrorF(s.Log("用户密码:%v 数据库中密码:%v", req.Password, us.Password))
return nil, pb.ErrCode_LoginUserOrPwdErr, node
}
switch us.Status {
case user.AccountFrozen:
return nil, pb.ErrCode_AccountFrozen, node
case user.AccountBanned:
return nil, pb.ErrCode_AccountBanned, node
default:
return us, pb.ErrCode_OK, node
}
}
// 生成JWT令牌(简化版)
func generateToken(userID int64, username string) (string, error) {
_ = userID
_ = username
// 这里应该使用JWT库生成实际令牌
// 简化实现实际项目中请使用安全的JWT实现
return "generated-token-placeholder", nil
}
// 获取用户数据,如果没有则创建
func (s *ColorService) getUser(accountId int64, tName string) (*user.User, pb.ErrCode) {
us := &user.User{
AccountId: accountId,
}
rpcMsg := ipb.MakeRpcMsg[user.User](rpcName.GetUserByAccountId, 0, us)
rsp, err := s.Call(service.RpcTopicEx(tName), timeout, rpcMsg)
if err != nil {
log.ErrorF(s.Log("call rpc:%v err:%s", rpcMsg.RpcMsgId, err.Error()))
return nil, pb.ErrCode_SystemErr
}
_ = json.Unmarshal(rsp.Msg, us)
if us.ID == 0 {
log.ErrorF(s.Log("call rpc:%v return:%v", rpcMsg.RpcMsgId, string(rsp.Msg)))
return us, pb.ErrCode_SystemErr
}
return us, pb.ErrCode_OK
}
// 登录或注册
func (s *ColorService) onLoginOrRegister(iMsg *ipb.InternalMsg, req *pb.C2SUserLogin) {
ksync.GoSafe(func() {
account, code, node := s.checkLoginOrRegister(req)
userId := int64(0)
rsp := &pb.S2CUserLogin{Code: code}
if account != nil && code == pb.ErrCode_OK {
// 拉取用户数据
us := &user.User{}
us, code = s.getUser(userId, req.Username)
if code == pb.ErrCode_OK {
rsp.UserId = us.ID
rsp.Token, _ = generateToken(account.ID, account.Username)
userId = rsp.UserId
}
}
s.SendServiceMsg(service.TopicEx(iMsg.ServiceName), iMsg.ConnId, userId, int32(pb.MsgId_S2CUserLoginId), rsp)
if account != nil && account.ID > 0 {
loginLog := &user.UserLoginLog{
AccountID: account.ID,
LoginIP: req.Ip,
LoginTime: time.Now(),
DeviceInfo: req.DeviceId,
LoginResult: code == pb.ErrCode_OK,
FailReason: code.String(),
}
switch code {
case pb.ErrCode_LoginUserOrPwdErr, pb.ErrCode_OK:
rpcMsg := ipb.MakeRpcMsg[user.UserLoginLog](rpcName.LogUserAccountLogin, 0, loginLog)
ksync.GoSafe(func() {
_, _ = s.Call(service.RpcTopicEx(node.Name), timeout, rpcMsg)
}, nil)
}
}
}, nil)
}

View File

@ -89,6 +89,16 @@ func (s *ColorService) NotifyStop() {
}
func (s *ColorService) CanStop() bool {
switch s.roomMgr.Status() {
case baseroom.SsWorking:
return false
case baseroom.SsWaitStop, baseroom.SsStopped:
// 没有玩家
if s.playerMgr.Count() == 0 {
return true
}
return false
}
return true
}
@ -97,6 +107,18 @@ func (s *ColorService) OnStop() {
log.Debug("OnStop")
}
func (s *ColorService) findRoom(uid int64) (baseroom.IPlayer, baseroom.IRoom, error) {
if user := s.playerMgr.Find(uid); user != nil {
if rm := s.roomMgr.Find(user.RoomId()); rm != nil {
return user, rm, nil
} else {
return nil, nil, fmt.Errorf("user:%v not found room %d", uid, user.RoomId())
}
} else {
return nil, nil, fmt.Errorf("user:%v not exist", uid)
}
}
// 处理其它服发送过来的消息
func (s *ColorService) OnMessage(data []byte) error {
var iMsg = &ipb.InternalMsg{}
@ -108,7 +130,16 @@ func (s *ColorService) OnMessage(data []byte) error {
if req, err := s.processor.Unmarshal(iMsg.MsgId, iMsg.Msg); err == nil {
err = s.processor.Dispatch(iMsg.MsgId, iMsg, req)
} else {
log.Error(err.Error())
var user baseroom.IPlayer
var rm baseroom.IRoom
if user, rm, err = s.findRoom(iMsg.UserId); err == nil {
if req, err = rm.Unmarshal(iMsg.MsgId, iMsg.Msg); err == nil {
err = rm.Dispatch(user, iMsg.MsgId, iMsg, req)
}
}
if err != nil {
log.Error(err.Error())
}
}
log.Debug(s.Log("received message:%v", iMsg.MsgId))
return nil

View File

@ -152,19 +152,6 @@ func (s *GateService) OnMessage(data []byte) error {
return nil
}
/*
查找topic,根据serviceTypeId以及玩家id查找玩家过往访问该服务的节点优先使用原节点
*/
func (s *GateService) findTopic(userId int64, serviceTypeId pb.ServiceTypeId) (topic, sName string) {
var err error
if sName, err = s.bindService.FindServiceName(userId, serviceTypeId); err == nil {
return service.TopicEx(sName), sName
} else {
log.Error(err.Error())
}
return "", sName
}
// 运行在conn的read协程里。将消息转发给内部服务
func (s *GateService) WsOnMessage(conn ws.IConn, data []byte) {
msg := &pb.ClientMsg{}
@ -179,7 +166,7 @@ func (s *GateService) WsOnMessage(conn ws.IConn, data []byte) {
if msg.ServiceName != "" {
topic = service.TopicEx(msg.ServiceName)
} else {
topic, msg.ServiceName = s.findTopic(conn.UserId(), msg.ServiceTid)
topic, msg.ServiceName = s.bindService.FindTopic(conn.UserId(), msg.ServiceTid)
}
if topic != "" {
if msg.MsgId == int32(pb.MsgId_C2SUserLoginId) {