package clawdoll import ( "container/list" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "io/ioutil" "math/rand" "mongo.games.com/game/common" rule "mongo.games.com/game/gamerule/clawdoll" "mongo.games.com/game/gamesrv/base" "mongo.games.com/game/model" "mongo.games.com/game/proto" "mongo.games.com/game/protocol/clawdoll" "mongo.games.com/game/protocol/machine" "mongo.games.com/game/protocol/webapi" "mongo.games.com/goserver/core/basic" "mongo.games.com/goserver/core/logger" "mongo.games.com/goserver/core/netlib" "mongo.games.com/goserver/core/task" "mongo.games.com/goserver/core/timer" "mongo.games.com/goserver/srvlib" "net/http" "net/url" "strconv" "time" ) type PlayerData struct { SnId int32 Head int32 //头像框 VIP int32 //VIP帐号 等级 Name string //名字 Sex int32 //性别 IsRob bool Coin int64 gainCoin int64 //本局赢的金币 taxCoin int64 //本局税收 isBilled bool //是否结算 CurIsWin int64 //当局输赢 负数:输 正数:赢 InviterId int32 //邀请人Id BeUnderAgentCode string //隶属经销商(推广人) IsPlayerFirst bool Platform string //平台 Channel string //渠道信息 PackageID string //推广包标识 对应客户端的packagetag flag int } type SceneEx struct { *base.Scene // 场景 logic *rule.Logic // players map[int32]*PlayerEx // 玩家信息 PlayerBackup map[int32]*PlayerData // 本局离场玩家数据备份 seats []*PlayerEx // 本局游戏中的玩家状态数据 waitPlayers *list.List playingSnid int32 // 正在玩的玩家snid RoundId int // 局数,第几局 robotNum int // 参与游戏的机器人数量 logid string machineId int //娃娃机ID machineConn *netlib.Session //娃娃机链接 machineStatus int32 //娃娃机链接状态 0:离线 1:在线 PayCoinCount int //娃娃机总投币次数 payCoinTimeHandle timer.TimerHandle //上分投币托管handle grabTimerHandle timer.TimerHandle //下抓托管handle } // 游戏是否能开始 func (this *SceneEx) CanStart() bool { //人数>=1自动开始 if len(this.players) >= 1 && (this.GetRealPlayerNum() >= 1 || this.IsPreCreateScene() || this.IsHasPlaying()) && this.machineStatus == 1 { return true } return false } // 从房间删除玩家 func (this *SceneEx) delPlayer(p *base.Player) { if p, exist := this.players[p.SnId]; exist { //this.seats[p.GetPos()] = nil delete(this.players, p.SnId) this.RemoveWaitPlayer(p.SnId) } } // 广播玩家进入 func (this *SceneEx) BroadcastPlayerEnter(p *base.Player, reason int) { pack := &clawdoll.SCCLAWDOLLPlayerEnter{} pack.Data = &clawdoll.CLAWDOLLPlayerDigestInfo{} pack.Data.SnId = p.GetSnId() pack.Data.Head = p.Head pack.Data.HeadUrl = p.HeadUrl pack.Data.Name = p.Name if playerEx, ok := p.ExtraData.(*PlayerEx); ok { pack.Data.Stat = playerEx.clawDollState } proto.SetDefaults(pack) this.Broadcast(int(clawdoll.CLAWDOLLPacketID_PACKET_SC_PlayerEnter), pack, p.GetSid()) } // 广播玩家离开 func (this *SceneEx) BroadcastPlayerLeave(p *base.Player, reason int) { scLeavePack := &clawdoll.SCCLAWDOLLPlayerLeave{ SnId: proto.Int32(p.SnId), } proto.SetDefaults(scLeavePack) this.Broadcast(int(clawdoll.CLAWDOLLPacketID_PACKET_SC_PlayerLeave), scLeavePack, p.GetSid()) } // 玩家进入事件 func (this *SceneEx) OnPlayerEnter(p *base.Player, reason int) { this.BroadcastPlayerEnter(p, reason) machineId := this.GetDBGameFree().GetId() % 6080000 machineInfo := this.GetMachineServerInfo(machineId, p.Platform) if machineInfo == nil { return } if this.GetPlayerNum() >= 1 { logger.Logger.Trace("Clawdoll (*SceneEx) OnPlayerEnter, GetPlayerNum = ", this.GetPlayerNum()) // 发送http Get请求 恢复直播间流 operateTask(this, 2, rule.Zego_ResumeRTCStream, p.Platform) } } // 玩家离开事件 func (this *SceneEx) OnPlayerLeave(p *base.Player, reason int) { this.delPlayer(p) this.BroadcastPlayerLeave(p, reason) machineId := this.GetDBGameFree().GetId() % 6080000 machineInfo := this.GetMachineServerInfo(machineId, p.Platform) if machineInfo == nil { return } if len(this.players) <= 0 { logger.Logger.Trace("Clawdoll (*SceneEx) OnPlayerLeave, cur player num = ", len(this.players)) // 发送http Get请求 关闭直播间流 operateTask(this, 2, rule.Zego_ForbidRTCStream, p.Platform) } } func (this *SceneEx) SceneDestroy(force bool) { //销毁房间 this.Scene.Destroy(force) } func (e *SceneEx) playerOpPack(snId int32, opcode int, opRetCode clawdoll.OpResultCode, params []int64) *clawdoll.SCCLAWDOLLOp { pack := &clawdoll.SCCLAWDOLLOp{ SnId: proto.Int32(snId), OpCode: proto.Int32(int32(opcode)), Params: params, OpRetCode: opRetCode, } proto.SetDefaults(pack) return pack } // OnPlayerSCOp 发送玩家操作情况 func (e *SceneEx) OnPlayerSCOp(p *base.Player, opcode int, opRetCode clawdoll.OpResultCode, params []int64) { pack := e.playerOpPack(p.SnId, opcode, opRetCode, params) p.SendToClient(int(clawdoll.CLAWDOLLPacketID_PACKET_SC_PLAYEROP), pack) logger.Logger.Tracef("OnPlayerSCOp %s", pack) } // 房间信息打包 func (this *SceneEx) ClawdollCreateRoomInfoPacket(s *base.Scene, p *base.Player) interface{} { pack := &clawdoll.SCCLAWDOLLRoomInfo{ RoomId: proto.Int(s.GetSceneId()), GameId: proto.Int(s.GetGameId()), RoomMode: proto.Int(s.GetSceneMode()), Params: common.CopySliceInt64ToInt32(s.Params), State: proto.Int(s.GetSceneState().GetState()), TimeOut: proto.Int(s.GetSceneState().GetTimeout(s)), TotalPlayer: proto.Int(len(this.players)), RoundId: proto.Int(this.RoundId), ParamsEx: nil, GameFreeId: 0, } // 玩家信息 for _, playerEx := range this.players { if p.SnId == playerEx.SnId { pd := &clawdoll.CLAWDOLLPlayerData{ SnId: proto.Int32(playerEx.SnId), Name: proto.String(playerEx.Name), Head: proto.Int32(playerEx.Head), Sex: proto.Int32(playerEx.Sex), Coin: proto.Int64(playerEx.Coin), Flag: proto.Int(playerEx.GetFlag()), HeadOutLine: proto.Int32(playerEx.HeadOutLine), VIP: proto.Int32(playerEx.VIP), WinCoin: proto.Int64(playerEx.gainCoin), ClawDollState: proto.Int32(playerEx.clawDollState), } pack.Players = append(pack.Players, pd) } } proto.SetDefaults(pack) if p != nil { p.SyncFlag() } return pack } func NewClawdollSceneData(s *base.Scene) *SceneEx { sceneEx := &SceneEx{ Scene: s, logic: new(rule.Logic), players: make(map[int32]*PlayerEx), PlayerBackup: make(map[int32]*PlayerData), waitPlayers: list.New(), } return sceneEx } func (this *SceneEx) init() bool { this.Clear() this.PayCoinCount = this.LoadPayCoinCount() return true } func (this *SceneEx) ShutDown() bool { this.SavePayCoinCount(false) return true } // 检查上分投币是否合法 func (this *SceneEx) CheckPayCoinOp(p *PlayerEx) bool { if p == nil { return false } if p.SnId == this.playingSnid { return true } return false } // 检查移动是否合法 func (this *SceneEx) CheckMoveOp(p *PlayerEx) bool { if p == nil { return false } if p.SnId == this.playingSnid { return true } return false } // 下抓是否合法 func (this *SceneEx) CheckGrapOp(p *PlayerEx) bool { if p == nil { return false } if p.SnId == this.playingSnid { return true } return false } func (this *SceneEx) Clear() { this.robotNum = 0 this.PlayerBackup = make(map[int32]*PlayerData) this.RoundId = 0 for e := this.waitPlayers.Front(); e != nil; e = e.Next() { if e != nil { p := e.Value.(*PlayerEx) p.Clear(0) } } } // 是否有玩家正在玩 func (this *SceneEx) IsHasPlaying() bool { if this.playingSnid == 0 { return false } return true } // 等待下一个玩家 func (this *SceneEx) ReStartGame() { logger.Logger.Trace("(*SceneEx) ReStartGame, playingSnid = ", this.playingSnid) this.playingSnid = 0 } func (this *SceneEx) BackupPlayer(p *PlayerEx, isBilled bool) { this.PlayerBackup[p.SnId] = &PlayerData{ SnId: p.SnId, gainCoin: p.gainCoin, taxCoin: p.taxCoin, isBilled: isBilled, IsRob: p.IsRob, Coin: p.Coin, Head: p.Head, flag: p.GetFlag(), Platform: p.Platform, Channel: p.Channel, PackageID: p.PackageID, CurIsWin: p.CurIsWin, Name: p.Name, Sex: p.Sex, VIP: p.VIP, InviterId: p.InviterId, IsPlayerFirst: this.IsPlayerFirst(p.Player), BeUnderAgentCode: p.BeUnderAgentCode, } } func (this *SceneEx) GetPlayGrabType(player *PlayerEx) int32 { if player == nil { return rule.ClawWeak } if this.PayCoinCount == 0 { this.PayCoinCount = 1 } machineId := this.GetDBGameFree().GetId() % 6080000 machineInfo := this.GetMachineServerInfo(machineId, player.Platform) if machineInfo == nil { logger.Logger.Errorf("Clawdoll GetPlayGrabType machineId: %v, platform: %v", machineId, player.Platform) return rule.ClawWeak } ProfitRate := common.RandFloat(machineInfo.ProfitMin, machineInfo.ProfitMax) strongRate := (machineInfo.CatchPrice/(machineInfo.BuyPrice*ProfitRate))*float64(this.PayCoinCount) - 1 logger.Logger.Tracef("Clawdoll GetPlayGrabType: SnId: %v, PayCoinCount:%v, ProfitRate:%v, strongRate:%v", player.SnId, this.PayCoinCount, ProfitRate, strongRate) if strongRate <= 0.00000 { return rule.ClawWeak } else if strongRate > 0.00000 && strongRate <= 0.99999 { // 再次随机 newRate := common.RandFromRangeInt64(0, 100) logger.Logger.Tracef("Clawdoll GetPlayGrabType: newRate:%v, SnId: %v, PayCoinCount:%v, ProfitRate:%v, strongRate:%v ", newRate, player.SnId, this.PayCoinCount, ProfitRate, strongRate) if int64(strongRate*100) >= newRate { logger.Logger.Tracef("Clawdoll GetPlayGrabType: SnId: %v gain ClawStrong, PayCoinCount:%v", player.SnId, this.PayCoinCount) return rule.ClawStrong } else { return rule.ClawWeak } } else if strongRate >= 1.00000 { return rule.ClawStrong } return rule.ClawWeak } func (this *SceneEx) AddWaitPlayer(player *PlayerEx) { if player == nil { return } this.waitPlayers.PushBack(player) } func (this *SceneEx) RemoveWaitPlayer(SnId int32) bool { l := this.waitPlayers for e := l.Front(); e != nil; e = e.Next() { if p := e.Value.(*PlayerEx); p.SnId == SnId { this.waitPlayers.Remove(e) return true } } return false } func (this *SceneEx) GetPlayingEx() *PlayerEx { if this.playingSnid == 0 { return nil } return this.players[this.playingSnid] } func (this *SceneEx) SetPlayingState(state int32) { if this.playingSnid == 0 { return } playerEx := this.players[this.playingSnid] if playerEx != nil { oldState := playerEx.GetClawState() if oldState != state { if oldState == rule.ClawDollPlayerStateWait && (state >= rule.ClawDollPlayerStateStart && state <= rule.ClawDollPlayerWaitPayCoin) { ClawdollBroadcastPlayingInfo(this.Scene) } if state == rule.ClawDollPlayerStateWait && (oldState >= rule.ClawDollPlayerStateStart && oldState <= rule.ClawDollPlayerWaitPayCoin) { ClawdollBroadcastPlayingInfo(this.Scene) } } playerEx.SetClawState(state) } } // 时间到 系统开始下抓 func (this *SceneEx) TimeOutPlayGrab() bool { playerEx := this.players[this.playingSnid] if playerEx != nil { grapType := this.GetPlayGrabType(playerEx) if grapType == rule.ClawStrong { this.PayCoinCount = 0 } logger.Logger.Trace("ClawDoll StatePlayGame OnPlayerOp Grab-----SnId:", playerEx.SnId, " grapType: ", grapType) this.OnPlayerSMGrabOp(this.playingSnid, int32(this.machineId), grapType) } return true } // OnPlayerSMGrabOp 下抓 //1-弱力抓 2 -强力抓 3-必出抓 func (this *SceneEx) OnPlayerSMGrabOp(SnId, Id, GrabType int32) { pack := &machine.SMDollMachineGrab{ Snid: proto.Int32(SnId), Id: proto.Int32(Id), TypeId: proto.Int32(GrabType), } this.SendToMachine(int(machine.DollMachinePacketID_PACKET_SMDollMachineGrab), pack) } // OnPlayerSCOp 发送玩家操作情况 1-前 2-后 3-左 4-右 5-投币 func (this *SceneEx) OnPlayerSMPerateOp(SnId, Id, Perate int32) { pack := &machine.SMDollMachineoPerate{ Snid: proto.Int32(SnId), Id: proto.Int32(Id), Perate: proto.Int32(Perate), } this.SendToMachine(int(machine.DollMachinePacketID_PACKET_SMDollMachinePerate), pack) } // 向娃娃机主机发送消息 func (this *SceneEx) SendToMachine(pid int, msg interface{}) { this.machineConn = srvlib.ServerSessionMgrSington.GetSession(1, 10, 1001) if this.machineConn != nil { this.machineConn.Send(pid, msg) } else { logger.Logger.Error("MachineConn is nil !") } } // 下抓托管handle func (this *SceneEx) GrabTimerHandle() { if this.grabTimerHandle != timer.TimerHandle(0) { timer.StopTimer(this.grabTimerHandle) this.grabTimerHandle = timer.TimerHandle(0) } this.grabTimerHandle, _ = timer.StartTimer(timer.TimerActionWrapper(func(h timer.TimerHandle, ud interface{}) bool { logger.Logger.Tracef("ClawDoll SceneEx GrabTimerHandle: TimeOut: %v", rule.ClawDollSceneGrapTimeOut) this.TimeOutPlayGrab() return true }), nil, rule.ClawDollSceneGrapTimeOut, 1) } // 上分投币托管handle func (this *SceneEx) PayCoinTimeHandle() { if this.payCoinTimeHandle != timer.TimerHandle(0) { timer.StopTimer(this.payCoinTimeHandle) this.payCoinTimeHandle = timer.TimerHandle(0) } this.payCoinTimeHandle, _ = timer.StartTimer(timer.TimerActionWrapper(func(h timer.TimerHandle, ud interface{}) bool { logger.Logger.Tracef("ClawDoll SceneEx PayCoinTimeHandle: TimeOut: %v", rule.ClawDollScenePayCoinTimeOut) return true }), nil, rule.ClawDollScenePayCoinTimeOut, 1) } // 得到投币次数 func (this *SceneEx) LoadPayCoinCount() int { machineId := this.GetDBGameFree().GetId() PayCoinCountDBKey := fmt.Sprintf("Clawdoll_PayCoinCount_%v", machineId) data := model.GetStrKVGameData(PayCoinCountDBKey) PayCoinCount, err := strconv.Atoi(data) if err == nil { return PayCoinCount } return 0 } // 保存投币次数 func (this *SceneEx) SavePayCoinCount(asyn bool) { machineId := this.GetDBGameFree().GetId() PayCoinCountDBKey := fmt.Sprintf("Clawdoll_PayCoinCount_%v", machineId) saveBuff := strconv.Itoa(int(this.PayCoinCount)) if asyn { task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { return model.UptStrKVGameData(PayCoinCountDBKey, string(saveBuff)) }), nil, "UptStrKVGameData").Start() } else { model.UptStrKVGameData(PayCoinCountDBKey, string(saveBuff)) } } // actionType : 1 禁止推流 ForbidRTCStream // actionType : 2 恢复推流 ResumeRTCStream func operateTask(sceneEx *SceneEx, times int, actionType int, platform string) { machineId := sceneEx.GetDBGameFree().GetId() % 6080000 machineInfo := sceneEx.GetMachineServerInfo(machineId, platform) if machineInfo == nil { logger.Logger.Errorf("ZegoRTCStreamAction machineId: %v, platform: %v", machineId, platform) return } actionTypeStr := "NoneAction" if actionType == rule.Zego_ForbidRTCStream { actionTypeStr = "ForbidRTCStream" } else { actionTypeStr = "ResumeRTCStream" } logger.Logger.Tracef("ZegoRTCStreamAction: actionTypeStr: %v, machineId: %v, machineInfo: %v", actionTypeStr, machineId, machineInfo) var resp rule.ZegoForbidRTCResp task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} { resp = ZegoRTCStreamAction(actionTypeStr, machineInfo) return nil }), task.CompleteNotifyWrapper(func(i interface{}, t task.Task) { if resp.Error == 0 && resp.Code == 0 && resp.Message == "ok" { } else { logger.Logger.Errorf("ZegoForbidRTCResp Code: %v, Error: %v, Message: %v", resp.Code, resp.Error, resp.Message) if times > 0 { timer.StartTimer(timer.TimerActionWrapper(func(h timer.TimerHandle, ud interface{}) bool { operateTask(sceneEx, times-1, actionType, platform) return true }), nil, time.Second*5, 1) } } }), "ZegoRTCStream_Action").Start() } func ZegoRTCStreamAction(Action string, machineInfo *webapi.MachineInfo) rule.ZegoForbidRTCResp { var zegoForbidRTCResp = rule.ZegoForbidRTCResp{ Error: 1, } timestamp := time.Now().Unix() queryParams := url.Values{} queryParams.Set("StreamId", machineInfo.StreamId) queryParams.Set("Sequence", fmt.Sprintf("%d", timestamp)) // 生成16进制随机字符串(16位) nonce := make([]byte, 8) rand.Read(nonce) hexNonce := hex.EncodeToString(nonce) // 生成签名 signature := generateSignature(uint32(machineInfo.AppId), machineInfo.ServerSecret, hexNonce, timestamp) authParams := url.Values{} authParams.Set("AppId", fmt.Sprintf("%d", uint32(machineInfo.AppId))) //公共参数中的随机数和生成签名的随机数要一致 authParams.Set("SignatureNonce", hexNonce) authParams.Set("SignatureVersion", "2.0") //公共参数中的时间戳和生成签名的时间戳要一致 authParams.Set("Timestamp", fmt.Sprintf("%d", timestamp)) authParams.Set("Signature", signature) //authParams.Set("IsTest", "true") // rtc-api.zego.im 表示使用的产品是云通讯产品,包括了实时音视频(Express Video)、实时音频(Express Audio)、低延迟直播(L3) addr := fmt.Sprintf("https://rtc-api.zego.im/?Action=%s&%s&%s", Action, authParams.Encode(), queryParams.Encode()) logger.Logger.Tracef("ZegoRTCStreamAction Get addr: %+v", addr) rsp, err := http.Get(addr) if err != nil { logger.Logger.Errorf("ZegoRTCStreamAction Get err: %v", err) return zegoForbidRTCResp } defer rsp.Body.Close() body, err := ioutil.ReadAll(rsp.Body) if err != nil { logger.Logger.Errorf("ZegoRTCStreamAction ioutil.ReadAll err: %v", err) return zegoForbidRTCResp } logger.Logger.Tracef("ZegoRTCStreamAction Action: %+v, body: %+v", Action, string(body)) err = json.Unmarshal(body, &zegoForbidRTCResp) if err != nil { zegoForbidRTCResp.Error = 5 logger.Logger.Errorf("ZegoRTCStreamAction %v %v", zegoForbidRTCResp, err.Error()) return zegoForbidRTCResp } zegoForbidRTCResp.Error = 0 return zegoForbidRTCResp } // 生成签名 // Signature=md5(AppId + SignatureNonce + ServerSecret + Timestamp) func generateSignature(appId uint32, serverSecret, signatureNonce string, timeStamp int64) string { data := fmt.Sprintf("%d%s%s%d", appId, signatureNonce, serverSecret, timeStamp) h := md5.New() h.Write([]byte(data)) return hex.EncodeToString(h.Sum(nil)) }