package user_s import ( "encoding/json" "fmt" "github.com/jinzhu/copier" "hilo-user/_const/enum/diamond_e" "hilo-user/_const/enum/group_e" "hilo-user/_const/enum/user_e" "hilo-user/common" "hilo-user/cv/msg_cv" "hilo-user/cv/user_cv" "hilo-user/domain" "hilo-user/domain/cache" "hilo-user/domain/cache/user_c" "hilo-user/domain/event/user_ev" "hilo-user/domain/model/diamond_m" "hilo-user/domain/model/group_m" "hilo-user/domain/model/noble_m" "hilo-user/domain/model/res_m" "hilo-user/domain/model/user_m" "hilo-user/domain/service" "hilo-user/domain/service/group_s" "hilo-user/mycontext" "hilo-user/myerr" "hilo-user/myerr/bizerr" "hilo-user/mylogrus" "hilo-user/req/user_req" "hilo-user/resource/config" "hilo-user/resource/mysql" "hilo-user/sdk/sud" "hilo-user/sdk/tencentyun" "math" "sort" "strconv" "time" ) type GameService struct { svc *service.Service } func NewGameService(myContext *mycontext.MyContext) *GameService { svc := service.CreateService(myContext) return &GameService{svc} } // 生成App客户端code // param userId 用户id // condition // 1.jwt生成,只带userId // 2.入库 func (s *GameService) GenClientCode(userId mysql.ID, externalId mysql.Str) (string, error) { var model = domain.CreateModelContext(s.svc.MyContext) code, err := generateGameJwtToken(userId, externalId, config.GetConfigGameJWT().ISSUER_CLIENT) if err != nil { model.Log.Errorf("generateGameJwtToken fail:%v", err) return "", err } err = s.svc.Transactional(func() error { // todo 持久化 return nil }) return code, err } // 生成游戏服务器用的ssToken // return userId ssToken expireDateMs err func (s *GameService) GetSsToken(code string) (mysql.ID, string, int64, error) { var model = domain.CreateModelContext(s.svc.MyContext) userId, externalId, expiresAt, err := ParseJwtToken(code, config.GetConfigGameJWT().ISSUER_CLIENT) if err != nil { model.Log.Errorf("ParseJwtToken fail:%v", err) return userId, "", expiresAt, err } ssToken, err := generateGameJwtToken(userId, externalId, config.GetConfigGameJWT().ISSUER_SERVER) if err != nil { model.Log.Errorf("generateGameJwtToken fail:%v", err) return userId, "", expiresAt, err } err = s.svc.Transactional(func() error { // todo 持久化 return nil }) return userId, ssToken, expiresAt, err } // 生成游戏服务器用的ssToken // return userId ssToken expireDateMs err func (s *GameService) UpdateSsToken(ssToken string) (mysql.ID, string, int64, error) { var model = domain.CreateModelContext(s.svc.MyContext) userId, externalId, expiresAt, err := ParseJwtToken(ssToken, config.GetConfigGameJWT().ISSUER_SERVER) // err 不需要判断过期 if err != nil && err != bizerr.GameTokenExpire { model.Log.Errorf("ParseJwtToken fail:%v", err) return userId, "", expiresAt, err } newSsToken, err := generateGameJwtToken(userId, externalId, config.GetConfigGameJWT().ISSUER_SERVER) if err != nil { model.Log.Errorf("generateGameJwtToken fail:%v", err) return userId, "", expiresAt, err } err = s.svc.Transactional(func() error { // todo 持久化 return nil }) return userId, newSsToken, expiresAt, err } // 游戏服务器信息上报 func (s *GameService) ReportGameInfo(userInfo user_req.ReportGameInfoReq) error { model := domain.CreateModel(s.svc.CtxAndDb) var ( userRoundId = "" roomId = "" ) reportMsg, err := json.Marshal(userInfo.ReportMsg) if err != nil { model.Log.Errorf("ReportGameInfo err:%v, userInfo:%v", err, userInfo) return err } event := &user_ev.ReportGameInfoEvent{ ReportType: userInfo.ReportType, } if userInfo.ReportType == user_e.ReportTypeGameStart { event.GameStartObject = new(user_ev.GameStartObject) err = json.Unmarshal(reportMsg, event.GameStartObject) if err != nil { model.Log.Errorf("ReportGameInfo err:%v, userInfo:%v", err, userInfo) return err } userRoundId = event.GameStartObject.GameRoundId roomId = event.GameStartObject.RoomId } else if userInfo.ReportType == user_e.ReportTypeGameSettle { event.GameSettleObject = new(user_ev.GameSettleObject) err = json.Unmarshal(reportMsg, event.GameSettleObject) if err != nil { model.Log.Errorf("ReportGameInfo err:%v, userInfo:%v", err, userInfo) return err } userRoundId = event.GameSettleObject.GameRoundId roomId = event.GameSettleObject.RoomId } // 持久化 err = user_m.SaveGameSdkReport(domain.CreateModelContext(s.svc.MyContext), userRoundId, roomId, string(userInfo.ReportType), string(reportMsg), userInfo.ExternalId, userInfo.SsToken) if err != nil { return err } return model.Transaction(func(model *domain.Model) error { userId, err := user_c.ToUserId(model, userInfo.ExternalId) if err != nil { return err } event.UserId = userId return user_ev.PublishReportGameInfoEvent(model, event) }) } func (s *GameService) GameAdd(userId mysql.ID, extId, code, lang string, para *user_m.GameAddParam) error { var model = domain.CreateModelContext(s.svc.MyContext) if !user_c.LockGame(model, para.TxGroupId) { mylogrus.MyLog.Infof("GameAdd LockGame faild TxGroupId:%v", para.TxGroupId) return bizerr.ReqTooFrequent } defer user_c.UnLockGame(model, para.TxGroupId) info, err := group_m.GetByTxGroupId(model, para.TxGroupId) if err != nil { return myerr.WrapErr(err) } // 一个房间只能存在一个还未结束的游戏 // 房主/经理/管理员可以创建游戏,其它用户点击提示“仅房间管理可以创建游戏” role, err := group_m.GetRoleInGroup(model, userId, info.ImGroupId) if err != nil { return err } if role < common.GROUP_ADMIN { return bizerr.GameAddNoPermissions } // 在麦上才能创建游戏 micUser, err := group_m.GetMicUserByExternalId(model, extId) if err != nil { return err } if micUser == nil { return bizerr.GameAddNotOnMic } mgId := "" switch para.GameType { case 1: mgId = user_e.MgIdLudo case 2: mgId = user_e.MgIdUno } // 创建游戏 userInfo := &user_m.GameInfo{ MgId: mgId, GameType: para.GameType, Mode: int32(para.Mode), Piece: int32(para.Piece), OnOff1: uint8(para.OnOff1), Diamond: para.Diamond, CreateId: userId, TxGroupId: info.TxGroupId, AutoMatch: uint8(para.AutoMatch), } err = model.Transaction(func(model *domain.Model) error { err = user_m.Add(model, userInfo) if err != nil { return myerr.WrapErr(bizerr.GameHaveNoEnd) } // 自己作为游戏成员加入游戏 err = user_m.SaveGamePlayer(model, &user_m.GamePlayer{GameId: userInfo.Id, UserId: userId, UserCode: code, ExternalId: extId, CreatedTime: time.Now(), SeatIdx: 0}) if err != nil { model.Log.Errorf("CreateGame err:%v", err) return myerr.WrapErr(bizerr.GameHaveNoEndGame) } if userInfo.Diamond > 0 { // 扣费 diamondAccount, err := diamond_m.CheckEnoughDiamondFrozen(model, userId, mysql.Num(userInfo.Diamond)) if err != nil { model.Log.Errorf("CreateGame err:%v", err) return err } diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(diamond_e.GameJoin, userInfo.Id, mysql.Num(userInfo.Diamond)) if err != nil { model.Log.Errorf("CreateGame err:%v", err) return err } err = diamondAccountDetail.Persistent() if err != nil { model.Log.Errorf("CreateGame err:%v", err) return err } } // 游戏相关操作 err = s.afterCreate(model, userInfo, para.GameCode, extId) if err != nil { model.Log.Errorf("CreateGame err:%v", err) return err } return nil }) if err != nil { model.Log.Errorf("CreateGame err:%v", err) return err } go s.SendGamePublicMsg(model, userId, info.TxGroupId, lang, userInfo.GameType, user_e.GameOptCreate) go s.PushGameInfo("", extId, "", userInfo.Id) return nil } func (s *GameService) afterCreate(model *domain.Model, userInfo *user_m.GameInfo, userCode, extId string) error { // 清理房间游戏 err := sud.RoomClear(domain.CreateModelNil(), userInfo.MgId, sud.RoomClearReqData{RoomId: userInfo.TxGroupId}) if err != nil { model.Log.Errorf("CreateGame err:%v", err) return myerr.WrapErr(err) } mode := int32(0) if userInfo.MgId == user_e.MgIdLudo { mode = userInfo.Mode } // 加入游戏 err = sud.UserIn(model, userInfo.MgId, sud.UserInReqData{ Code: userCode, RoomId: userInfo.TxGroupId, Mode: mode, Language: "zh-CN", SeatIndex: -1, }) // 设置游戏 if userInfo.MgId == user_e.MgIdLudo { if err = sud.GameSetting(model, userInfo.MgId, sud.GameSettingReqData{ RoomId: userInfo.TxGroupId, LudoRule: sud.LudoRule{Mode: int(userInfo.Mode), ChessNum: int(userInfo.Piece), Item: int(userInfo.OnOff1)}, }); err != nil { model.Log.Errorf("CreateGame err:%v", err) return myerr.WrapErr(err) } } if err != nil { model.Log.Errorf("CreateGame err:%v", err) return myerr.WrapErr(err) } // 准备游戏 err = sud.UserReady(model, userInfo.MgId, sud.UserReadyReqData{ExternalId: extId, IsReady: true}) if err != nil { model.Log.Errorf("CreateGame err:%v", err) return myerr.WrapErr(err) } return nil } func (s *GameService) GameEdit(para *user_m.GameAddParam, userId uint64, extId, code string) error { var model = domain.CreateModelContext(s.svc.MyContext) // 查询游戏是否存在 userInfo, err := user_m.GetGameInfo(model, para.GameId, "", "", -1, -1) if err != nil { mylogrus.MyLog.Errorf("GameEdit err:%v", err) return bizerr.InvalidParameter } if userInfo.Id == 0 { mylogrus.MyLog.Errorf("GameEdit err:%v", err) return bizerr.GameNotFound } // 加锁 if !user_c.LockGame(model, userInfo.TxGroupId) { mylogrus.MyLog.Infof("GameEdit LockGame faild TxGroupId:%v", para.TxGroupId) return bizerr.ReqTooFrequent } defer user_c.UnLockGame(model, userInfo.TxGroupId) // 开始了不能编辑 if userInfo.BattleStartAt > 0 { return bizerr.GameStart } // 钻石不能编辑 if userInfo.Diamond != para.Diamond { return bizerr.GameDiamondCannotEdit } // 放入事务 err = model.Transaction(func(model *domain.Model) error { // 编辑user err = user_m.Edit(model, userInfo.Id, para) if err != nil { model.Log.Errorf("GameEdit err:%v", err) return err } // 设置游戏 if userInfo.MgId == user_e.MgIdLudo { if err = sud.GameSetting(domain.CreateModelNil(), userInfo.MgId, sud.GameSettingReqData{ RoomId: userInfo.TxGroupId, LudoRule: sud.LudoRule{Mode: para.Mode, ChessNum: para.Piece, Item: para.OnOff1}, }); err != nil { model.Log.Errorf("GameEdit err:%v", err) return myerr.WrapErr(err) } } return nil }) //err = s.svc.Transactional(func() error { // model := domain.CreateModelContext(s.svc.MyContext) // // 编辑user // err = user_m.Edit(model, userInfo.Id, para) // if err != nil { // model.Log.Errorf("GameEdit err:%v", err) // return err // } // //// 钻石前后不一致 // //if userInfo.Diamond != para.Diamond { // // // 所有用户退款 // // err = s.userRefund(model, userInfo) // // if err != nil { // // model.Log.Errorf("GameEdit err:%v", err) // // return myerr.WrapErr(err) // // } // // if para.Diamond > 0 { // // // 扣费 // // diamondAccount, err := diamond_m.CheckEnoughDiamondFrozen(model, userId, mysql.Num(para.Diamond)) // // if err != nil { // // model.Log.Errorf("GameEdit err:%v", err) // // return err // // } // // diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(diamond_e.GameJoin, userInfo.Id, mysql.Num(para.Diamond)) // // if err != nil { // // model.Log.Errorf("GameEdit err:%v", err) // // return err // // } // // err = diamondAccountDetail.Persistent() // // if err != nil { // // model.Log.Errorf("GameEdit err:%v", err) // // return err // // } // // } // // // 编辑者重新加入游戏 // // err = user_m.SaveGamePlayer(model, &user_m.GamePlayer{GameId: userInfo.Id, UserId: userId, UserCode: code, ExternalId: extId, CreatedTime: time.Now()}) // // if err != nil { // // model.Log.Errorf("GameEdit err:%v", err) // // return err // // } // // // 游戏相关操作 // // userInfo.Mode, userInfo.Piece, userInfo.OnOff1 = int32(para.Mode), int32(para.Piece), uint8(para.OnOff1) // // err = s.afterCreate(model, userInfo, para.GameCode, extId) // // if err != nil { // // model.Log.Errorf("CreateGame err:%v", err) // // return err // // } // // // 发布事件 // // event := &user_ev.GameEditEvent{GameId: userInfo.Id, TxGroupId: userInfo.TxGroupId} // // if err = user_ev.PublishGameEditEvent(model, event); err != nil { // // model.Log.Errorf("PublishGameEditEvent,event:%v,err:%v", event, err) // // return err // // } // //} else { // // // 设置游戏 // // err = sud.GameSetting(domain.CreateModelNil(), userInfo.MgId, sud.GameSettingReqData{ // // RoomId: userInfo.TxGroupId, // // LudoRule: sud.LudoRule{Mode: para.Mode, ChessNum: para.Piece, Item: para.OnOff1}, // // }) // //} // // 设置游戏 // if userInfo.MgId == user_e.MgIdLudo { // if err = sud.GameSetting(domain.CreateModelNil(), userInfo.MgId, sud.GameSettingReqData{ // RoomId: userInfo.TxGroupId, // LudoRule: sud.LudoRule{Mode: para.Mode, ChessNum: para.Piece, Item: para.OnOff1}, // }); err != nil { // model.Log.Errorf("GameEdit err:%v", err) // return myerr.WrapErr(err) // } // } // return nil //}) go s.PushGameInfo("", "", "", userInfo.Id) return nil } func (s *GameService) PushGameInfo(sourceExtId, targetExtId, txGroupId string, userId uint64) error { defer common.CheckGoPanic() var model = domain.CreateModelContext(s.svc.MyContext) var userInfo *user_m.GameInfo var err error if userId > 0 { userInfo, err = user_m.GetGameInfo(model, userId, "", "", -1, -1) } else { // 房间当前是否有未结束的游戏 userInfo, err = user_m.GetGamingInfo(model, txGroupId) } if err != nil { model.Log.Errorf("PushGameInfo err:%v", err) return err } if userInfo == nil || userInfo.Id == 0 { return nil } // 获取玩家信息 userPlayers, err := user_m.GetGamePlayers(model, userInfo.Id) if err != nil { model.Log.Errorf("PushGameInfo err:%v", err) return err } // 组装信令推送消息 var creatorExternalId string playerMsg := make([]*user_cv.GamePlayerMsg, 0, len(userPlayers)) for _, v := range userPlayers { tmp := &user_cv.GamePlayerMsg{Status: v.Status, IsEscaped: v.IsEscaped, SeatIdx: v.SeatIdx} userTiny, err := user_c.GetUserTinyById(model, v.UserId) if err != nil { model.Log.Errorf("PushGameInfo err:%v", err) return err } tmp.UserTiny = userTiny playerMsg = append(playerMsg, tmp) if v.UserId == userInfo.CreateId { creatorExternalId = userTiny.ExternalId } } msg := &user_cv.GameMsg{ GameId: userInfo.Id, MgId: userInfo.MgId, GameType: userInfo.GameType, Mode: userInfo.Mode + 1, // 客户端那边是:1.quick 2.classic Piece: userInfo.Piece, OnOff1: userInfo.OnOff1, Diamond: userInfo.Diamond, Status: userInfo.Status, AutoMatch: userInfo.AutoMatch, ExternalId: creatorExternalId, Players: playerMsg, } jsonMsg, err := json.Marshal(msg) if err != nil { model.Log.Errorf("PushGameInfo err:%v", err) return err } msgId := group_e.GroupGameInfoLudo if userInfo.MgId == user_e.MgIdUno { msgId = group_e.GroupGameInfoUno } // 发送腾讯云信令 group_s.SendSignalMsg(model, "", userInfo.TxGroupId, group_m.GroupSystemMsg{ MsgId: msgId, Source: sourceExtId, Target: targetExtId, Content: string(jsonMsg), }, false) return nil } func (s *GameService) GameOpt(userId, userId uint64, code, extId, lang, userCode, txGroupId string, opt user_e.GameOpt, seatIdx int8, isAi bool) (err error) { var model = domain.CreateModelContext(s.svc.MyContext) // log go user_m.SaveGameOptLog(model, userId, userId, opt, txGroupId) // 查询游戏是否存在 var userInfo *user_m.GameInfo if opt == user_e.GameOptExit { userInfo, err = user_m.GetGamingInfoByUserId(model, userId, txGroupId) if err != nil { return bizerr.InvalidParameter } if userInfo == nil { return } } else { userInfo, err = user_m.GetGameInfo(model, userId, "", "", -1, -1) if err != nil { return bizerr.InvalidParameter } } if userInfo.Id == 0 { model.Log.Errorf("GameOpt userInfo.Id = 0") return bizerr.GameNotFound } // 玩家信息 userr, err := user_m.GetGamePlayer(model, userInfo.Id, userId) if err != nil { return err } switch opt { case user_e.GameOptJoin: err = s.joinGame(model, userr, userInfo, userId, code, extId, userCode, seatIdx, isAi) if err != nil { model.Log.Errorf("GameOpt joinGame err:%v", err) return err } if !isAi { // 机器人没有加入腾讯云,发不了公屏 go s.SendGamePublicMsg(model, userId, userInfo.TxGroupId, lang, userInfo.GameType, user_e.GameOptJoin) } case user_e.GameOptExit: if userInfo.Status == uint8(user_e.GameStatusNoStart) { if userInfo.CreateId == userr.UserId { err = s.OwnerGameClear(model, userInfo.Id) if err != nil { model.Log.Errorf("GameOpt err:%v", err) return err } } else { err = s.exitGame(model, userr, userInfo) if err != nil { model.Log.Errorf("GameOpt err:%v", err) return err } } } else if userInfo.Status == uint8(user_e.GameStatusGaming) && userr.IsAi != 1 { // 给sdk发退出游戏 err = sud.GameEnd(domain.CreateModelNil(), userInfo.MgId, sud.GameEndReqData{ RoomId: userInfo.TxGroupId, ExternalId: userr.ExternalId, }) if err != nil { model.Log.Errorf("GameOpt sud.GameEnd err:%v", err) } err = user_m.UpdateGamePlayerExit(model, userr.Id) if err != nil { model.Log.Errorf("GameOpt sud.GameEnd err:%v", err) } if userInfo.GameType == user_e.GameTypeUno { // 如果只是剩下一个玩家,直接结束掉 if userPlayers, err := user_m.GetGamePlayers(model, userInfo.Id); err != nil { model.Log.Errorf("GameOpt GetGamePlayers err:%v", err) return err } else { var leftPlayer int for _, player := range userPlayers { if player.EndAt == 0 { leftPlayer++ } } if leftPlayer <= 1 { model.Log.Infof("GameOpt left player 1,roomClear:%v,players:%v", userInfo, userPlayers) if err := sud.RoomClear(model, userInfo.MgId, sud.RoomClearReqData{RoomId: userInfo.TxGroupId}); err != nil { model.Log.Infof("GameOpt RoomClear fail,user:%v:%v", *userInfo, err) } } } } } } go s.PushGameInfo("", "", "", userInfo.Id) return nil } func (s *GameService) joinGame(model *domain.Model, userr *user_m.GamePlayer, userInfo *user_m.GameInfo, userId uint64, code, extId, userCode string, seatIdx int8, isAi bool) error { if !isAi && userCode == "" { model.Log.Errorf("joinGame err:%v", bizerr.InvalidParameter) return bizerr.InvalidParameter } if userr.Id > 0 { model.Log.Errorf("joinGame err:%v", bizerr.GameAlreadyJoin) return bizerr.GameAlreadyJoin } if userInfo.Status != uint8(user_e.GameStatusNoStart) { return bizerr.GameJoinFailed } // 在麦上才能加入游戏 micUser, err := group_m.GetMicUserByExternalId(model, extId) if err != nil { return err } if micUser == nil { return bizerr.GameAddNotOnMic } if !user_c.LockGame(model, userInfo.TxGroupId) { mylogrus.MyLog.Infof("joinGame LockGame faild TxGroupId:%v", userInfo.TxGroupId) return bizerr.ReqTooFrequent } defer user_c.UnLockGame(model, userInfo.TxGroupId) return model.Transaction(func(model *domain.Model) error { var isAiInt uint8 if isAi { isAiInt = 1 } userInfoNew, err := user_m.GetGameInfo(model, userInfo.Id, "", "", -1, -1) if err != nil { return bizerr.InvalidParameter } if userInfoNew.Status != uint8(user_e.GameStatusNoStart) { // 游戏状态不对,不能加入 return bizerr.GameJoinFailed } if !isAi && userInfo.Diamond > 0 { // 扣费 diamondAccount, err := diamond_m.CheckEnoughDiamondFrozen(model, userId, mysql.Num(userInfo.Diamond)) if err != nil { model.Log.Errorf("joinGame err:%v", err) return err } diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(diamond_e.GameJoin, userInfo.Id, mysql.Num(userInfo.Diamond)) if err != nil { model.Log.Errorf("joinGame err:%v", err) return err } err = diamondAccountDetail.Persistent() if err != nil { model.Log.Errorf("joinGame err:%v", err) return err } } userr.GameId = userInfo.Id userr.UserId = userId userr.UserCode = code userr.SeatIdx = seatIdx userr.ExternalId = extId userr.IsAi = isAiInt userr.CreatedTime = time.Now() // 保存游戏玩家信息 err = user_m.SaveGamePlayer(model, userr) if err != nil { mylogrus.MyLog.Infof("joinGame SaveGamePlayer faild:%v, err:%v", userr, err) return bizerr.GameHaveNoEndGame } err = s.sendJoinToSDK(model, userr, userInfo, isAi, userCode, extId) if err != nil { model.Log.Errorf("joinGame err:%v", err) return err } return nil }) } func (s *GameService) exitGame(model *domain.Model, userr *user_m.GamePlayer, userInfo *user_m.GameInfo) error { lockKey := user_c.GetGameKey(userInfo.TxGroupId) if !cache.TryLock(model, lockKey, time.Millisecond*150, time.Minute*5) { mylogrus.MyLog.Infof("exitGame LockGame faild TxGroupId:%v", userInfo.TxGroupId) return bizerr.ReqTooFrequent } defer cache.UnLock(model, lockKey) //if !user_c.LockGame(model, userInfo.TxGroupId) { // mylogrus.MyLog.Infof("exitGame LockGame faild TxGroupId:%v", userInfo.TxGroupId) // return bizerr.ReqTooFrequent //} //defer user_c.UnLockGame(model, userInfo.TxGroupId) return model.Transaction(func(model *domain.Model) error { err := user_m.DelGamePlayer(model, userr.Id) if err != nil { model.Log.Errorf("GameOpt err:%v", err) return err } // 玩家退费 err = s.userrRefund(model, userr, userInfo) if err != nil { model.Log.Errorf("GameOpt err:%v", err) return err } // 判断游戏是否还有人 players, err := user_m.GetGamePlayers(model, userInfo.Id) if err != nil { model.Log.Errorf("GameOpt err:%v", err) return err } // 是否都是机器人 allAi := true for _, v := range players { if v.IsAi != 1 { allAi = false break } } if len(players) == 0 || allAi { // 修改所有游戏玩家状态 err = user_m.GameCloseUpdatePlayer(model, userInfo) if err != nil { return err } err = sud.RoomClear(domain.CreateModelNil(), userInfo.MgId, sud.RoomClearReqData{ RoomId: userInfo.TxGroupId, }) // 修改游戏状态 err = user_m.GameCloseUpdate(model, userInfo) if err != nil { return err } } else { sud.UserReady(domain.CreateModelNil(), userInfo.MgId, sud.UserReadyReqData{ ExternalId: userr.ExternalId, IsReady: false, }) //if err != nil { // model.Log.Errorf("GameOpt err:%v", err) // //return err //} sud.UserOut(domain.CreateModelNil(), userInfo.MgId, sud.UserOutReqData{ ExternalId: userr.ExternalId, }) //if err != nil { // model.Log.Errorf("GameOpt err:%v", err) // //return err //} } return nil }) } func (s *GameService) sendJoinToSDK(model *domain.Model, userr *user_m.GamePlayer, userInfo *user_m.GameInfo, isAi bool, userCode, extId string) (err error) { if isAi { user, err := user_c.GetUserTinyById(model, userr.UserId) if err != nil { model.Log.Errorf("sendJoinToSDK err:%v", err) return err } gender := "male" if user.Sex == 2 { gender = "female" } return sud.AddAi(domain.CreateModelNil(), userInfo.MgId, sud.AiAddReqData{ RoomId: userInfo.TxGroupId, AiPlayers: []sud.AiPlayer{{user.ExternalId, user.Avatar, user.Nick, gender}}, IsReady: 1, }) } mode := int32(0) if userInfo.GameType == user_e.GameTypeLudo { mode = userInfo.Mode } err = sud.UserIn(domain.CreateModelNil(), userInfo.MgId, sud.UserInReqData{ Code: userCode, RoomId: userInfo.TxGroupId, Mode: mode, Language: "zh-CN", SeatIndex: -1, }) if err != nil { model.Log.Errorf("sendJoinToSDK err:%v", err) return myerr.WrapErr(bizerr.GameJoinFailed) } err = sud.UserReady(domain.CreateModelNil(), userInfo.MgId, sud.UserReadyReqData{ ExternalId: extId, IsReady: true, }) if err != nil { model.Log.Errorf("sendJoinToSDK err:%v", err) return myerr.WrapErr(bizerr.GameJoinFailed) } return nil } func (s *GameService) GameStart(userStart *user_ev.GameStartObject) error { return s.svc.Transactional(func() error { model := domain.CreateModel(s.svc.CtxAndDb) return user_m.GameStartUpdate(model, userStart) }) } func (s *GameService) GameSettle(userSettle *user_ev.GameSettleObject) error { model := domain.CreateModel(s.svc.CtxAndDb) // 查询游戏 infoKey, err := strconv.Atoi(userSettle.ReportGameInfoKey) if err != nil { model.Log.Errorf("GameSettle err:%v", err) return myerr.WrapErr(bizerr.GameNotFound) } userInfo, err := user_m.GetGameInfo(model, uint64(infoKey), "", "", -1, 1) if err != nil { model.Log.Errorf("GameSettle err:%v", err) return myerr.WrapErr(bizerr.GameNotFound) } if userInfo.Id == 0 { model.Log.Errorf("GameSettle err:%v, GameRoundId:%v", bizerr.GameNotFound, userSettle.GameRoundId) return myerr.WrapErr(bizerr.GameNotFound) } err = model.Transaction(func(model *domain.Model) error { // 计算排名奖励 userExtIds, winList, userSettle2, err := s.calcAwardDiamond(userSettle, userInfo) if err != nil { model.Log.Errorf("GameSettle err:%v", err) return err } userSettle = userSettle2 if userInfo.Diamond > 0 { userMap, err := user_m.GetGamePlayersMap(model, userInfo.Id, userExtIds) if err != nil { model.Log.Errorf("GameSettle err:%v", err) return err } if userInfo.Status == 1 { // 避免多发 // 发奖励 err = s.sendAwardDiamond(model, userInfo, winList, userMap) if err != nil { model.Log.Errorf("GameSettle err:%v", err) return err } } } // 更新游戏信息、玩家信息 err = user_m.GameSettleUpdate(model, userSettle) if err != nil { model.Log.Errorf("GameSettle err:%v", err) return err } return nil }) if err != nil { model.Log.Errorf("GameSettle err:%v", err) return err } if userInfo.Status == 1 { // 因为sdk可能有多个settle上报过来,所以这里只处理游戏状态还没转变的 // 组装信令推送消息 model := domain.CreateModelContext(model.MyContext) go s.userSettlePushMsg(model, userSettle, userInfo.TxGroupId, userInfo.CreateId, userInfo.GameType) } return nil } func (s *GameService) calcAwardDiamond(userSettle *user_ev.GameSettleObject, userInfo *user_m.GameInfo) ([]string, []*user_ev.PlayerResultObject, *user_ev.GameSettleObject, error) { // 计算奖励 userExtIds := make([]string, 0) allDiamond := float64(userInfo.Diamond) * float64(len(userSettle.Results)) winList := make([]*user_ev.PlayerResultObject, 0, len(userSettle.Results)) diamondMap := make(map[string]int64) for _, v := range userSettle.Results { userExtIds = append(userExtIds, v.Uid) // 逃跑不算win if v.IsEscaped != 1 { tmpWin := new(user_ev.PlayerResultObject) copier.Copy(tmpWin, &v) tmpWin.LudoExtras = new(user_ev.LudoExtras) json.Unmarshal([]byte(tmpWin.Extras), &tmpWin.LudoExtras) winList = append(winList, tmpWin) } } switch userInfo.GameType { case user_e.GameTypeLudo: // 排序:isWin -> score -> steps sort.Slice(winList, func(i, j int) bool { if winList[i].IsWin == 2 && winList[j].IsWin != 2 { return true } if winList[i].Score > winList[j].Score { return true } if winList[i].LudoExtras.Steps >= winList[j].LudoExtras.Steps { return true } return false }) if len(userSettle.Results) > 2 { // 游戏人数大于2人,2人获胜瓜分 for i, v := range winList { switch i { case 0: v.Diamond = int64(math.Floor(allDiamond * 0.65)) case 1: v.Diamond = int64(math.Floor(allDiamond * 0.30)) default: v.Diamond = int64(userInfo.Diamond) * -1 } diamondMap[v.Uid] = v.Diamond } } else { for i, v := range winList { if i == 0 { v.Diamond = int64(math.Floor(allDiamond * 0.95)) } else { v.Diamond = int64(userInfo.Diamond) * -1 } diamondMap[v.Uid] = v.Diamond } } case user_e.GameTypeUno: // 排序:isWin -> score sort.Slice(winList, func(i, j int) bool { if winList[i].IsWin == 2 && winList[j].IsWin != 2 { return true } return winList[i].Score > winList[j].Score }) if len(winList) > 0 { // 第一名获得游戏分成的95%,系统抽成5% diamond := int64(math.Floor(allDiamond * 0.95)) winList[0].Diamond = diamond diamondMap[winList[0].Uid] = diamond } } // 给客户端的返回结果赋值 for i, v := range userSettle.Results { if winDia, ok := diamondMap[v.Uid]; ok { userSettle.Results[i].Diamond = winDia if winDia > 0 { userSettle.Results[i].IsWin = 2 } } else { userSettle.Results[i].Diamond = int64(userInfo.Diamond) * -1 } } return userExtIds, winList, userSettle, nil } func (s *GameService) sendAwardDiamond(model *domain.Model, userInfo *user_m.GameInfo, winList []*user_ev.PlayerResultObject, userMap map[string]*user_m.GamePlayer) error { if userInfo.Status != uint8(user_e.GamerStatusGaming) { return nil } for _, v := range winList { if v.Diamond <= 0 || v.IsAi == 1 { continue } player, ok := userMap[v.Uid] if !ok { mylogrus.MyLog.Errorf("sendAwardDiamond 找不到用户 extId:%v, userMap:%v", v.Uid, userMap) continue } diamondAccount, err := diamond_m.GetDiamondAccountByUserId(model, player.UserId) if err != nil { model.Log.Errorf("GameSettle err:%v", err) return err } diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(diamond_e.GameAward, userInfo.Id, mysql.Num(v.Diamond)) if err != nil { model.Log.Errorf("GameSettle err:%v", err) return err } err = diamondAccountDetail.Persistent() if err != nil { model.Log.Errorf("GameSettle err:%v", err) return err } } return nil } func (s *GameService) userSettlePushMsg(model *domain.Model, userSettle *user_ev.GameSettleObject, txGroupId string, createId uint64, userType user_e.GameType) { defer common.CheckGoPanic() if txGroupId == "" { model.Log.Errorf("userSettlePushMsg txGroupId is null") return } createExtId := "" // 组装信令推送消息 playerMsg := make([]*user_cv.GameAwardPlayer, 0, len(userSettle.Results)) for _, v := range userSettle.Results { tmp := &user_cv.GameAwardPlayer{Rank: v.Rank, IsWin: v.IsWin == 2, Diamond: v.Diamond} userTiny, err := user_c.GetUserByExternalId(model, v.Uid) if err != nil { model.Log.Errorf("PushGameInfo err:%v,v:%v,userSettle:%v", err, v, *userSettle) continue } tmp.UserTiny = userTiny playerMsg = append(playerMsg, tmp) if userTiny.ID == createId { createExtId = userTiny.ExternalId } } msg := &user_cv.GameAward{ OwnerId: createExtId, Players: playerMsg, } jsonMsg, err := json.Marshal(msg) if err != nil { model.Log.Errorf("GameSettle err:%v", err) return } //time.Sleep(time.Second * 2) // 延迟两秒,避免客户端棋子没有走完就弹结算 // 发送腾讯云信令 msgId := group_e.GroupGameSettleLudo if userType == user_e.GameTypeUno { msgId = group_e.GroupGameSettleUno } group_s.SendSignalMsg(model, "", txGroupId, group_m.GroupSystemMsg{ MsgId: msgId, Content: string(jsonMsg), }, false) } func (s *GameService) SendGamePublicMsg(model *domain.Model, userId uint64, txGroupId, lang string, userType user_e.GameType, opt user_e.GameOpt) error { defer common.CheckGoPanic() user, err := user_c.GetUserTinyById(model, userId) if err != nil { model.Log.Errorf("SendGamePublicMsg err:%v", err) return err } nobleLevel, err := noble_m.GetNobleLevel(model.Db, userId) if err != nil { model.Log.Errorf("SendGamePublicMsg err:%v", err) return err } // 公屏内容 userName := "Game" switch userType { case user_e.GameTypeLudo: userName = "Ludo" case user_e.GameTypeUno: userName = "Uno" } msgTmp := "Take mic and create a user" msgId := common.MSG_ID_GAME_CREATE msgContent := msgTmp if opt == user_e.GameOptJoin { msgTmp = "I joined the %s" msgId = common.MSG_ID_GAME_JOIN msgContent = fmt.Sprintf(msgTmp, userName) } if resMul, _ := res_m.GetResMultiTextBy(model.Db, msgId, lang); resMul != nil { msgContent = resMul.Content if opt == user_e.GameOptJoin { msgContent = fmt.Sprintf(resMul.Content, userName) } } typeScreen := group_e.GameLudoPubMsg if userType == user_e.GameTypeUno { typeScreen = group_e.GameUnoPubMsg } msg := msg_cv.GamePublicMsg{CommonPublicMsg: msg_cv.CommonPublicMsg{Type: typeScreen, GameType: userType, ExternalId: user.ExternalId, Nick: user.Nick, Avatar: user.Avatar, NobleLevel: nobleLevel}, Msg: msgContent} body, err := json.Marshal(msg) if err != nil { return myerr.WrapErr(err) } //发送公屏消息, u, err := tencentyun.SendCustomMsg(model.Log, txGroupId, &user.ExternalId, string(body), group_m.GetHiloUserInfo(domain.CreateModelContext(model.MyContext), user.ExternalId)) model.Log.Infof("SendGamePublicMsg result response.MsgSeq:%v, err:%v", u, err) return err } func (s *GameService) GameQuickMatch(userId uint64, externalId string, userType user_e.GameType) (txGroupId, userCode string, err error) { var model = domain.CreateModelContext(s.svc.MyContext) txGroupId, err = user_m.GetGamingTxGroupId(model, userType) if err != nil { return "", "", myerr.WrapErr(err) } if txGroupId == "" { // 获取自己房间的id groupInfo, err := group_m.GetGroupInfoByOwner(model, userId) if err != nil { return "", "", myerr.WrapErr(err) } if groupInfo == nil { // 自己没有房间 return "", "", myerr.WrapErr(bizerr.GameHaveNoMyRoom) } txGroupId = groupInfo.TxGroupId } // 获取userCode userCode, err = s.GenClientCode(userId, externalId) if err != nil { return "", "", myerr.WrapErr(err) } return } // 游戏房间主人清理 func (s *GameService) OwnerGameClear(model *domain.Model, userId uint64) error { userInfo, err := user_m.GetGameInfo(model, userId, "", "", -1, -1) if err != nil { model.Log.Errorf("GameClear err:%v", err) return myerr.WrapErr(err) } lockKey := user_c.GetGameKey(userInfo.TxGroupId) if !cache.TryLock(model, lockKey, time.Millisecond*150, time.Minute*5) { mylogrus.MyLog.Infof("OwnerGameClear LockGame faild TxGroupId:%v", userInfo.TxGroupId) return bizerr.ReqTooFrequent } defer cache.UnLock(model, lockKey) //if !user_c.LockGame(model, userInfo.TxGroupId) { // mylogrus.MyLog.Infof("OwnerGameClear LockGame faild TxGroupId:%v", userInfo.TxGroupId) // return bizerr.ReqTooFrequent //} //defer user_c.UnLockGame(model, userInfo.TxGroupId) return model.Transaction(func(model *domain.Model) error { // 修改游戏状态 err = user_m.GameCloseUpdate(model, userInfo) if err != nil { model.Log.Errorf("GameClear err:%v", err) return err } // 用户退费 err = s.userRefund(model, userInfo) if err != nil { model.Log.Errorf("GameClear err:%v", err) return myerr.WrapErr(err) } // 清理房间游戏 err = sud.RoomClear(domain.CreateModelNil(), userInfo.MgId, sud.RoomClearReqData{RoomId: userInfo.TxGroupId}) if err != nil { model.Log.Errorf("GameClear err:%v", err) return myerr.WrapErr(err) } return nil }) } // 游戏房间清理,所有人踢出游戏,退钻,游戏置为结束;拿到房间锁之后才能调用该方法 func (s *GameService) GameClear(model *domain.Model, userId uint64) error { userInfo, err := user_m.GetGameInfo(model, userId, "", "", -1, -1) if err != nil { model.Log.Errorf("GameClear err:%v", err) return myerr.WrapErr(err) } // 用户退费 err = s.userRefund(model, userInfo) if err != nil { model.Log.Errorf("GameClear err:%v", err) return myerr.WrapErr(err) } // 修改游戏状态 err = user_m.GameCloseUpdate(model, userInfo) if err != nil { model.Log.Errorf("GameClear err:%v", err) return err } // 清理房间游戏 err = sud.RoomClear(domain.CreateModelNil(), userInfo.MgId, sud.RoomClearReqData{RoomId: userInfo.TxGroupId}) if err != nil { model.Log.Errorf("GameClear err:%v", err) return myerr.WrapErr(err) } go s.PushGameInfo("", "", "", userInfo.Id) return nil } // 游戏房间玩家退钻 func (s *GameService) userRefund(model *domain.Model, userInfo *user_m.GameInfo) (err error) { var players []*user_m.GamePlayer if userInfo.Diamond > 0 { players, err = user_m.GetGamePlayers(model, userInfo.Id) if err != nil { model.Log.Errorf("GameRefund err:%v", err) return myerr.WrapErr(err) } } err = user_m.DelGamePlayers(model, userInfo.Id) if err != nil { model.Log.Errorf("GameRefund err:%v", err) return myerr.WrapErr(err) } if userInfo.Diamond <= 0 { return nil } if userInfo.Diamond > 0 { for _, v := range players { err = s.userrRefund(model, v, userInfo) if err != nil { model.Log.Errorf("GameRefund err:%v", err) return err } } } return nil } // 游戏房间玩家退钻 func (s *GameService) userrRefund(model *domain.Model, userr *user_m.GamePlayer, userInfo *user_m.GameInfo) error { if userr.UserId <= 0 || userr.IsAi == 1 || userInfo.Diamond <= 0 { return nil } // 退费 diamondAccount, err := diamond_m.GetDiamondAccountByUserId(model, userr.UserId) if err != nil { model.Log.Errorf("GameRefund err:%v", err) return err } diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(diamond_e.GameRefund, userInfo.Id, mysql.Num(userInfo.Diamond)) if err != nil { model.Log.Errorf("GameRefund err:%v", err) return err } err = diamondAccountDetail.Persistent() if err != nil { model.Log.Errorf("GameRefund err:%v", err) return err } return nil }