package baseroom import ( "encoding/json" "fmt" "github.com/google/uuid" "math/rand" "samba/pkg/log" "samba/pkg/servername" pkgSrv "samba/pkg/service" "samba/proto" . "samba/server/game/player" . "samba/server/game/service" "samba/stub" "samba/util/model" "samba/util/routingKey" "samba/util/state" "samba/util/util" "time" ) var srv pkgSrv.IService func SetService(s pkgSrv.IService) { srv = s } type LastMsg struct { Msg interface{} MsgId string Tm time.Time } type BaseRoom[Seat ISeat] struct { RoomCnf *stub.Room clubRoomCnf *model.ClubPlayInfo id int clubId int // 俱乐部id roomType int // 房间配置id playType int GameNo string status state.RoomStatus Seats []Seat timeTypes map[TimerType]uint32 timerHandler ITimerHandler LastMsg map[int64]*LastMsg truthRoom IRoom baseGameLog BaseGameLog // 游戏操作汇总给后台 HasLuckyUser bool // 是否有幸运用户 } func NewBaseRoom[Seat ISeat](id, roomType, clubId int) (*BaseRoom[Seat], proto.ErrorCode) { playType := 0 var roomCnf stub.Room var clubRoomCnf *model.ClubPlayInfo if clubId == 0 { cnf, code := stub.FindRoomCnf(roomType) if code == proto.Ok { roomCnf = *cnf playType = roomCnf.PlayType } else { log.Error(fmt.Sprintf("room type %d not found", roomType)) return nil, proto.Internal } } else { playInfo := model.NewClubPlayInfoOp().LoadAPlayInfo(clubId, roomType) if playInfo == nil { log.Error(fmt.Sprintf("room type %d not found.club:%v", roomType, clubId)) return nil, proto.NotClubPlayGame } if playInfo.State != 1 || playInfo.IsValid != 1 { log.Error(fmt.Sprintf("room type %d is not valid.club:%v", roomType, clubId)) return nil, proto.NotClubPlayGame } if playInfo.Blind < 1 || playInfo.PlayerNum < 2 { log.Error(fmt.Sprintf("room type %d p.club:%v,blind:%v playerNum:%v", roomType, clubId, playInfo.Blind, playInfo.PlayerNum)) return nil, proto.NotClubPlayGame } playType = playInfo.PlayType for _, rm := range stub.Rooms { if rm.PlayType == playType { roomCnf = *rm break } } roomCnf.Valid = 1 roomCnf.TakeCoin = 0 roomCnf.Blind = playInfo.Blind roomCnf.MinPlayers = playInfo.PlayerNum roomCnf.Rate = playInfo.Fee clubRoomCnf = playInfo } room := &BaseRoom[Seat]{ id: id, clubId: clubId, roomType: roomType, playType: playType, RoomCnf: &roomCnf, clubRoomCnf: clubRoomCnf, GameNo: uuid.NewString(), timeTypes: make(map[TimerType]uint32), LastMsg: make(map[int64]*LastMsg), baseGameLog: BaseGameLog{ RoomId: id, RoomType: roomType, Level: roomCnf.Level, PlayType: roomCnf.PlayType, Blind: roomCnf.Blind, GameNo: "", // 游戏开始之后生成gameNo StartTime: 0, // 游戏开始之后赋值 EndTime: 0, // 游戏结束之后赋值 Players: nil, // 游戏结束之后赋值(涉及到输赢金币) ClubId: clubId, PlatformFee: 0, // 俱乐部分成之后赋值 ClubFee: 0, // 俱乐部分成之后赋值 }, } if room.ClubId() > 0 { log.Debug(room.Log("创建房间")) } room.UpdateClubPlayingRoomInfo() return room, proto.Ok } func (r *BaseRoom[Seat]) Id() int { return r.id } func (r *BaseRoom[Seat]) Type() int { return r.roomType } func (r *BaseRoom[Seat]) SetTruthRoom(rm IRoom) { r.truthRoom = rm } func (r *BaseRoom[Seat]) PlayType() int { return r.playType } func (r *BaseRoom[Seat]) ClubId() int { return r.clubId } func (r *BaseRoom[Seat]) BaseGameLog() *BaseGameLog { return &r.baseGameLog } // 入座玩家数量(包含机器人) func (r *BaseRoom[Seat]) SeatPlayerNum() int { num := 0 for _, seat := range r.Seats { if !seat.Empty() { num++ } } return num } // 真实玩家数量 func (r *BaseRoom[Seat]) RealPlayerNum() int { num := 0 for _, seat := range r.Seats { if !seat.Empty() && !seat.Player().IsRobot() { num++ } } return num } // 机器人数量 func (r *BaseRoom[Seat]) RobotPlayerNum() int { num := 0 for _, seat := range r.Seats { if !seat.Empty() && seat.Player().IsRobot() { num++ } } return num } func (r *BaseRoom[Seat]) HasEmptySeat() bool { for _, seat := range r.Seats { if seat.Empty() { return true } } return false } // IsPlayerCurrentRoom 多个房间都有该玩家时,判断该房间是不是这个玩家的当前房间 func (r *BaseRoom[Seat]) IsPlayerCurrentRoom(uid int64) bool { for _, seat := range r.Seats { if !seat.Empty() && seat.Player().UID == uid { return true } } return false } func (r *BaseRoom[Seat]) HasPlayer(uid int64) bool { for _, seat := range r.Seats { if !seat.Empty() && seat.Player().UID == uid { return true } } return false } func (r *BaseRoom[Seat]) SetTimerHandler(th ITimerHandler) { if r.timerHandler == nil { r.timerHandler = th } } func (r *BaseRoom[Seat]) SetCurrentRoom(uid int64, current bool) { for _, seat := range r.Seats { if !seat.Empty() && seat.Player().UID == uid { if seat.IsPlayerCurrentRoom() != current { seat.SetCurrentRoom(current) } } } } // LatestSeatedTime 获取最后一个就座时间 func (r *BaseRoom[Seat]) LatestSeatedTime() time.Time { var t time.Time for _, seat := range r.Seats { sTime := seat.SeatedTime() if t.IsZero() || sTime.After(t) { t = sTime } } return t } func (r *BaseRoom[Seat]) AddPlayer(player *Player, seat int) { if seat < 0 || seat >= len(r.Seats) { log.Error(r.SeatLog(r.Seats[seat], "out of range")) return } r.Seats[seat].SetPlayer(player) // todo:默认最近一次进的房间为玩家正在玩的房间 r.Seats[seat].SetCurrentRoom(true) return } func (r *BaseRoom[Seat]) RemovePlayer(player *Player) { for _, seat := range r.Seats { if !seat.Empty() && seat.Player().UID == player.UID { seat.SetPlayer(nil) break } } } func (r *BaseRoom[Seat]) Status() state.RoomStatus { return r.status } func (r *BaseRoom[Seat]) SetStatus(s state.RoomStatus) { r.status = s } func (r *BaseRoom[Seat]) OnMessage(_ string, _ map[string]interface{}) {} func (r *BaseRoom[Seat]) DebugSendMsg(user *Player, msgId string, msg interface{}) { sMsg, _ := json.Marshal(msg) for _, seat := range r.Seats { if !seat.Empty() && seat.Player().UID == user.UID { log.Debug(r.SeatLog(seat, "send msg:%v %v", msgId, string(sMsg))) break } } } func (r *BaseRoom[Seat]) SendMsg(user *Player, msgId string, msg interface{}, save bool) { r.DebugSendMsg(user, msgId, msg) if user.IsRobot() { user.Robot.OnMessage(msgId, msg) } else { if save { r.LastMsg[user.UID] = &LastMsg{MsgId: msgId, Msg: msg, Tm: time.Now()} } if msgId == proto.RspEnterRoomId || msgId == proto.RspReconnectId || msgId == proto.RspCachetaReconnectId { SendMsgToGate(srv, user.UID, msgId, msg) } else { for _, seat := range r.Seats { if !seat.Empty() && seat.Player().UID == user.UID && !seat.FakeLeave() { SendMsgToGate(srv, user.UID, msgId, msg) break } } } if user.IsHosting() { user.Robot.OnMessage(msgId, msg) } } } func (r *BaseRoom[Seat]) Broadcast(msgId string, msg interface{}, save bool, exclude ...*Player) { for _, seat := range r.Seats { if !seat.Empty() { exist := false for _, excludePlayer := range exclude { if excludePlayer.UID == seat.Player().UID { exist = true break } } if !exist { r.SendMsg(seat.Player(), msgId, msg, save) } } } } func (r *BaseRoom[Seat]) NewTimer(timerType TimerType, duration time.Duration, args ...interface{}) { if r.timerHandler == nil { log.Error(r.Log("timer handler is nil")) return } if _, ok := r.timeTypes[timerType]; ok { log.Error(r.Log("timer type:%v is exist.can not new timer", timerType.String())) //if timerType == TtPlayerAct { // log.Debug(r.Log(log.StackTrace())) //} return } tid := srv.NewTimer(duration+time.Duration(10)*time.Millisecond, func() { r.timerHandler.OnTimer(timerType, args...) }, true, r.Log("start type:%v timer", timerType.String())) r.timeTypes[timerType] = tid //log.Debug(r.Log("start type:%v timer", timerType.String())) } func (r *BaseRoom[Seat]) CancelTimer(timerType TimerType) { if tid, ok := r.timeTypes[timerType]; ok { srv.CancelTimer(tid) delete(r.timeTypes, timerType) //log.Debug(r.Log("stop type:%v timer, rest timer:%+v", timerType.String(), r.timeTypes)) } } func (r *BaseRoom[Seat]) CancelAllTimer() { for _, tid := range r.timeTypes { srv.CancelTimer(tid) //log.Debug(r.Log("start type:%v timer", timerType.String())) } r.timeTypes = make(map[TimerType]uint32) } func (r *BaseRoom[Seat]) Log(format string, a ...any) string { head := fmt.Sprintf("room:%v type:%v ", r.id, r.roomType) return head + fmt.Sprintf(format, a...) } func (r *BaseRoom[Seat]) SeatLog(seat Seat, format string, a ...any) string { head := "" if seat.Player() != nil { head = fmt.Sprintf("room:%v type:%v seat:%v user:%v robot:%v ", r.id, r.roomType, seat.No(), seat.Player().UID, seat.Player().IsRobot()) } else { head = fmt.Sprintf("room:%v type:%v seat:%v ", r.id, r.roomType, seat.No()) } return head + fmt.Sprintf(format, a...) } func (r *BaseRoom[Seat]) UserLog(uid int64, format string, a ...any) string { var seat Seat exist := false for _, st := range r.Seats { if !st.Empty() && st.Player().UID == uid { seat = st exist = true break } } if exist { return r.SeatLog(seat, format, a...) } else { return r.Log(format, a...) } } func (r *BaseRoom[Seat]) RandRobotCnf() stub.RobotTemper { var robotConfigs map[stub.RobotTemper]int var wSum int if r.ClubId() > 0 { robotConfigs, wSum = stub.FindClubRobotConfig(r.RoomCnf.Blind, r.RoomCnf.PlayType).RobotTempers() } else { robotConfigs, wSum = r.RoomCnf.RobotTempers() } if wSum < 1 { log.Error(r.Log("robot config not exist")) return stub.RteUnknown } w := rand.Int() % wSum for temper, weight := range robotConfigs { if w < weight { return temper } w -= weight } log.Error(r.Log("robot config not exist")) return stub.RteUnknown } func (r *BaseRoom[Seat]) IsMVM() bool { return r.RoomCnf.MinPlayers >= 4 } // 玩家俱乐部房间玩游戏 func (r *BaseRoom[Seat]) GameStart() { r.SetStatus(state.RsGaming) r.UpdateClubPlayingRoomInfo() if r.ClubId() > 0 { for _, seat := range r.Seats { if !seat.Empty() { _ = model.NewUserQuickMatchOp().Save(seat.Player().UID, r.PlayType(), r.Type()) } } } if r.RealPlayerNum() > 1 { model.NewRealPlayGameOp().Update() } model.NewPlayGameOp().Update() for _, seat := range r.Seats { if !seat.Player().IsRobot() && !seat.Empty() && seat.Player().LuckyPoint.IsLuckyUser() { r.HasLuckyUser = true break } } } // 玩家俱乐部房间玩游戏 func (r *BaseRoom[Seat]) GameEnd() { r.SetStatus(state.RsDelete) r.RemovePlayingRoom() } // 尝试释放房间 func (r *BaseRoom[Seat]) TryReleaseRoom() { if RoomMgr.Find(r.Id()) == nil { return } emptyRoom := true for _, seat := range r.Seats { if !seat.Empty() { emptyRoom = false } } if emptyRoom { r.CancelAllTimer() r.GameEnd() log.Debug(r.Log("删除房间")) RoomMgr.Del(r.Id()) } } // 解散房间 func (r *BaseRoom[Seat]) ReleaseRoom() { for _, st := range r.Seats { if !st.Empty() { msgL := util.MakeMessage(proto.ReqLeaveRoomId, &proto.ReqLeaveRoom{RoomId: r.Id()}, st.Player().UID, r.Id()) r.truthRoom.OnMessage(proto.ReqLeaveRoomId, msgL) } } r.TryReleaseRoom() if r.ClubId() > 1 { err := model.NewClubWaitingRoomOp().Delete(r.clubId, r.id) if err != nil { log.Error(r.Log("删除俱乐部等待房间失败:%v", err)) } } } // 玩家俱乐部房间玩游戏 func (r *BaseRoom[Seat]) TriggerClubMemberPlaying(uid int64, playing bool) { if r.ClubId() < 1 { return } ntf := proto.ReqUserInClubGame{ ClubId: r.ClubId(), Playing: util.Tie(playing, 1, 0), RoomId: r.Id(), UserId: uid, } SendMsgToOther(srv, servername.Club, routingKey.Club, ntf.UserId, proto.ReqUserInClubGameId, ntf) if playing { r.UpdateClubPlayingRoomInfo() } } // 更新俱乐部游戏状态 func (r *BaseRoom[Seat]) UpdateClubPlayingRoomInfo() { if r.ClubId() < 1 { return } // 未开始游戏时,更新俱乐部房间状态 playingRoom := &model.ClubPlayingRoom{ RoomId: r.Id(), PlayType: r.PlayType(), RoomType: r.Type(), Blind: r.RoomCnf.Blind, CurrPlayerNum: r.SeatPlayerNum(), MaxPlayerNum: r.RoomCnf.MinPlayers, Status: int(r.status), MaxGameNum: 1, ServiceTime: ServiceStartTime, } //log.Debug(r.Log("当前人数:%v", playingRoom.CurrPlayerNum)) model.NewClubPlayingRoomOp().Update(r.ClubId(), playingRoom) } // 游戏启动后,从俱乐部空房间列表中移除 func (r *BaseRoom[Seat]) RemovePlayingRoom() { if r.ClubId() < 1 { return } model.NewClubPlayingRoomOp().Del(r.ClubId(), r.Id()) } // 游戏启动后,从俱乐部空房间列表中移除 func (r *BaseRoom[Seat]) MakeClubPlayGameInfo() *proto.ClubPlayGameInfo { if r.ClubId() < 1 { return nil } return &proto.ClubPlayGameInfo{ Id: r.clubRoomCnf.ID, Name: r.clubRoomCnf.Name, PlayType: r.clubRoomCnf.PlayType, Blind: r.clubRoomCnf.Blind, PlayerNum: r.clubRoomCnf.PlayerNum, GameNum: r.clubRoomCnf.GameNum, Fee: r.clubRoomCnf.Fee, Valid: r.clubRoomCnf.IsValid, } } // 更新俱乐部等待房间信息 func (r *BaseRoom[Seat]) UpdateClubWaitingRoom() { if r.ClubId() < 1 { return } if r.Status() != state.RsWait { err := model.NewClubWaitingRoomOp().Delete(r.clubId, r.id) if err != nil { log.Error(r.Log("删除等待房间信息失败:%v", err)) } return } seatsWithTime := make([]int64, 0, 2) for _, seat := range r.Seats { st := seat.SeatedTime() seatsWithTime = append(seatsWithTime, util.Tie(st.IsZero(), 0, st.Unix())) } info := model.WaitingRoom{ RoomType: r.roomType, RoomId: r.Id(), CurrentPlayers: r.SeatPlayerNum(), MaxPlayers: len(seatsWithTime), SeatsWithTime: seatsWithTime, } err := model.NewClubWaitingRoomOp().Update(r.clubId, &info) if err != nil { log.Error(r.Log("更新等待房间信息失败:%v", err)) } } // 获取金币或者俱乐部币 func (r *BaseRoom[Seat]) GetCoin(uid int64) (coins int64) { if r.ClubId() == 0 { op := model.NewUserResourceOp() coins, _ = op.Get(uid, model.ResCoins) return coins } else { op := model.NewUserClubInfoOp() if userClub, err := op.Load(uid, r.ClubId()); err == nil { return userClub.Score + userClub.FreeScore + userClub.InOutFreeScore } else { return 0 } } } // 获取携带的金币,俱乐部没有携带概念 func (r *BaseRoom[Seat]) GetTakeCoin(player *Player) (coins int64) { return util.Tie(r.ClubId() == 0, player.TakeCoin, 0) } func (r *BaseRoom[Seat]) AddTakeCoin(player *Player, add int64, reason string) { // holdTakeCoin在多开时,是多个房间的总携带,不是本房间的携带 if r.ClubId() == 0 { if holdTakeCoin, ok := model.NewUserResourceOp().AddTakeCoin(player.UID, add); ok { player.TakeCoin += add res := &proto.ReqAddResource{ UserId: player.UID, ResValue: add, ResType: model.ResTakeCoins, Reason: reason, RoomId: r.Id(), RoomType: r.Type(), GameNo: r.GameNo, Desc: "", IsNotify: true, } model.NewResourceRecord(res, holdTakeCoin).Flush(srv) } } else { log.Error(r.UserLog(player.UID, "club room:%v has not take_club_coin.room_type:%v play_type:%v club:%v ", r.Id(), r.Type(), r.PlayType(), r.ClubId())) } }