samba/server/game/baseroom/baseRoom.go

628 lines
15 KiB
Go
Raw Normal View History

2025-06-04 09:51:39 +08:00
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()))
}
}