package room import ( "encoding/json" "fmt" "game/common/proto/pb" "github.com/fox/fox/ipb" "github.com/fox/fox/log" "sort" "sync" "time" ) func (rm *ColorRoom) SceneUserSitDown(user *model.User) { _ = user rm.SendOnlinePlayerNum() } // CheckAndBet 检查并下注 func (rm *ColorRoom) CheckAndBet(user *model.User, bets []*pb.ColorPinoyLiveBetReq) { if len(bets) == 0 { log.Error(rm.Log("玩家(%d) bets data err :%v ", bets)) model.SendBetFailMessage(model.DataErr, user) return } betInfos := [config.BET_TYPE_NUM]int64{} betCountInfos := [config.BET_TYPE_NUM]int64{} totalBetAmount := int64(0) oneBetMessage := &pb.ColorPinoyLiveS2CRepetBet{} oneBetMessage.Uid = user.UserID oneBetMessage.BetInfo = []*pb.ColorPinoyLiveBetSuccessMessage{} if rm.GetGameStatus() != pb.ColorPinoyLiveGameStatus_ColorPinoyLiveBetStatus { log.Error(rm.Log("玩家(%d) UndoBet data err GetGameStatus:%v,LastTimeBet:%v ", user.UserID, rm.GetGameStatus(), user.LastTimeBet)) model.SendBetFailMessage(model.StatusError, user) return } for _, bet := range bets { // 判断下注下标和下注区域下标是否超出列表 if _, ok := pb.ColorPinoyLiveBetTypeJP_name[int32(bet.BetType)]; !ok || bet.BetLevel < 0 || bet.BetLevel >= int32(len(rm.RoomCfg.ColorPinoyLiveConfig.BetList)) || bet.BetIndex < 0 || bet.BetIndex >= int32(len(rm.RoomCfg.ColorPinoyLiveConfig.BetList[bet.BetLevel])) { log.Error(rm.Log("玩家(%d) bets data err BetLevel:%d BetIndex:%d ", bet.BetLevel, bet.BetIndex)) model.SendBetFailMessage(model.DataErr, user) return } // 下注总金额 betAmountCount := rm.RoomCfg.ColorPinoyLiveConfig.BetList[bet.BetLevel][bet.BetIndex] bet.BetAmount = betAmountCount if betAmountCount > user.Balance-totalBetAmount { log.Debug(rm.Log("用户余额为:%v 总押注%d 押注%d", user.Balance, totalBetAmount, betAmountCount)) model.SendBetFailMessage(model.ScoreLess, user) return } else { totalBetAmount += betAmountCount betInfos[bet.BetType%config.BET_TYPE_NUM] += betAmountCount betCountInfos[bet.BetType%config.BET_TYPE_NUM] += 1 } } if totalBetAmount <= 0 || totalBetAmount+user.TotalBet > rm.RoomCfg.ColorPinoyLiveConfig.TotalBetLimit { log.Error(rm.Log("DataErr totalBetAmount:%d err ", totalBetAmount)) model.SendBetFailMessage(model.PlayerEarTopScore, user) return } log.Debug(rm.LogEx(user, "下注区域最大金额:%v", rm.Cfg.AreaBetLimit)) for i, num := range betInfos { // 投注大于下注区域限制 if (user.TotalBets[i] + num) > rm.Cfg.AreaBetLimit { // log.Debug("PlayerEarTopScore totalBetAmount:%d err ", totalBetAmount) model.SendBetFailMessage(model.PlayerEarTopScore, user) return } } user.AddBalance(-totalBetAmount) if user.LastTimeBet == nil { user.LastTimeBet = make([][]*pb.ColorPinoyLiveBetReq, 0) } user.LastTimeBet = append(user.LastTimeBet, bets) user.TotalBet += totalBetAmount // log.Debug("上次下注:", user.LastTimeBet) rm.MutexData.Lock() for i, num := range betInfos { if num > 0 { user.TotalBets[i] += num rm.TotalBets[i] += num SendSuccessMessage := new(pb.ColorPinoyLiveBetSuccessMessage) SendSuccessMessage.BetType = pb.ColorPinoyLiveBetTypeJP(i) SendSuccessMessage.UserBet = num SendSuccessMessage.UserBets = user.TotalBets[i] + num SendSuccessMessage.TotalBets = rm.TotalBets[i] + num oneBetMessage.BetInfo = append(oneBetMessage.BetInfo, SendSuccessMessage) } } // 统计个区域投注次数 // user.TotalBetsCount = make([]int64, config.BET_TYPE_NUM) for i, count := range betCountInfos { if count > 0 { user.TotalBetsCount[i] += count } } user.AllBet += totalBetAmount user.NoBetCount = 0 oneBetMessage.UserScore = user.Balance // oneBetMessage.UserBets = user.TotalBets[:] rm.TotalBet += totalBetAmount rm.MutexData.Unlock() rm.Table.Broadcast(int32(pb.ColorPinoyLiveSendToClientMessageType_ColorPinoyLiveNoticeGameSeatUserBet), oneBetMessage) live.PushColorBet(&lws.CPLBet{ Uid: user.UserID, Nickname: user.UserInetr.GetNike(), Bet: totalBetAmount, Avatar: user.UserInetr.GetHead(), }) } func (rm *ColorRoom) UndoBet(user *model.User, undoType pb.ColorPinoyLiveUndoType) { betInfos := [config.BET_TYPE_NUM]int64{} betCountInfos := [config.BET_TYPE_NUM]int64{} totalBetAmount := int64(0) undoBetMessage := &pb.S2CUndoBet{} undoBetMessage.Uid = user.UserID undoBetMessage.BetInfo = []*pb.ColorPinoyLiveBetSuccessMessage{} undoBetMessage.UndoType = undoType if rm.GetGameStatus() != pb.ColorPinoyLiveGameStatus_ColorPinoyLiveBetStatus { log.Error(rm.Log("玩家(%d) UndoBet data err GetGameStatus:%v,LastTimeBet:%v ", user.UserID, rm.GetGameStatus(), user.LastTimeBet)) model.SendBetFailMessage(model.StatusError, user) return } if len(user.LastTimeBet) == 0 { log.Error(rm.Log("玩家(%d) LastTimeBet null err:%v ", user.UserID, user.LastTimeBet)) model.SendBetFailMessage(model.LastBetNull, user) return } var bets []*pb.ColorPinoyLiveBetReq if undoType == pb.ColorPinoyLiveUndoType_ColorPinoyLiveUndoOne { bets = user.LastTimeBet[len(user.LastTimeBet)-1] } else if undoType == pb.ColorPinoyLiveUndoType_ColorPinoyLiveUndoAll { for _, bet := range user.LastTimeBet { bets = append(bets, bet...) } } else { log.Error(rm.Log("玩家(%d) undoType err %v,", user.UserID, undoType)) model.SendBetFailMessage(model.DataErr, user) return } if len(bets) == 0 { log.Error(rm.Log("玩家(%d) bets err %v,", user.UserID, bets)) model.SendBetFailMessage(model.LastBetNull, user) return } for _, bet := range bets { // 判断下注下标和下注区域下标是否超出列表 if _, ok := pb.ColorPinoyLiveBetTypeJP_name[int32(bet.BetType)]; !ok { log.Error(rm.Log("玩家(%d) bet err %v,", bet)) model.SendBetFailMessage(model.DataErr, user) return } totalBetAmount += bet.BetAmount betInfos[bet.BetType%config.BET_TYPE_NUM] += bet.BetAmount betCountInfos[bet.BetType%config.BET_TYPE_NUM] += 1 } if totalBetAmount > 0 { user.AddBalance(totalBetAmount) } if user.LastTimeBet != nil && len(user.LastTimeBet) > 0 { if undoType == pb.ColorPinoyLiveUndoType_ColorPinoyLiveUndoOne { user.LastTimeBet = user.LastTimeBet[:len(user.LastTimeBet)-1] } else { user.LastTimeBet = nil } } rm.MutexData.Lock() for i, num := range betInfos { user.TotalBets[i] -= num rm.TotalBets[i] -= num } // 统计个区域投注次数 for i, count := range betCountInfos { if count > 0 { user.TotalBetsCount[i] -= count } if user.TotalBetsCount[i] < 0 { user.TotalBetsCount[i] = 0 } } rm.TotalBet -= totalBetAmount user.TotalBet -= totalBetAmount user.AllBet -= totalBetAmount user.NoBetCount = 0 for i, num := range betInfos { SendSuccessMessage := new(pb.ColorPinoyLiveBetSuccessMessage) SendSuccessMessage.SeatId = int32(user.SceneChairId) SendSuccessMessage.BetType = pb.ColorPinoyLiveBetTypeJP(i) SendSuccessMessage.UserBet = num SendSuccessMessage.UserBets = user.TotalBets[i] SendSuccessMessage.TotalBets = rm.TotalBets[i] undoBetMessage.BetInfo = append(undoBetMessage.BetInfo, SendSuccessMessage) } rm.MutexData.Unlock() // 玩家当前分数 undoBetMessage.UserScore = user.Balance // log.Debug("undoBetMessage %v", undoBetMessage) rm.Table.Broadcast(int32(pb.ColorPinoyLiveSendToClientMessageType_ColorPinoyLiveNoticeUndoBet), undoBetMessage) } func (rm *ColorRoom) KickOutUser(u *model.User) { // delete(rm.AllUserList, k) rm.DeletePlayer(u.UserID) if u.SceneChairId != 0 { rm.OnUserStanUp(u) } role := "机器人" // rm.DeleteExitUserFromOnlineUserListSlice(u) if !u.IsRobot { rm.Table.KickOut(u.UserInetr) role = "玩家" } log.Debug(rm.Log("删除(%s)(%d) 金额:%d 输赢:%d", role, u.UserID, u.Balance, u.Balance-u.UserInetr.GetScore())) rm.SendOnlinePlayerNum() } func (rm *ColorRoom) TransInoutGameBet(user *model.User, bet int64) error { if config.CHIPS_DEBUG { user.Balance -= bet return nil } user.Mn.Lock() defer user.Mn.Unlock() transferInOutResp, err := rm.Table.TransInoutGameCarryAdd(user.UserInetr, rm.Table.GetLevel(), bet, "") if err != nil { return err } user.Balance = transferInOutResp.Balance user.Carry = transferInOutResp.Carry user.UserInetr.SetPreserve(transferInOutResp.Preserve) // time.Sleep(10 * time.Second) return nil } func (rm *ColorRoom) TransInoutGameEnd(user *model.User, bet int64, amountWin int64, tax int64) (int64, error) { if config.CHIPS_DEBUG || user.IsRobot { user.Balance += amountWin return amountWin, nil } transferInOutResp, err := rm.Table.TransInoutGameEnd(user.UserInetr, rm.Table.GetLevel(), bet+tax, amountWin+tax, "") if err != nil { return 0, err } user.Balance = transferInOutResp.GetBalance() if transferInOutResp.GetRealTransfer() == 0 { return 0, errors.New("error") } user.TransBet += bet user.TransWin += amountWin // time.Sleep(10 * time.Second) return transferInOutResp.RealTransfer, nil } func (rm *ColorRoom) ResetUserBet(user *model.User) { rm.MutexData.Lock() for i, bet := range user.TotalBets { rm.TotalBets[i] -= bet // rm.BetNumber[i] -= user.BetNumber[i] } rm.TotalBet -= user.TotalBet rm.MutexData.Unlock() user.TotalBet = 0 user.TotalBets = [config.BET_TYPE_NUM]int64{} // user.BetNumber = [config.BET_TYPE_NUM]int64{} } func (rm *ColorRoom) StartTransInoutBet() { wg := new(sync.WaitGroup) var failUser []*model.User failMutx := &sync.Mutex{} rm.Traverse(func(u *model.User) bool { wg.Add(1) go func(user *model.User) { defer wg.Done() user.Mn.Lock() defer user.Mn.Unlock() if user.TotalBet > 0 { _, err := rm.TransInoutGameEnd(user, user.TotalBet, 0, 0) if err != nil { func() { failMutx.Lock() defer failMutx.Unlock() failUser = append(failUser, user) }() } } }(u) return true }) wg.Wait() // 异步扣款完成后,处理部分扣款失败回滚操作 if len(failUser) > 0 { pbMsg := &pb.ColorPinoyLiveS2CBetEndFailResult{ Code: 1, } for _, user := range failUser { rm.ResetUserBet(user) player := &pb.ColorPinoyLiveS2CRepetBet{} player.UserScore = user.Balance player.Uid = user.UserID var bets []*pb.ColorPinoyLiveBetReq for _, bet := range user.LastTimeBet { bets = append(bets, bet...) } for _, bet := range bets { // 判断下注下标和下注区域下标是否超出列表 if _, ok := pb.ColorPinoyLiveBetTypeJP_name[int32(bet.BetType)]; !ok { log.Error(rm.Log("玩家(%d) bet err %v,", bet)) model.SendBetFailMessage(model.DataErr, user) return } SendSuccessMessage := new(pb.ColorPinoyLiveBetSuccessMessage) SendSuccessMessage.BetIndex = bet.BetIndex SendSuccessMessage.BetLevel = bet.BetLevel SendSuccessMessage.BetType = bet.BetType SendSuccessMessage.SeatId = int32(user.SceneChairId) SendSuccessMessage.UserBet = bet.BetAmount SendSuccessMessage.TotalBets = rm.TotalBets[bet.BetType] player.BetInfo = append(player.BetInfo, SendSuccessMessage) } pbMsg.Players = append(pbMsg.Players, player) } // log.Debug("玩家停止下注后 扣钱失败: %v", pbMsg) rm.Table.Broadcast(int32(pb.ColorPinoyLiveSendToClientMessageType_ColorPinoyLiveNoticePlayerBetEndResultFailed), pbMsg) } // 大客投注 var allWinner []*pb.ColorPinoyLiveBigWinner rm.Traverse(func(user *model.User) bool { if user.TotalBet > 0 { allWinner = append(allWinner, &pb.ColorPinoyLiveBigWinner{NickName: user.UserInetr.GetNike(), Avatar: user.UserInetr.GetHead(), WinChips: user.TotalBet, AreaId: user.TotalBets[:]}) } return true }) rm.updateBigWinner(allWinner) bigMsg := &pb.ColorPinoyLivePlayerBigWinner{BigBet: allWinner, Jackpot: rm.jackpotMgr.GetJackpot()} rm.Table.Broadcast(int32(pb.ColorPinoyLiveSendToClientMessageType_ColorPinoyLiveNoticeGameBigWinner), bigMsg) rm.TimerJob, _ = rm.Table.AddTimer(time.Duration(rm.RoomCfg.TimeConf.Endmove), func() { rm.NotifyBigBetAreaMul() }) allUserTotalBet := int64(0) // 所有玩家投注未中奖的总金额,用于计算jackpot池 rm.Traverse(func(user *model.User) bool { if user.TotalBet < 1 { return true } allUserTotalBet += user.TotalBet return true }) log.Debug(rm.Log("本局总投注金额为:%v 赎回比例:%v 追加比例:%v 满额追加比例:%v", allUserTotalBet, rm.Cfg.JpXRate, rm.Cfg.JpYRate, rm.Cfg.JpXYRate)) rm.jackpotX, rm.jackpotY = rm.jackpotMgr.AddJp(allUserTotalBet, int64(rm.Cfg.JpXRate), int64(rm.Cfg.JpYRate), int64(rm.Cfg.JpXYRate)) if rm.jackpotX > 0 || rm.jackpotY > 0 { rm.kafkaJackpotUserRepaid(rm.jackpotX, rm.jackpotY) } } // 计算 下注区域中奖得分,返回是否有jp奖,jp奖位置,中奖人数 func (rm *ColorRoom) CalculateJackpotScore() (pb.ColorPinoyLiveBetTypeJP, map[int64]int64) { jackpotArea := pb.ColorPinoyLiveBetTypeJP(-1) for _, winArea := range rm.PokerMsg.WinBetArea { if !winArea.IsJackpot || !winArea.IsWin { continue } color := getColorByBetArea(winArea.BetArea) colorCount := getColorCount(rm.NormalDices, color) if colorCount != 3 { continue } jackpotArea = winArea.BetArea rm.Traverse(func(user *model.User) bool { if user.TotalBets[winArea.BetArea] <= 0 { return true } rm.jackpotMgr.AddJpUser(user.UserID, user.TotalBets[winArea.BetArea]) return true }) } rm.jackpotUser, rm.costJackpot = rm.jackpotMgr.WinJackpot() if rm.costJackpot > 0 { rm.kafkaHitJackpot(rm.costJackpot, rm.jackpotUser) } log.Debug(rm.Log("本局是否中jackpot奖:%v, 玩家一起分走jackpot:%v, 当前jackpot值:%v", rm.costJackpot > 0, rm.costJackpot, rm.jackpotMgr.GetJackpot())) return jackpotArea, rm.jackpotUser } // 计算 所有玩家的下注区域中奖得分 func (rm *ColorRoom) CalculateAllUserScore() { // 赢钱会清空jackpot池 jpArea, userJackPot := rm.CalculateJackpotScore() var jackpotUserName []string rm.Traverse(func(user *model.User) bool { msg := new(pb.ColorPinoyLiveUserSettleMsg) msg.WinAreaOdd = rm.PokerMsg.WinBetArea msg.UserBets = rm.CopyArr(user.TotalBets) msg.UserRealWins = make([]int64, config.BET_TYPE_NUM) msg.UserWins = make([]int64, config.BET_TYPE_NUM) if user.TotalBet > 0 { // 算分 jpScore := userJackPot[user.UserID] rm.CalculateScore(user, jpArea, jpScore, msg) if jpScore > 0 { jackpotUserName = append(jackpotUserName, user.UserInetr.GetNike()) rm.BroadHitJackpot(user, jpScore) } } // 统计玩家信息 if msg.TotalWin > user.TotalBet { user.UserCount(true, msg.TotalWin) } else { user.UserCount(false, 0) } user.Balance += msg.TotalWin msg.UserScore = user.Balance user.SettleMsg = msg return true }) rm.Traverse(func(user *model.User) bool { user.SettleMsg.JackpotUserName = jackpotUserName return true }) } // 计算 下注区域中奖得分 func (rm *ColorRoom) CalculateScore(user *model.User, jpArea pb.ColorPinoyLiveBetTypeJP, jpScore int64, msg *pb.ColorPinoyLiveUserSettleMsg) { for _, winArea := range msg.WinAreaOdd { if msg.UserBets[winArea.BetArea] <= 0 { continue } if jpArea != winArea.BetArea { odds := winArea.Odd // 奖金计算公式 Payouts =( Odds * Bet ) * ( 1 + Bonus) + Bet odds 是倍率 Bonus是猜中幸运骰子 的加成 // 本区域赢未扣税 倍率是百分值所以计算时要除以100 win2 := (odds * msg.UserBets[winArea.BetArea]) / 100 // 本区域赢扣税 Gold := win2 * (100 - rm.RoomCfg.Rate) / 100 // 算税() msg.Tax += win2 - Gold // 加回投注本金 Gold += msg.UserBets[winArea.BetArea] msg.TotalWin += Gold // 统计赢区的下注总额 msg.TotalWinBaseBet += msg.UserBets[winArea.BetArea] msg.UserWins[winArea.BetArea] += win2 msg.UserRealWins[winArea.BetArea] += Gold log.Debug(rm.LogEx(user, "算分 odds:%v 投注区:%v win2:%v Gold:%v", odds, winArea.BetArea, win2, Gold)) } else { win2 := jpScore // 本区域赢扣税 Gold := jpScore * (100 - rm.RoomCfg.Rate) / 100 // 算税() msg.Tax += win2 - Gold // 加回投注本金 Gold += msg.UserBets[winArea.BetArea] msg.TotalWin += Gold // 统计赢区的下注总额 msg.TotalWinBaseBet += msg.UserBets[winArea.BetArea] msg.UserWins[winArea.BetArea] += win2 msg.UserRealWins[winArea.BetArea] += Gold msg.JackpotWin = Gold log.Debug(rm.LogEx(user, "算分 jackpot 投注区:%v win2:%v Gold:%v", winArea.BetArea, win2, Gold)) } } } type areaWin struct { areaId int64 win int64 } // 更新大赢家 func (rm *ColorRoom) updateBigWinner(allWinner []*pb.ColorPinoyLiveBigWinner) { // log.Debug(fmt.Sprintf("allWinner:%+v", allWinner)) rm.BigWinner = rm.BigWinner[:0] sort.Slice(allWinner, func(i, j int) bool { return allWinner[i].WinChips > allWinner[j].WinChips }) if len(allWinner) > BigWinnerCount { allWinner = allWinner[:BigWinnerCount] } for _, winner := range allWinner { var areaWins []*areaWin for areaId, win := range winner.AreaId { if win > 0 { areaWins = append(areaWins, &areaWin{areaId: int64(areaId), win: win}) } } sort.Slice(areaWins, func(i, j int) bool { return areaWins[i].win > areaWins[j].win }) if len(areaWins) > BigWinnerCount { areaWins = areaWins[:BigWinnerCount] } winner.AreaId = make([]int64, 0, BigWinnerCount) for _, area := range areaWins { winner.AreaId = append(winner.AreaId, area.areaId) } } rm.BigWinner = allWinner } // 和平台做结算 func (rm *ColorRoom) SetUserSettleMsg() { var allWinner []*pb.ColorPinoyLiveBigWinner rm.CalculateAllUserScore() wg := new(sync.WaitGroup) rm.Traverse(func(uu *model.User) bool { wg.Add(1) go func(u *model.User) { defer wg.Done() if u.TotalBet > 0 { // 和平台做赢钱结算 // if msg.TotalWin > 0 { _, err := rm.TransInoutGameEnd(u, 0, u.SettleMsg.TotalWin, u.SettleMsg.Tax) if err != nil { log.Error(rm.Log(err.Error())) // model.SendBetFailMessage(model.BalanceError, u) } // } } }(uu) return true }) wg.Wait() // 异步加钱完成后再执行后续的结算操作 rm.Traverse(func(u *model.User) bool { msg := u.SettleMsg if msg == nil || msg.TotalWin < 1 { return true } winner := &pb.ColorPinoyLiveBigWinner{ NickName: u.UserInetr.GetNike(), Avatar: u.UserInetr.GetHead(), WinChips: msg.TotalWin, AreaId: msg.UserRealWins, } allWinner = append(allWinner, winner) log.Debug(rm.LogEx(u, "赢钱:%v", winner.WinChips)) return true }) rm.updateBigWinner(allWinner) rm.SetGameTrend() rm.sendSettleMsg2Client() // log.Debug(time.Now().Unix() - rm.endAt) rm.Table.EndGame() rm.NotifyLiveDelayUpdate() // 通知延迟更新服务器配置,切换维护状态 rm.TimerJob, _ = rm.Table.AddTimer(time.Duration(rm.RoomCfg.TimeConf.Endpay), func() { rm.Rank() }) } func (rm *ColorRoom) CountUser(u *model.User) { u.Icon = 0 // rm.OnlineUserList = append(rm.OnlineUserList, u) } func (rm *ColorRoom) ResetData(all bool) { rm.TotalBets = [config.BET_TYPE_NUM]int64{} rm.TotalBet = 0 rm.LuckyDice = 0 rm.NormalDices = make([]byte, 3) rm.StartDices = make([]byte, 3) rm.ResultImgs = make([]string, 0) rm.InitBigOddsBetAreas() if all { rm.LiveMgr.Reset() rm.Traverse(func(u *model.User) bool { u.ResetUserData() u.RetrunGold = 0 return true }) } } func (rm *ColorRoom) GetPairDice(count int, startIndex int, endIndex int) []byte { if gconfig.GConfig.IsProd() { return nil } // PokerCard 表示一张扑克牌 type Dice struct { Color int // 花色 Point int // 点数 } type PairDice struct { Cards []Dice } key := fmt.Sprintf("preset-cards:0:color:%d", rm.Table.GetId()) log.Debug(rm.Log("获取配牌配置 key:", key)) str := redisf.RSC.GetPairCard(key) if str == "" { return nil } pairCard := new(PairDice) err := json.Unmarshal([]byte(str), pairCard) if err != nil { log.Debug(rm.Log("getPairCard:%v", err)) return nil } if len(pairCard.Cards) < 2 { log.Error(rm.Log("getPairCard ", pairCard)) return nil } log.Debug(rm.Log("getPairCard pairCard:%v", pairCard)) var res []byte for index, card := range pairCard.Cards { if index >= startIndex && index < endIndex { res = append(res, byte(card.Point)) if (len(res)) == count { break } } } return res } func (rm *ColorRoom) GetGameTrend() (luckyRates []int32) { // var mapLuckyRate = map[pb.ColorPinoyLiveDiceColorType]int32{} // for _, trend := range rm.GameTrend.ListTrendGroup { // mapLuckyRate[trend.LuckyDice]++ // } // for i := pb.ColorPinoyLiveDiceColorType_ColorPinoyLiveType_YELLOW; i <= pb.ColorPinoyLiveDiceColorType_ColorPinoyLiveType_GREEN; i++ { // luckyRates = append(luckyRates, 0) // } // if len(mapLuckyRate) > 0 { // for k, v := range mapLuckyRate { // // log.Debug(fmt.Sprintf("颜色:%v 数量:%v 总数量:%v 配置:%v", k, v, len(rm.GameTrend.ListTrendGroup), config.WinTrendNum)) // if int(k-1) < len(luckyRates) { // luckyRates[k-1] = v * 10000 / int32(len(rm.GameTrend.ListTrendGroup)) // } // } // } return luckyRates } func (rm *ColorRoom) SetGameTrend() { trendGroup := new(pb.ColorPinoyLiveTrendGroup) trendGroup.LuckyDice = pb.ColorPinoyLiveDiceColorType(model.GetColor(rm.LuckyDice)) for _, dice := range rm.NormalDices { trendGroup.ThreeDice = append(trendGroup.ThreeDice, pb.ColorPinoyLiveDiceColorType(model.GetColor(dice))) } rm.GameTrend.ListTrendGroup = append(rm.GameTrend.ListTrendGroup, trendGroup) // log.Debug("走势图 rm.GameTrend.ListTrendGroup: ", rm.GameTrend.ListTrendGroup) winlen := len(rm.GameTrend.ListTrendGroup) if winlen > config.WinTrendNum { rm.GameTrend.ListTrendGroup = rm.GameTrend.ListTrendGroup[(winlen - config.WinTrendNum):] } if rm.ServerStatus == define.GameStatusNoraml { str, err := json.Marshal(rm.GameTrend.ListTrendGroup) if err != nil { return } redisf.RSC.SetColorGameTrend(rm.TrendRedisKey, str) // log.Debug("SetGameTrend 保存走势图到redis :", str) } } // 获取投注区域的颜色 func getColorByBetArea(area pb.ColorPinoyLiveBetTypeJP) pb.ColorPinoyLiveDiceColorType { switch area { case pb.ColorPinoyLiveBetTypeJP_CLJ_Yellow, pb.ColorPinoyLiveBetTypeJP_CLJ_Double_Yellow, pb.ColorPinoyLiveBetTypeJP_CLJ_Three_Yellow: return pb.ColorPinoyLiveDiceColorType_ColorPinoyLiveType_YELLOW case pb.ColorPinoyLiveBetTypeJP_CLJ_White, pb.ColorPinoyLiveBetTypeJP_CLJ_Double_White, pb.ColorPinoyLiveBetTypeJP_CLJ_Three_White: return pb.ColorPinoyLiveDiceColorType_ColorPinoyLiveType_WHITE case pb.ColorPinoyLiveBetTypeJP_CLJ_Pink, pb.ColorPinoyLiveBetTypeJP_CLJ_Double_Pink, pb.ColorPinoyLiveBetTypeJP_CLJ_Three_Pink: return pb.ColorPinoyLiveDiceColorType_ColorPinoyLiveType_PINK case pb.ColorPinoyLiveBetTypeJP_CLJ_Blue, pb.ColorPinoyLiveBetTypeJP_CLJ_Double_Blue, pb.ColorPinoyLiveBetTypeJP_CLJ_Three_Blue: return pb.ColorPinoyLiveDiceColorType_ColorPinoyLiveType_BLUE case pb.ColorPinoyLiveBetTypeJP_CLJ_Red, pb.ColorPinoyLiveBetTypeJP_CLJ_Double_Red, pb.ColorPinoyLiveBetTypeJP_CLJ_Three_Red: return pb.ColorPinoyLiveDiceColorType_ColorPinoyLiveType_RED case pb.ColorPinoyLiveBetTypeJP_CLJ_Green, pb.ColorPinoyLiveBetTypeJP_CLJ_Double_Green, pb.ColorPinoyLiveBetTypeJP_CLJ_Three_Green: return pb.ColorPinoyLiveDiceColorType_ColorPinoyLiveType_GREEN } return pb.ColorPinoyLiveDiceColorType_ColorPinoyLiveType_Void } func getColorCount(result []byte, color pb.ColorPinoyLiveDiceColorType) int { count := 0 for _, c := range result { if model.GetColor(c) == int32(color) { count++ } } return count } func stringDices(result []byte) string { s := "" for _, c := range result { s += fmt.Sprintf("%v", pb.ColorPinoyLiveDiceColorType(model.GetColor(c))) } return s } // 检查投掷结果是否匹配某个下注区域 func isWinningArea(result []byte, area pb.ColorPinoyLiveBetTypeJP) (win bool, bigPos pb.ColorPinoyLiveBigBetAreaPos, colorCount int) { color := getColorByBetArea(area) colorCount = getColorCount(result, color) if colorCount == 0 { return false, 0, colorCount } // 单色投注区,查看开出几个该颜色 if area < pb.ColorPinoyLiveBetTypeJP_CLJ_Double_Yellow { if colorCount == 1 { return true, pb.ColorPinoyLiveBigBetAreaPos_BBA_Single_0, colorCount } else if colorCount == 2 { return true, pb.ColorPinoyLiveBigBetAreaPos_BBA_Single_1, colorCount } else { return true, pb.ColorPinoyLiveBigBetAreaPos_BBA_Single_2, colorCount } } else if area < pb.ColorPinoyLiveBetTypeJP_CLJ_Three_Yellow { if colorCount > 1 { return true, pb.ColorPinoyLiveBigBetAreaPos_BBA_Double, colorCount } } else { if colorCount > 2 { return true, pb.ColorPinoyLiveBigBetAreaPos_BBA_Three, colorCount } } return false, 0, colorCount } // 计算开奖结果 func (rm *ColorRoom) CompareDiceResult() { var wins []*pb.ColorPinoyLiveBetAreaOdd // // 存储中奖区域 result := rm.NormalDices log.Debug(rm.Log("开奖结果:%v", stringDices(result))) rm.afterBetAreaOdds = rm.afterBetAreaOdds[0:0] // 检查所有下注区域是否中奖 for pos, area := range rm.betEndBetAreasOdds { betAreaOdd := &pb.ColorPinoyLiveBetAreaOdd{ BetArea: area.BetType, Odd: area.Odd[0], ViewOdd: 0, IsBigOdd: area.IsBigOdd, BigSingleColorOddPos: area.BigSingleColorOddPos, IsWin: false, IsJackpot: area.IsJackpot, } if pos/6 == 0 { // 第一行投注区域,赔率是多个赔率中的一个 betAreaOdd.Odd = area.Odd[area.BigSingleColorOddPos] } else { betAreaOdd.Odd = area.Odd[0] } betAreaOdd.ViewOdd = betAreaOdd.Odd // 更新实际赔率 isWin, winOddPos, colorCount := isWinningArea(result, area.BetType) betAreaOdd.IsWin = isWin if betAreaOdd.IsWin { if betAreaOdd.IsJackpot && colorCount == 3 { betAreaOdd.Odd = 0 } else { if winOddPos == pb.ColorPinoyLiveBigBetAreaPos_BBA_Single_0 { betAreaOdd.Odd = area.Odd[0] } else if winOddPos == pb.ColorPinoyLiveBigBetAreaPos_BBA_Single_1 { betAreaOdd.Odd = area.Odd[1] } else if winOddPos == pb.ColorPinoyLiveBigBetAreaPos_BBA_Single_2 { betAreaOdd.Odd = area.Odd[2] } // 爆奖需要修正 if betAreaOdd.IsBigOdd && winOddPos > betAreaOdd.BigSingleColorOddPos { betAreaOdd.Odd = area.Odd[area.BigSingleColorOddPos] } } wins = append(wins, betAreaOdd) } // 修正牌局记录倍率 tmpAreaOdd := &pb.ColorPinoyLiveBetAreaOdd{ BetArea: betAreaOdd.BetArea, Odd: betAreaOdd.Odd, ViewOdd: betAreaOdd.ViewOdd, IsBigOdd: betAreaOdd.IsBigOdd, BigSingleColorOddPos: betAreaOdd.BigSingleColorOddPos, IsWin: betAreaOdd.IsWin, IsJackpot: betAreaOdd.IsJackpot, } if !tmpAreaOdd.IsWin && pos/6 == 0 && (betAreaOdd.BigSingleColorOddPos == pb.ColorPinoyLiveBigBetAreaPos_BBA_Single_1 || betAreaOdd.BigSingleColorOddPos == pb.ColorPinoyLiveBigBetAreaPos_BBA_Single_2) { tmpAreaOdd.Odd = area.Odd[0] if !tmpAreaOdd.IsBigOdd { tmpAreaOdd.ViewOdd = tmpAreaOdd.Odd } } if !tmpAreaOdd.IsWin && pos/6 == 1 && tmpAreaOdd.IsBigOdd { tmpAreaOdd.Odd = rm.RoomCfg.ColorPinoyLiveConfig.WinDoubleColorMul[0].Mul } if !tmpAreaOdd.IsWin && pos/6 == 2 && tmpAreaOdd.IsBigOdd { tmpAreaOdd.Odd = rm.RoomCfg.ColorPinoyLiveConfig.WinThreeColorMul[0].Mul } rm.afterBetAreaOdds = append(rm.afterBetAreaOdds, tmpAreaOdd) log.Debug(rm.Log("开奖,区域:%v 是否中奖:%v 实际赔率:%v 显示赔率:%v 是否爆奖:%v 爆奖位置:%v", betAreaOdd.BetArea, betAreaOdd.IsWin, betAreaOdd.Odd, betAreaOdd.ViewOdd, betAreaOdd.IsBigOdd, betAreaOdd.BigSingleColorOddPos)) } rm.PokerMsg.WinBetArea = wins } // 检查用户是否被踢掉 func (rm *ColorRoom) checkUserBet() { kickMsg, isKick := redisf.RSC.IsGameMaintenance(gconfig.GConfig.GRoomConfig.ChannelId, gconfig.GConfig.GServConfig.GameId) rm.Traverse(func(u *model.User) bool { u.NoBetCount++ if isKick { msg := new(pb.ColorPinoyLiveKickOutUserMsg) // msg.KickOutReason = fmt.Sprintf("you did not bet %d games", rm.RoomCfg.LongHuConfig.NoBetCountMax) msg.Reason = int32(pb.ColorPinoyLiveLeaveReason_ColorPinoyLiveLeaveReason_Maintaince) msg.KickOutReason = kickMsg u.SendMsg(int32(pb.ColorPinoyLiveSendToClientMessageType_ColorPinoyLiveNoticeKickOutUser), msg) u.NoBetCount = 0 rm.KickOutUser(u) return true } if rm.ServerStatus != define.GameStatusNoraml || u.UserInetr.NeedKickout() { msg := new(pb.ColorPinoyLiveKickOutUserMsg) // msg.KickOutReason = fmt.Sprintf("game server maintenance ") msg.Reason = int32(pb.ColorPinoyLiveLeaveReason_ColorPinoyLiveLeaveReason_PLAYER_QUIT_ROOM) u.SendMsg(int32(pb.ColorPinoyLiveSendToClientMessageType_ColorPinoyLiveNoticeKickOutUser), msg) rm.KickOutUser(u) return true } if redisf.RSC.IsUserLiveBan(u.UserInetr.GetOpToken(), int64(gconfig.GConfig.GRoomConfig.GameId), u.UserID) { msg := new(pb.ColorPinoyLiveKickOutUserMsg) msg.Reason = int32(pb.ColorPinoyLiveLeaveReason_ColorPinoyLiveLeaveReason_Ban) u.SendMsg(int32(pb.ColorPinoyLiveSendToClientMessageType_ColorPinoyLiveNoticeKickOutUser), msg) rm.KickOutUser(u) return true } // if u.NoBetCount >= (rm.RoomCfg.ColorPinoyLiveConfig.NoBetCountMax + 1) { // msg := new(pb.ColorPinoyLiveKickOutUserMsg) // // msg.KickOutReason = fmt.Sprintf("you did not bet %d games", rm.RoomCfg.LongHuConfig.NoBetCountMax) // msg.Reason = 1 // u.SendMsg(int32(pb.ColorPinoyLiveSendToClientMessageType_ColorPinoyLiveNoticeKickOutUser), msg) // // 踢掉用户 // u.NoBetCount = 0 // rm.KickOutUser(u) // return true // } return true }) } // 初始走势图 func (rm *ColorRoom) InitWinTrend() { rm.TrendRedisKey = fmt.Sprintf("pb.:%d:%d:trend", gconfig.GConfig.GDataConfig.VersionMode, rm.RoomCfg.Level) if rm.ServerStatus == define.GameStatusNoraml { winTrend := redisf.RSC.GetColorGameTrend(rm.TrendRedisKey) if winTrend != "" { rm.GameTrend = new(pb.ColorPinoyLiveTrend) rm.GameTrend.ListTrendGroup = []*pb.ColorPinoyLiveTrendGroup{} err := json.Unmarshal([]byte(winTrend), &rm.GameTrend.ListTrendGroup) winlen := len(rm.GameTrend.ListTrendGroup) // log.Debug("初始化走势图1:", len(rm.GameTrend.ListTrendGroup)) if winlen > config.WinTrendNum { rm.GameTrend.ListTrendGroup = rm.GameTrend.ListTrendGroup[(winlen - config.WinTrendNum):] // log.Debug("初始化走势图2:", len(rm.GameTrend.ListTrendGroup)) } // log.Debug("初始化走势图:", rm.GameTrend.ListTrendGroup) if err != nil { log.Error(rm.Log("初始化走势图:%v", err)) } } } } func (rm *ColorRoom) DeleteExitUserFromOnlineUserListSlice(user *model.User) { rm.MutexUserList.Lock() defer rm.MutexUserList.Unlock() for k, v := range rm.OnlineUserList { if user == v { rm.OnlineUserList = append(rm.OnlineUserList[:k], rm.OnlineUserList[k+1:]...) break } } } func (rm *ColorRoom) SelectUserListBalanceTopSitDownChair() { rm.MutexUserList.Lock() rm.SceneInfo.ClearSceneChairId() rm.SceneInfo.Init() index := len(rm.OnlineUserList) if index >= config.SEAT_NUM { index = config.SEAT_NUM } cou := model.Usercount{} cou = rm.OnlineUserList sort.Sort(cou) for i := 0; i < index; i++ { u := rm.OnlineUserList[i] ChairId := i + 1 if rm.SceneInfo.SitScene(u, ChairId) { u.SceneChairId = ChairId } } rm.MutexUserList.Unlock() } func (rm *ColorRoom) CopyArr(arr [config.BET_TYPE_NUM]int64) []int64 { slice := make([]int64, len(arr)) copy(slice, arr[:]) return slice } func (rm *ColorRoom) GameDiscard() { rm.TimerJob.Cancel() rm.GameUserDiscard() // rm.TimerJob, _ = rm.Table.AddTimer(1000, func() { // rm.LiveCnt(rm.Ready, false, true, []bool{true}...) // }) rm.ResetData(false) rm.Traverse(func(user *model.User) bool { if rm.LiveMgr.MaintenanceStatus == 1 { rm.sendUserMainte(user, int32(pb.ColorPinoyLiveSendToClientMessageType_ColorPinoyLiveNoticeGameMainte)) } if rm.LiveMgr.DiscardStatus == 1 { rm.sendUserMainte(user, int32(pb.ColorPinoyLiveSendToClientMessageType_ColorPinoyLiveNoticeDiscard)) } return true }) rm.Ready() } func (rm *ColorRoom) sendUserMainte(user *model.User, cmd int32) { userinfo := new(pb.ColorPinoyLiveUserInfo) userinfo.NikeName = user.UserInetr.GetNike() userinfo.UserGlod = user.Balance userinfo.Head = user.UserInetr.GetHead() userinfo.UserID = user.UserID _ = user.UserInetr.SendMsg(cmd, &pb.ColorPinoyLiveMainteNtf{ UserInfo: userinfo, MaintMsg: rm.LiveMgr.MainteMsg, ReturnGold: user.RetrunGold, }) } func (rm *ColorRoom) GameUserDiscard() { rm.MutexStatus.RLock() defer rm.MutexStatus.RUnlock() log.Debug(rm.Log("流局,状态:%v,分出去的jackpot:%v", rm.GetGameStatus(), rm.costJackpot)) // 回退 if rm.GetGameStatus() >= pb.ColorPinoyLiveGameStatus_ColorPinoyLiveEndBetMovie && rm.GetGameStatus() < pb.ColorPinoyLiveGameStatus_ColorPinoyLiveSettleStatus { rm.jackpotMgr.RollBackJpXAndJpY(0, 0, rm.jackpotX, rm.jackpotY) rm.kafkaBackJackpot(0, rm.jackpotX, rm.jackpotY, rm.jackpotUser) } else if rm.GetGameStatus() >= pb.ColorPinoyLiveGameStatus_ColorPinoyLiveSettleStatus { rm.jackpotMgr.RollBackJpXAndJpY(rm.costJackpot, rm.jackpotFunding, rm.jackpotX, rm.jackpotY) rm.kafkaBackJackpot(rm.costJackpot, rm.jackpotX, rm.jackpotY, rm.jackpotUser) } var betCount int64 GameTotalBets := make([]int64, config.BET_TYPE_NUM) var PlayerData []*pb.ColorPinoyLivePlayerData copyBetAreaMul := rm.copyBetAreaOdds() wg := new(sync.WaitGroup) playerDataMu := new(sync.Mutex) rm.Traverse(func(v *model.User) bool { wg.Add(1) monitor.GoSafe(func(u *model.User) { defer wg.Done() u.RetrunGold = u.TotalBet msg := new(pb.ColorPinoyLiveUserSettleMsg) msg.UserBets = rm.CopyArr(u.TotalBets) msg.UserRealWins = make([]int64, config.BET_TYPE_NUM) msg.UserWins = make([]int64, config.BET_TYPE_NUM) msg.WinAreaOdd = rm.PokerMsg.WinBetArea msg.UserScore = u.Balance u.SettleMsg = msg // 写入数据库统计信息 if u.TotalBet > 0 { for i, bet := range u.TotalBets { betCount += bet GameTotalBets[i] += bet } // 玩家下注区域统计 u.SettleMsg.UserBetsCount = make([]int64, config.BET_TYPE_NUM) for i, count := range u.TotalBetsCount { u.SettleMsg.UserBetsCount[i] = count } if rm.GetGameStatus() < pb.ColorPinoyLiveGameStatus_ColorPinoyLiveEndBetMovie { u.AddBalance(u.TotalBet) } else if rm.GetGameStatus() < pb.ColorPinoyLiveGameStatus_ColorPinoyLiveSettleStatus { _, err := rm.TransInoutGameEnd(u, u.TransWin, u.TransBet, 0) if err != nil { log.Error(rm.Log("discard error:%v, game_no:%s, uid:%d, TransWin:%d, TransBet:%d", err, rm.Table.GetGameRoundId(), u.UserID, u.TransWin, u.TransBet)) } } else { if rm.LiveMgr.DiscardStatus == 1 { _, err := rm.TransInoutGameEnd(u, u.TransWin, u.TransBet, 0) if err != nil { log.Error(rm.Log("discard error:%v, game_no:%s, uid:%d, TransWin:%d, TransBet:%d", err, rm.Table.GetGameRoundId(), u.UserID, u.TransWin, u.TransBet)) } } else { return } } func() { playerDataMu.Lock() defer playerDataMu.Unlock() PlayerData = append(PlayerData, &pb.ColorPinoyLivePlayerData{ Uid: u.UserID, TotalBets: u.SettleMsg.UserBets, // 玩家各个区域的总下注额 TotalBet: u.TotalBet, Profit: u.SettleMsg.TotalWin, Tax: u.SettleMsg.Tax, Balance: u.Balance, PreBalance: u.PreBalance, UserWins: u.SettleMsg.UserWins, // 玩家赢取的下注区域总下注额 UserRealWins: u.SettleMsg.UserRealWins, // 玩家赢取的下注区域总下注额 扣税后 AreaOdds: copyBetAreaMul, // 投注区域倍率 StartTime: u.StartAt, TransBet: u.TransBet, TransWin: u.TransWin, DevMode: u.UserInetr.GetDevMode(), UserBetsCount: u.SettleMsg.UserBetsCount, IsDiscard: 1, }) }() } u.ResetUserData() }, v) return true }) wg.Wait() // 发布事件 if PlayerData == nil || len(PlayerData) == 0 { return } // 开奖结果 gameDetail := &pb.ColorPinoyLiveDetail{DealerName: rm.dealerName, ResultImg: rm.ResultImgs} gameDetail.BetAreaMul = copyBetAreaMul gameRecordData := &pb.ColorPinoyLiveEnd{ GameNo: fmt.Sprintf("%d|%d|%d", gconfig.GConfig.GRoomConfig.GameId, rm.Table.GetId(), time.Now().UnixMilli()), StartTime: rm.startAt, EndTime: rm.endAt, Level: gconfig.GConfig.GRoomConfig.Level, BaseBet: rm.RoomCfg.BaseBet, PlayerData: PlayerData, TaxRate: rm.RoomCfg.Rate, TotalBet: betCount, TotalBets: GameTotalBets[:], OpToken: gconfig.GConfig.GServConfig.ChannelId, Detail: gameDetail, IsDiscard: 1, } log.Debug("GameUserDiscard") go func() { err := gconfig.Produce(context.Background(), define.TopicColoLiveGameGameEnd, gameRecordData) if err != nil { log.Error(rm.Log("[%s] fail to Produce TongitsGameEndEvent(%+v), err: %v", gameRecordData.GameNo, gameRecordData, err)) } }() } func (rm *ColorRoom) LoadDealerNames() { ssv := redisf.RSC.DealerNameGet(gconfig.GConfig.GDataConfig.VersionMode, gconfig.GConfig.GRoomConfig.GameId, GameName) var dealerNames []string _ = json.Unmarshal([]byte(ssv), &dealerNames) rm.dealerName = dealerNames log.Debug(rm.Log("dealerNames:%v", rm.dealerName)) } // 发送垫资kafka func (rm *ColorRoom) kafkaJackpotFunding(funding int64) { msg := &events.JackpotEvent{ EventType: events.JackpotEvent_et_funding, GameId: rm.RoomCfg.GameId, Time: time.Now().UnixMilli(), Funding: &events.JackpotEvent_Funding{Funding: funding}, GameNo: rm.Table.GetGameRoundId(), } go func() { err := gconfig.Produce(context.Background(), define.TopicJackpot, msg) if err != nil { log.Error(rm.Log("kafka JackpotEvent_et_funding err: %v", err)) } }() log.Debug(rm.Log("kafka 垫资:%v", funding)) } // 发送赎回及追加kafka func (rm *ColorRoom) kafkaJackpotUserRepaid(jpx, jpy int64) { msg := &events.JackpotEvent{ EventType: events.JackpotEvent_et_user_repaid, GameId: rm.RoomCfg.GameId, Time: time.Now().UnixMilli(), UserRepaid: &events.JackpotEvent_UserRepaid{JackpotX: jpx, JackpotY: jpy}, GameNo: rm.Table.GetGameRoundId(), } go func() { err := gconfig.Produce(context.Background(), define.TopicJackpot, msg) if err != nil { log.Error(rm.Log("kafka JackpotEvent_et_user_repaid err: %v", err)) } }() log.Debug(rm.Log("kafka 赎回jpx:%v 追加jpy:%v", jpx, jpy)) } // 中jackpot分奖 func (rm *ColorRoom) kafkaHitJackpot(jackpot int64, userJp map[int64]int64) { msg := &events.JackpotEvent{ EventType: events.JackpotEvent_et_hit_jackpot, GameId: rm.RoomCfg.GameId, Time: time.Now().UnixMilli(), HitJackpot: &events.JackpotEvent_HitJackpot{SumJackpot: jackpot, UserJackpot: userJp}, GameNo: rm.Table.GetGameRoundId(), } go func() { err := gconfig.Produce(context.Background(), define.TopicJackpot, msg) if err != nil { log.Error(rm.Log("kafka JackpotEvent_HitJackpot err: %v", err)) } }() log.Debug(rm.Log("kafka 中jackpot:%v", jackpot)) } func (rm *ColorRoom) kafkaBackJackpot(jackpot, jpx, jpy int64, userJp map[int64]int64) { userJp2 := make(map[int64]int64) for k, v := range userJp { userJp2[k] = -v } msg := &events.JackpotEvent{ EventType: events.JackpotEvent_et_game_discard, GameId: rm.RoomCfg.GameId, Time: time.Now().UnixMilli(), GameDiscard: &events.JackpotEvent_GameDiscard{ Jackpot: -jackpot, JackpotX: -jpx, JackpotY: -jpy, UserJackpot: userJp2, }, GameNo: rm.Table.GetGameRoundId(), } go func() { err := gconfig.Produce(context.Background(), define.TopicJackpot, msg) if err != nil { log.Error(rm.Log("kafka JackpotEvent_HitJackpot err: %v", err)) } }() log.Debug(rm.Log("kafka 回退jackpot:%v jpx:%v jpy:%v", -jackpot, -jpx, -jpy)) }