package room import ( "fmt" "samba/pkg/log" "samba/proto" "samba/server/game/baseroom" . "samba/server/game/player" "samba/server/truco/poker" . "samba/server/truco/service" "samba/stub" "samba/util/model" "samba/util/state" "samba/util/util" "time" ) func (r *TrucoRoom) loadRobot(user *Player, seat *TrucoSeat) { if user.IsRobot() { init := false for _, s := range r.Seats { if !s.Empty() && s.Player().IsRobot() && s.Player().Robot != nil { r.SetRobotTemper(seat.No(), s.Player().Robot.Cnf()) init = true break } } if !init { if cnf := r.RandRobotCnf(); cnf != stub.RteUnknown { //cnf = r.RobotCnfByTemper(stub.RteAplomb) r.SetRobotTemper(seat.No(), cnf) } else { log.Error(r.SeatLog(seat, "rand robot error. cnf is nil")) } } } } func (r *TrucoRoom) makePlayerToProto(seat *TrucoSeat, hidePoker bool) *proto.Player { user := seat.Player() protoPlayer := &proto.Player{ UserId: user.UID, Mnick: user.MNick, HeadUrl: user.HeadURL, IconId: user.IconId, AvatarFrame: user.AvatarFrame, Sex: user.Sex, Coin: 0, TakeCoin: user.TakeCoin, ClubTakeCoin: user.TakeClubCoin, Poker: nil, Seat: seat.No(), IsRed: int(r.teamColor(seat.No())), State: util.Tie(seat.FakeLeave(), 0, 1), } for _, pk := range seat.Pokers { protoPlayer.Poker = append(protoPlayer.Poker, util.Tie[int](!hidePoker, pk.ToInt(), 0)) } if seat.OutPoker != nil { protoPlayer.OutPoker = seat.OutPoker.ToInt() } if r.ClubId() != 0 { protoPlayer.ClubCoin = r.GetCoin(user.UID) } else { protoPlayer.Coin = r.GetCoin(user.UID) } return protoPlayer } func (r *TrucoRoom) makeProtoRoomInfo() *proto.NtfRoomInfo { protoRoom := &proto.NtfRoomInfo{ RoomId: r.Id(), RoomType: r.Type(), ClubId: r.ClubId(), Blind: r.RoomCnf.Blind, CuttingPoker: 0, Point: r.point, RedScore: r.points[TcRed], GreenScore: r.points[TcGreen], RoundSettle: nil, State: int(r.gameStep), GameCount: r.smallGameCount, Star: r.star, } if r.pokers.GhostPoker() != nil { protoRoom.CuttingPoker = r.pokers.GhostPoker().ToInt() } if r.gameStep != GsGameSettle && r.gameStep != GsWaitGame { for _, rs := range r.light { protoRoom.RoundSettle = append(protoRoom.RoundSettle, int(rs)) } } return protoRoom } // 重进入 func (r *TrucoRoom) checkEnterRoom(user *Player) (code proto.ErrorCode, seat *TrucoSeat, isReentry bool) { if r.ClubId() > 0 { if userClubInfo, _ := model.NewUserClubInfoOp().Load(user.UID, r.ClubId()); userClubInfo == nil { code = proto.NotInClub return } } code = proto.RoomFull for _, st := range r.Seats { if !st.Empty() && st.Player().UID == user.UID { seat = st isReentry = true code = proto.Ok st.SetFakeLeave(false) break } } if !isReentry { for _, st := range r.Seats { if st.Empty() { seat = st code = proto.Ok st.SetPlayer(user) st.SetFakeLeave(false) break } } } return code, seat, isReentry } // 重进入房间 func (r *TrucoRoom) onReentryRoom(msg map[string]interface{}) { _, _, uid, _ := ParseMsg(msg) seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } r.OnEnterRoom(seat.Player()) } // 加入房间 func (r *TrucoRoom) OnEnterRoom(user *Player) { code, seat, isReentry := r.checkEnterRoom(user) if code != proto.Ok { r.SendMsg(user, proto.RspEnterRoomId, &proto.RspEnterRoom{Code: code}, false) return } if !isReentry { if user.IsRobot() { r.loadRobot(user, seat) } r.addResource(seat.Player(), r.RoomCnf.MinCoin, model.ResTakeCoins, model.ReasonEnter) log.Info(r.SeatLog(seat, "进入房间,携带金币:%v", r.GetTakeCoin(seat.Player()))) model.SetUserPlayingRoom(seat.Player().UID, r.Id(), true) r.TriggerClubMemberPlaying(seat.Player().UID, true) } else { log.Info(r.SeatLog(seat, "重连进入房间")) } // 向玩家发送消息 rsp := &proto.RspEnterRoom{ Code: proto.Ok, RoomId: r.Id(), RoomType: r.Type(), PlayType: r.PlayType(), ClubId: r.ClubId(), GameInfo: r.MakeClubPlayGameInfo(), IsReentry: util.Tie(isReentry, 1, 0), } r.SendMsg(user, proto.RspEnterRoomId, rsp, false) r.notifyRoomInfo(user) r.notifyPlayerInfo(user, isReentry) // 发送游戏开始消息 if !isReentry && r.SeatPlayerNum() == r.RoomCnf.MinPlayers { ntf := &proto.NtfGameStart{Time: time.Now().Unix()} r.Broadcast(proto.NtfStartGameId, ntf, false) r.SetStatus(state.RsReadyStart) r.gameStep = GsReadyGame r.NewTimer(baseroom.TtGameReadyStart, time.Duration(stub.GGlobal.TrucoGameStartTime)*time.Second) } if isReentry { rMsg := util.MakeMessage(proto.ReqReconnectId, &proto.ReqReconnect{RoomId: r.Id()}, user.UID, r.Id()) r.OnMessage(proto.ReqReconnectId, rMsg) } r.UpdateClubWaitingRoom() } func (r *TrucoRoom) leaveRoom(seat *TrucoSeat) { //log.Debug(r.Log("games tep:%v room status:%v", GsWaitGame, state.RsWait)) if r.gameStep == GsWaitGame || r.Status() == state.RsWait { r.addResource(seat.Player(), -r.GetTakeCoin(seat.Player()), model.ResTakeCoins, model.ReasonLeave) log.Info(r.SeatLog(seat, "归还携带金币:%v", -r.GetTakeCoin(seat.Player()))) model.SetUserPlayingRoom(seat.Player().UID, r.Id(), false) // 回收机器人 if seat.Player().IsRobot() { log.Debug(r.SeatLog(seat, "清理并归还机器人")) seat.Player().CleanRobot() seat.Player().Robot = nil if r.ClubId() > 0 { ClubRobotMgr.Push(seat.Player(), r.ClubId()) } else { RobotMgr.Push(seat.Player()) } } else { playingNum, err := model.DelRoomTypePlayerNum(r.RoomCnf, seat.Player().UID) if err == nil && r.RoomCnf.Mode != int(stub.RmClub) { // 金币场 广播虚假在玩人数 BroadcastMsg(proto.NtfPlayingNumId, &proto.NtfPlayingNum{RoomType: r.RoomCnf.Id, PlayingNum: model.CalculateFakeCount(playingNum)}) } } r.TriggerClubMemberPlaying(seat.Player().UID, false) seat.SetPlayer(nil) r.UpdateClubPlayingRoomInfo() r.UpdateClubWaitingRoom() } else { seat.SetFakeLeave(true) } } // 离开房间 func (r *TrucoRoom) onLeaveRoom(msg map[string]interface{}) { _, _, uid, _ := ParseMsg(msg) seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } log.Debug(r.SeatLog(seat, "玩家申请离开房间, room status:%v", r.gameStep)) // 向玩家发送消息 rsp := &proto.NtfLeaveRoom{ CurrentSeat: seat.No(), RoomId: r.Id(), } r.SendMsg(seat.Player(), proto.RspLeaveRoomId, &proto.RspLeaveRoom{Code: proto.Ok, RoomId: r.Id(), CurrentSeat: seat.No()}, false) if r.Status() == state.RsWait { r.Broadcast(proto.NtfLeaveRoomId, rsp, false, seat.Player()) } else { seat.SetFakeLeave(true) r.notifyFakeLeave(seat) } r.leaveRoom(seat) } func (r *TrucoRoom) checkOutPoker(seat *TrucoSeat, req *proto.ReqPlayerOutPoker) (proto.ErrorCode, *poker.Poker) { if !seat.CanAct() { log.Error(r.SeatLog(seat, "不能行动")) return proto.BadAction, nil } if r.gameStep >= GsGameSettle { log.Error(r.SeatLog(seat, "game is over")) return proto.BadAction, nil } if seat.No() != r.current { log.Error(r.SeatLog(seat, "is not current:%v", r.current)) return proto.NotCurrent, nil } // 叫分中,不能出牌 if !r.canOutPoker() { log.Error(r.SeatLog(seat, "out card error.because room status is call status")) return proto.BadAction, nil } pokerPos := -1 for pos, p := range seat.Pokers { if p.ToInt() == req.Poker { pokerPos = pos } } if pokerPos == -1 { log.Error(r.SeatLog(seat, "has not poker:%v %v", req.Poker, poker.NewPoker(req.Poker).ToString())) return proto.BadParam, nil } pk := seat.Pokers[pokerPos] seat.Pokers = append(seat.Pokers[:pokerPos], seat.Pokers[pokerPos+1:]...) return proto.Ok, pk } // 玩家出牌 func (r *TrucoRoom) onOutPoker(msg map[string]interface{}) { _, _, uid, data := ParseMsg(msg) req, err := util.MapToStructT[proto.ReqPlayerOutPoker](data) if err != nil { log.Error(r.Log(err.Error())) return } seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } //log.Debug(r.SeatLog(seat, "申请出牌:%v,玩家手牌:%v", poker.NewPoker(req.Poker).ToString(), poker.PokersToString(seat.pokers))) code, outPoker := r.checkOutPoker(seat, req) if code != proto.Ok { rsp := &proto.RspPlayerOutPoker{Code: code, Poker: nil} for _, p := range seat.Pokers { rsp.Poker = append(rsp.Poker, p.ToInt()) } if seat.OutPoker != nil { rsp.OutPoker = seat.OutPoker.ToInt() } r.SendMsg(seat.Player(), proto.RspPlayerOutPokerId, rsp, false) return } else { r.Broadcast(proto.NtfPlayerOutPokerId, &proto.NtfPlayerOutPoker{CurrentSeat: seat.No(), Poker: req.Poker}, false) } // 牌局回顾 r.record.AddAction(proto.NtfPlayerOutPokerId, &proto.NtfPlayerOutPoker{CurrentSeat: seat.No(), Poker: req.Poker}) if r.pokers.IsBigPoker(outPoker, stub.GGlobalAI.TrucoBiggerPoker) { r.gameLog.Teams[r.teamColor(seat.No())].BigPoker++ } seat.SetCanAct(false) log.Debug(r.SeatLog(seat, "出牌:%v 剩余牌:%v", poker.NewPoker(req.Poker).ToString(), poker.PokersToString(seat.Pokers))) //log.Debug(r.Log("cancel timer:TtPlayerAct")) r.CancelTimer(baseroom.TtPlayerAct) seat.OutPoker = outPoker if r.isRoundOver() { if r.roundSettle() { // 对局结束,会清理部分数据 r.GameOver(nil) } } else { r.NextSeat() r.PlayerAct(r.CurrentPlayer()) } } func (r *TrucoRoom) checkSetDarkPoker(seat *TrucoSeat, req *proto.ReqSetDarkPoker) (proto.ErrorCode, *poker.Poker) { if r.gameStep >= GsGameSettle { log.Error(r.SeatLog(seat, "game is over")) return proto.BadAction, nil } pokerPos := -1 for pos, p := range seat.Pokers { if p.ToInt() == req.Poker { pokerPos = pos } } if pokerPos == -1 { log.Error(r.SeatLog(seat, "has not poker:%v %v", req.Poker, poker.NewPoker(req.Poker).ToString())) return proto.BadParam, nil } // 第一回合不能设置暗排 if r.round == 0 { log.Error(r.SeatLog(seat, "第一回合不能设置暗排")) return proto.NotSetDarkPoker, nil } pk := seat.Pokers[pokerPos] return proto.Ok, pk } // 设置暗牌或恢复明牌 func (r *TrucoRoom) onSetDarkPoker(msg map[string]interface{}) { _, _, uid, data := ParseMsg(msg) req, err := util.MapToStructT[proto.ReqSetDarkPoker](data) if err != nil { log.Error(r.Log(err.Error())) return } seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } code, outPoker := r.checkSetDarkPoker(seat, req) if code != proto.Ok { r.SendMsg(seat.Player(), proto.RspSetDarkPokerId, &proto.RspPlayerOutPoker{Code: code}, false) return } outPoker.IsDark = !outPoker.IsDark rsp := &proto.RspSetDarkPoker{Code: code, Poker: outPoker.ToInt()} r.Broadcast(proto.RspSetDarkPokerId, rsp, false) log.Debug(r.SeatLog(seat, "设置暗牌或恢复明牌:%v,玩家手牌:%v", poker.NewPoker(req.Poker).ToString(), outPoker.ToString())) // 牌局回顾 r.record.AddAction(proto.RspSetDarkPokerId, rsp) } func (r *TrucoRoom) checkPlayerAct(seat *TrucoSeat, req *proto.ReqPlayerAct) proto.ErrorCode { if !seat.CanAct() { log.Error(r.SeatLog(seat, "不能行动")) return proto.BadAction } if r.gameStep != GsPlayerAct && r.gameStep != GsDecidingAct { log.Error(r.SeatLog(seat, "gameStep:%v can not act", r.gameStep)) return proto.BadAction } // 玩家本轮操作中已操作过,不能重复操作 if lastAct := r.lastTeamAct(TcUnknown); lastAct != nil { if _, ok := lastAct.actType[seat.No()]; ok { return proto.BadAction } } // 投降,直接判输,进入本局结算 if req.Raise == AtGiveUp.ToInt(r.PlayType()) { return proto.Ok } colorPos := r.teamColor(seat.No()) switch req.Raise { case AtTruco3.ToInt(r.PlayType()), AtP6.ToInt(r.PlayType()), AtP9.ToInt(r.PlayType()), AtP12.ToInt(r.PlayType()): _, code := r.canRaise(colorPos, req.IsTruco, true) return code case AtAgree.ToInt(r.PlayType()): return r.canAgree(colorPos) default: log.Error(r.SeatLog(seat, "req.sendRaise:%v is error", req.Raise)) return proto.BadParam } } // 队友是否都已操作 func (r *TrucoRoom) isTeamAllOperated(teamColor TeamColor) bool { teamAct := r.lastTeamAct(teamColor) operated := true for _, st := range r.Seats { if r.teamColor(st.No()) == teamColor { if _, ok := teamAct.actType[st.No()]; !ok { operated = false break } } } return operated } // 玩家放弃,1v1则直接结算,2v2则看队友是否也放弃 func (r *TrucoRoom) actGiveUp(seat *TrucoSeat) { teamColor := r.teamColor(seat.No()) teamAct := r.lastTeamAct(teamColor) operated := true if r.IsMVM() { operated = r.isTeamAllOperated(teamColor) } if teamAct.maxAct == AtGiveUp && operated { r.GameOver(seat) } } // 玩家行动回合,申请加分或弃牌或同意 func (r *TrucoRoom) onPlayerAct(msg map[string]interface{}) { _, _, uid, data := ParseMsg(msg) req, err := util.MapToStructT[proto.ReqPlayerAct](data) if err != nil { log.Error(r.Log(err.Error())) return } seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } rsp := &proto.RspPlayerAct{Point: r.point} if rsp.Code = r.checkPlayerAct(seat, req); rsp.Code != proto.Ok { r.SendMsg(seat.Player(), proto.RspPlayerActId, rsp, false) return } color := r.teamColor(seat.No()) seat.SetCanAct(false) log.Debug(r.SeatLog(seat, "玩家申请操作:%v,操作回合:%v", IntToActType(req.Raise), r.round)) // 最近的一个操作是同意或者没有操作,本操作为放弃,则算主动放弃 nextLastAct := r.lastTeamAct(TcUnknown) // 更新玩家选择 r.updateTeamAct(color, IntToActType(req.Raise), seat.No()) timerType := r.getActTimeType() if timerType != baseroom.TtUnknown { r.CancelTimer(timerType) } // 更新操作后的最近一个行动,与nextLastAct不一定是同一个act lastAct := r.lastTeamAct(TcUnknown) rsp.NotOptSeat = -1 if r.IsMVM() && len(lastAct.actType) < r.RoomCnf.MinPlayers/2 { ntfTeammateAct := &proto.NtfTeammateAct{CurrentSeat: seat.No(), Act: req.Raise} // 广播给队友更新我的选择 r.BroadcastToColor(proto.NtfTeammateActId, ntfTeammateAct, color, false) } rsp.Point = r.point rsp.IsTruco = util.Tie(len(r.actTypes) == 1, 1, 0) rsp.CurrentSeat = seat.No() rsp.Raise = lastAct.maxAct.ToInt(r.PlayType()) rsp.Star = r.star if rsp.Raise == AtGiveUp.ToInt(r.PlayType()) && (nextLastAct == nil || nextLastAct.maxAct == AtAgree) { rsp.IsTruco = 2 } // 本队所有人都已操作完 if len(lastAct.actType) == r.RoomCnf.MinPlayers/2 { if r.gameStep == GsDecidingAct { r.gameStep = GsPlayerAct } if lastAct.maxAct == AtGiveUp { // 广播给所有人 r.Broadcast(proto.RspPlayerActId, rsp, false) // 牌局回顾 r.record.AddAction(proto.RspPlayerActId, rsp) r.GameOver(seat) } else if lastAct.maxAct == AtTruco3 || lastAct.maxAct == AtP6 || lastAct.maxAct == AtP9 || lastAct.maxAct == AtP12 { r.lastTrucoColor = color // 金币场 玩家接受truco,或者接受并加到P6,P9,P12等情况倍数加1 if r.ClubId() == 0 && len(r.actTypes) > 1 { r.star++ r.gameLog.Star = r.star rsp.Star = r.star } actColor := r.rivalColor2(color) for _, st := range r.Seats { if r.teamColor(st.No()) == actColor { st.SetCanAct(true) log.Debug(r.SeatLog(st, "玩家可以操作")) } } // 广播给所有人 r.Broadcast(proto.RspPlayerActId, rsp, false) // 牌局回顾 r.record.AddAction(proto.RspPlayerActId, rsp) r.notifyPlayerRspRaise() r.NewTimer(baseroom.TtPlayerRspRaise, time.Duration(r.RoomCnf.ActTime+stub.GGlobal.TrucoActExTime)*time.Second, int(r.rivalColor2(color))) } else if lastAct.maxAct == AtAgree { // 金币场 玩家接受truco,倍数加1 if r.ClubId() == 0 { r.star++ r.gameLog.Star = r.star rsp.Star = r.star } // 广播给所有人 r.Broadcast(proto.RspPlayerActId, rsp, false) // 牌局回顾 r.record.AddAction(proto.RspPlayerActId, rsp) // 通知原玩家出牌 r.PlayerAct(r.CurrentPlayer()) } } } // 游戏结束玩家申请展示手牌 func (r *TrucoRoom) onShowPoker(msg map[string]interface{}) { _, _, uid, _ := ParseMsg(msg) seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } log.Debug(r.SeatLog(seat, "申请展示手牌")) if r.gameStep != GsGameSettle || len(seat.Pokers) == 0 { r.SendMsg(seat.Player(), proto.RspShowPokerId, &proto.RspShowPoker{Code: proto.BadAction}, false) return } r.SendMsg(seat.Player(), proto.RspShowPokerId, &proto.RspShowPoker{Code: proto.Ok}, false) ntf := &proto.NtfShowPoker{CurrentSeat: seat.No()} for _, pk := range seat.Pokers { pk.IsDark = false ntf.Poker = append(ntf.Poker, pk.ToInt()) } r.Broadcast(proto.NtfShowPokerId, ntf, false) r.gameLog.Teams[r.teamColor(seat.No())].Show++ } // 玩家断线重连 func (r *TrucoRoom) onReconnect(msg map[string]interface{}) { _, _, uid, _ := ParseMsg(msg) seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } seat.SetFakeLeave(false) r.notifyFakeLeave(seat) //log.Debug(r.SeatLog(seat, "seat:%v 重连", seat.No())) // 重连后取消托管状态 r.setPlayerHosting(seat.Player(), false) rsp := &proto.RspReconnect{ Code: proto.Ok, RoomInfo: r.makeProtoRoomInfo(), Players: nil, Dealer: r.dealer, } for _, st := range r.Seats { if st.Empty() { continue } rsp.Players = append(rsp.Players, r.makePlayerToProto(st, st.Player().UID != seat.Player().UID)) } r.SendMsg(seat.Player(), proto.RspReconnectId, rsp, false) if lastMsg, ok := r.LastMsg[uid]; ok { ilm := lastMsg.Msg if lm, ok1 := lastMsg.Msg.(*proto.NtfPlayerAct); ok1 && lastMsg.Tm.Unix() != 0 { lm.Countdown -= int(time.Now().Unix() - lastMsg.Tm.Unix()) lm.Countdown = util.Tie(lm.Countdown < 0, 0, lm.Countdown) } else if lm, ok1 := lastMsg.Msg.(*proto.NtfPlayerRspRaise); ok1 && lastMsg.Tm.Unix() != 0 { lm.Countdown -= int(time.Now().Unix() - lastMsg.Tm.Unix()) lm.Countdown = util.Tie(lm.Countdown < 0, 0, lm.Countdown) if teamAct := r.lastTeamAct(TeamColor(lm.TeamColor)); teamAct != nil { for _, act := range teamAct.actType { lm.Ally = append(lm.Ally, act.ToInt(r.PlayType())) } } } r.SendMsg(seat.Player(), lastMsg.MsgId, ilm, false) log.Debug(r.SeatLog(seat, "重连补发消息:%v", lastMsg.MsgId)) } } // 服务器维护中 func (r *TrucoRoom) onMaintain(msg map[string]interface{}) { r.onDisbandRoom(msg) } // gm解散房间 func (r *TrucoRoom) onDisbandRoom(_ map[string]interface{}) { //_, _, _, data := ParseMsg(msg) //req, err := util.MapToStructT[proto.NtfMaintain](data) //if err != nil { // log.Error(err.Error()) // return //} ntf := &proto.NtfMaintain{Code: proto.DisbandRoom} r.CancelAllTimer() r.gameStep = GsWaitGame r.disband = state.RdtGm r.Broadcast(proto.NtfMaintainId, ntf, false) // 游戏结束 r.endTime = time.Now() if r.record != nil { log.Debug("r.record is not nil") r.record.SetDisbandType(r) r.record.AddAction(proto.NtfMaintainId, ntf) r.SaveRecord(nil) } r.ReleaseRoom() } func (r *TrucoRoom) checkEmote(user *Player, req *proto.ReqEmote, rsp *proto.NtfEmote) { rsp.Code = proto.Ok rsp.EmoteId = req.EmoteId rsp.DestSeat = req.DestSeat it, ok := stub.GPayEmote[req.EmoteId] if !ok { // 免费表情 return } if it.Value < 1 { rsp.Code = proto.Ok return } resType, err := model.IntToRes(it.Type) if err != nil { log.Error(err.Error()) rsp.Code = proto.BadParam return } rsp.ResType = it.Type rsp.CostValue = it.Value if resType == model.ResCoins { op := model.NewUserResourceOp() coins, err := op.Get(user.UID, model.ResCoins) takeCoins, err1 := op.GetTakeCoin(user.UID) if err1 != nil || err != nil { rsp.Code = proto.Internal log.Error(fmt.Sprintf("err:%v err1:%v", err, err1)) return } if coins-takeCoins < it.Value { rsp.Code = proto.NotEnough log.Debug(fmt.Sprintf("coins:%v takeCoins:%v emote cost:%v not enough", coins, takeCoins, it.Value)) return } r.addResource(user, -it.Value, model.ResCoins, model.ReasonEmote) coins -= it.Value rsp.ResVal = coins } else { op := model.NewUserResourceOp() value, err := op.Get(user.UID, resType) if err != nil { rsp.Code = proto.Internal log.Error(fmt.Sprintf("err:%v", err)) return } if value < it.Value { rsp.Code = proto.NotEnough log.Debug(fmt.Sprintf("resType:%v resValue:%v emote cost:%v not enough", resType, value, it.Value)) return } r.addResource(user, -it.Value, resType, model.ReasonEmote) value -= it.Value rsp.ResVal = value } return } // 游戏结束玩家申请展示手牌 func (r *TrucoRoom) onEmote(msg map[string]interface{}) { _, _, uid, data := ParseMsg(msg) seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } req, err := util.MapToStructT[proto.ReqEmote](data) if err != nil { log.Error(err.Error()) return } rsp := &proto.NtfEmote{Seat: seat.No()} r.checkEmote(seat.Player(), req, rsp) log.Debug(r.SeatLog(seat, "第%d小局,使用表情:%v", r.smallGameCount, Emote(req.EmoteId))) isTeamEmote := Emote(req.EmoteId).IsSecret() r.gameLog.TrucoPlayers[seat.No()].EmoteNum++ if isTeamEmote { r.BroadcastToColor(proto.NtfEmoteId, rsp, r.teamColor(seat.No()), false) } else { if rsp.Code == proto.Ok { r.Broadcast(proto.NtfEmoteId, rsp, false) } else { r.SendMsg(seat.Player(), proto.NtfEmoteId, rsp, false) } } } // 玩家准备 func (r *TrucoRoom) onReady(msg map[string]interface{}) { _, _, uid, _ := ParseMsg(msg) seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } log.Debug(r.SeatLog(seat, "收到玩家准备")) seat.SetReady(true) allReady := true for _, st := range r.Seats { if !st.Ready() { allReady = false break } } if allReady { r.CancelTimer(baseroom.TtGameReadyStart) if r.SeatPlayerNum() == r.RoomCnf.MinPlayers && r.Status() != state.RsGaming { r.ReInit() r.GameStart() } } // 游戏中,重连玩家发送准备后,设置为假离开后返回 if r.Status() == state.RsGaming { r.SetFakeLeave(uid, false) } } // 返回错误码,是否解散,解散发起人 func (r *TrucoRoom) checkUserDisbandRoom(uid int64, req *proto.ReqUserDisbandRoom) (proto.ErrorCode, bool, int64) { if r.ClubId() < 1 { return proto.BadParam, false, 0 } if r.Status() == state.RsWait { return proto.NotDisbandRoom, false, 0 } // 申请解散 if req.Type == 0 { if req.Agree == int(state.DtStart) { r.disbandInfo = make(map[int64]int) r.disbandTime = time.Now().Unix() r.NewTimer(baseroom.TtDelDisbandRoomInfo, time.Second*(baseroom.DisbandRoomCd+2)) } if r.disbandInfo == nil { return proto.BadParam, false, 0 } seat := r.GetSeat(uid) if seat == nil { return proto.BadParam, false, 0 } r.disbandInfo[uid] = req.Agree initiator := int64(0) agree := 0 for k, v := range r.disbandInfo { if v > 0 { agree++ } if v == 2 { initiator = k } } if agree == r.SeatPlayerNum() { return proto.Ok, true, initiator } return proto.Ok, false, initiator } // 管理员强制解散 clubUser, err := model.NewUserClubInfoOp().Load(uid, r.ClubId()) if err != nil { return proto.NotClubMgr, false, 0 } if clubUser.Admin == 1 { return proto.Ok, true, 0 } if clubUser.Admin != 2 { return proto.NotClubMgr, false, 0 } perms := model.NewClubPermissionsOp().Load(r.ClubId()) for _, pi := range perms { if pi == 1005 { return proto.Ok, true, 0 } } return proto.NotClubMgr, false, 0 } func (r *TrucoRoom) onUserDisbandRoom(msg map[string]interface{}) { _, _, uid, data := ParseMsg(msg) req, err := util.MapToStructT[proto.ReqUserDisbandRoom](data) if err != nil { log.Error(err.Error()) return } code, ok, initiator := r.checkUserDisbandRoom(uid, req) rsp := proto.RspUserDisbandRoom{Code: code, RoomId: r.Id(), Type: req.Type} SendMsgToGate(uid, proto.RspUserDisbandRoomId, rsp) log.Debug(r.Log("user:%v 解散房间.reqType:%v 同意:%v", uid, req.Type, req.Agree)) ntf := &proto.NtfUserDisbandRoom{ RoomId: r.Id(), CountDown: int(baseroom.DisbandRoomCd - (time.Now().Unix() - r.disbandTime)), CountDownEx: 2, Type: req.Type, Agree: nil, Against: nil, Initiator: initiator, } if code == proto.Ok { log.Debug(r.Log("解散时间:%v now:%v begin:%v 差值:%v", baseroom.DisbandRoomCd, time.Now().Unix(), r.disbandTime, time.Now().Unix()-r.disbandTime)) if ntf.CountDown < 0 || ntf.CountDown > 10 { ntf.CountDown = 0 } for user, agree := range r.disbandInfo { if agree == 0 { ntf.Against = append(ntf.Against, user) } else { ntf.Agree = append(ntf.Agree, user) } } r.Broadcast(proto.NtfUserDisbandRoomId, ntf, true) } // 管理员解散房间,此时可能房间未开始,直接解散 if ok { r.CancelAllTimer() r.gameStep = GsWaitGame // 游戏结束 r.endTime = time.Now() r.disband = util.Tie(req.Type == 0, state.RdtMember, state.RdtMgr) if r.record != nil { r.record.SetDisbandType(r) r.record.AddAction(proto.NtfUserDisbandRoomId, ntf) r.SaveRecord(nil) } r.ReleaseRoom() } } func (r *TrucoRoom) onHosting(msg map[string]any) { _, _, uid, data := ParseMsg(msg) req, err := util.MapToStructT[proto.ReqHosting](data) if err != nil { log.Error(err.Error()) return } seat := r.GetSeat(uid) if seat == nil { log.Error(r.Log("player:%d seat is nil", uid)) return } r.setPlayerHosting(seat.Player(), req.Enable) }