samba/server/truco/room/trucoRoomHandler.go
2025-06-04 09:51:39 +08:00

892 lines
25 KiB
Go
Raw Permalink 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 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,或者接受并加到P6P9,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)
}