samba/server/game/baseroom/baseRoom.go
2025-06-04 09:51:39 +08:00

628 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()))
}
}