import * as _ from "underscore"; import { ns_card } from "../../Config/_ini_"; import { Timer,GLog } from 'cgserver'; import { EErrorCode } from '../../Config/_error_'; import { CardServer } from '../CardServer'; import { CardRecordSeatInfo, CardSeat, CardSeatInfo, ECardSeatState, PlayerInfo } from "./CardSeat"; import { ExtUserModel, GExtUserSer } from "../../../Common/Service/ExtUserService"; import { GUserCreateTableSer as GUserCreateTableSer } from "../../../Common/Service/UserCreateTableService"; import { StatisticsCurrencyModel, ECurrencyWhy, GStatisticsCurrency } from "../../../Common/Service/StatisticsCurrency"; import { GTableSer } from "../../../Common/Service/TableService"; import { CardRecordModel, GCardRecordSer } from "../../../Common/Service/CardRecordService"; import { ClubModel, GClubSer } from "../../../Common/Service/ClubService"; import { GClubTableSer } from "../../../Common/Service/ClubTableService"; import { GCoinRecordSer } from "../../../Common/Service/CoinRecordService"; import { ExtRoomModel } from "../Room/CardRoom"; import { GRecordSer, RecordModel } from "../../../Common/Service/RecordService"; export class CardCreateTableDataModel { /** * 房间类型 */ table_type:ns_card.ETableType=-1 /** * 游戏分类 */ game_type=-1 /** * 底分 */ base_point:number=1 /** * 最小金币携带量 */ min_point:number=-1 /** * 最多人数 */ max_user:number=-1 /** * 最低人数 */ min_user:number=-1 /** * 局数 */ total_num:number=-1 /** * 付费方式 */ fee_type:ns_card.EFeeType=-1 /** * 是否可以中途加入 */ can_enter_after:boolean = false /** * 是否可以中途离开 */ can_leave:boolean = false /** * 是否可以随时离开,包括正在玩耍还没结束当前局 */ can_any_leave:boolean = false /** * 是否需要记录结束时候的总结战绩,通常只有再单纯固定局数的玩法中为true其他都为false */ need_end_record:boolean = false /** * 是否自动准备 */ auto_prepare:boolean = false /** * 自动坐下模式不可支持旁观 * 非自动坐下模式可支持旁观 */ auto_sitdown:boolean = false /** * 是否自动开始 */ auto_start:boolean = false /** * 超时准备的处理方式 */ prepare_timeout_type:ns_card.EPrepareTimeOutType = ns_card.EPrepareTimeOutType.None /** * 是否随机座位 */ random_seat:boolean=true /** * 虚拟金币(非携带模式使用) */ virtual_coin:number = -1 } export class CardTableInfo { table_type:ns_card.ETableType = -1 ctdm:CardCreateTableDataModel = null id = -1 room_id = "" room_level = -1 club_id = -1 state = -1 create_user_id = -1 is_dismissing = false apply_dismiss_user_id = -1 cur_num = -1 //当前局数 dismiss_timeout = -1 timeout = -1 seats = new Map() audience:{[user_id:number]:{id:number,logo:string,nickname:string}} = {} } export class CardRecordTableInfo { userrecords:{[user_id:number]:CardRecordSeatInfo}={} table_id:number=-1 create_time:number=-1 create_user_id=-1 create_user_nickname="" create_user_logo="" ctdm:CardCreateTableDataModel=null total_num:number=-1 cur_num:number=-1 begin_time:number=-1 end_time:number=-1 } /* 牌桌相关操作打牌之类的 */ export class CardTable { /** * 是否每一局结束的时候就把金币数据写入数据库(捕鱼,连线机类型的推荐false) */ protected _is_record_coin_every_battle = true protected _is_support_robot:boolean = false get clientNum() { let num=0 for(let i=0;i() get seats() { return this._seats } protected _audience:{[user_id:number]:{id:number,logo:string,nickname:string}} = {} get audience() { return this._audience } clearAudience() { this._audience={} } protected _dismiss_timer = new Timer() protected _timer = new Timer() protected _user_last_action_check_timer = new Timer() protected _quest_robot_timer = new Timer()//积分场用来间隔性请求机器人 protected _records:CardRecordTableInfo[]=[]//每一局的统计 get user_id_list():number[] { let uids:number[] = [] for(let i=0;i0) { seat.point=this._ctdm.virtual_coin } return seat } init(ctdm:CardCreateTableDataModel) { this._ctdm = ctdm //自动补齐参数 let default_dm = new CardCreateTableDataModel() for(let key in default_dm) { if(typeof this._ctdm[key] == "undefined") { this._ctdm[key] = default_dm[key] } } this._seats = new Array(this._ctdm.max_user) this._create_time = new Date().getTime() if(this._ctdm.table_type==ns_card.ETableType.Friend ||this._ctdm.table_type==ns_card.ETableType.ClubFriend) { this._timer.setTimeout(this._onBeginTimeOut.bind(this),this._server.cfg.Wait_Begin_Time) } this._state = ns_card.ETableState.Waiting if(this._ctdm.table_type!=ns_card.ETableType.Match) { this._user_last_action_check_timer.setTimeout(this._onLastActionCheck.bind(this),2*60*1000) } if(this._club) { GClubTableSer.updateOne({$addToSet:{tableids:this._id}},{club_id:this._club.id}) } if(this._ctdm.table_type==ns_card.ETableType.Score) { this._quest_robot_timer.setTimeout(this._tryQuestRobot.bind(this),3000) } return null } protected _tryQuestRobot() { if(!this._is_support_robot) { return } let has_realy_man = false for(let i=0;i() for(let seat of this._seats) { //五分钟不操作踢出去 if(seat &&!seat.isState(ECardSeatState.Waiting) &&time-seat.lastActionTime>5*60*1000) { lst_kickout.push(seat.id) } } for(let user_id of lst_kickout) { await this.onLeave(user_id) } if(!this._is_dismissed) { this._user_last_action_check_timer.setTimeout(this._onLastActionCheck.bind(this),2*60*1000) } } protected _onBeginTimeOut() { let ws = this._server.getAnyWebSocket() ws?.send_dismiss_table_bc(this._id,this.bc_user_id_list,true,true) this.dismiss() } async onTableEnd() { this._clear() if(!this._is_record_coin_every_battle) { await this._processCoin() } this._table_end_time = new Date().getTime() let ws = this._server.getAnyWebSocket() if(!ws) { this.dismiss() GLog.error(this._server.fullServerName+" onTableEnd() this._game.getAnyWebSocket()==null") return } let user_id_list = this.user_id_list if(this._ctdm.need_end_record &&this._cur_num>=2) { let brm = new RecordModel() brm.game_name=this._server.name brm.table_id=this._id if(this._club) { brm.club_id=this._club.id } brm.create_user_id=this._creator.id brm.create_time=this._create_time brm.table_end_time=this._table_end_time let table_info = new CardRecordTableInfo() table_info.create_time = this._create_time table_info.end_time = this._table_end_time table_info.create_user_id = this._creator.id table_info.create_user_nickname=this._creator.nickname table_info.create_user_logo=this._creator.logo table_info.ctdm = this._ctdm for(let index in this._seats) { let seat = this._seats[index] if(!seat||seat.isState(ECardSeatState.Waiting)) { continue } table_info.userrecords[seat.id] = seat.getGameEndRecordSeatInfo() } brm.table_info = JSON.stringify(table_info) brm.table_detail_info = JSON.stringify(this._records) let record_id = await GRecordSer.add(user_id_list,brm) //再这里发送的原因是因为方便再得到brm的id ws?.send_game_end_bc(this._id,user_id_list,brm) //从牌桌管理器中删除 this._server.tableMgr.onWeaklyDismissTable(this._id,record_id) } else { ws?.send_game_end_bc(this._id,user_id_list,null) this.dismiss() } } /** * 解散 */ dismiss() { this._is_dismissed = true let user_id_list = this.bc_user_id_list for(let i=0;i { if(!seat) { return } seat.agreeDismiss = null }) this.onDismissCancel() return ns_card.EDismissResult.Cancel } seat.agreeDismiss = is_agree let is_all_agree = true for(let i=0;i { if(!st) { return } st.agreeDismiss = null }) this.resume() } /** * 判断是不是创建者 * @param user_id */ isCreator(user_id) { return this._creator.id==user_id } protected _doFee() { if(this._ctdm.fee_type==ns_card.EFeeType.Creator) { let table_crystal = this._server.cfg.All[this._ctdm.total_num] this._doFeeForOne(this._creator.id,table_crystal) } else if(this._ctdm.fee_type==ns_card.EFeeType.AA) { let table_crystal = this._server.cfg.AA[this._ctdm.total_num] this._seats.forEach((seat)=> { if(!seat) { return } this._doFeeForOne(seat.id,table_crystal) }) } } protected _doFeeForOne(user_id,table_crystal) { GExtUserSer.updateOne({$inc:{crystal:-table_crystal}},{user_id:user_id}) //钻石统计 let scm = new StatisticsCurrencyModel() scm.from_id = user_id scm.create_time = new Date().getTime() scm.game_name = this._server.name scm.why = ECurrencyWhy.Table_Fee scm.crystal = table_crystal GStatisticsCurrency.insert(scm) let st = this._getSeatByUserId(user_id) st.isFee = true } stop() { this._timer.pause() } onForceDismiss() { this.stop() } protected _clear() { for (let id in this._seats) { let seat = this._seats[id]; if(!seat) { continue } seat.clear() } this._dismiss_timer.clear() this._timer.clear() this._user_last_action_check_timer.clear() this._state=ns_card.ETableState.Waiting } resume() { this._timer.resume() } isAudience(user_id) { if(this._audience[user_id]) { return true } return false } addAudience(user:{id:number,logo:string,nickname:string}) { if(this.isAudience(user.id)) { return } this._audience[user.id]=user } removeAudience(user_id) { delete this._audience[user_id] } getLastestRecord(num=1) { if(this._records.length<=0) { return } let start = this._records.length-num if(start<0) { start=0 } let rds:CardRecordTableInfo[]=[] for(let i=start;i0) { ws?.send_enter_table_bc(this._id,bc_user_id_list,seat.getSeatInfo()) } } else { if(this._ctdm.auto_sitdown) { let errcode = this._trySitdown(playerinfo) if(errcode) { return errcode } ws?.send_enter_table(this._id,playerinfo.id,info) errcode = this.onSitdown(playerinfo) if(errcode) { ws?.send_leave_table_bc(this._id,this.bc_user_id_list,playerinfo.id) } if(errcode) { return errcode } delete this._audience[playerinfo.id] } else { this.onEnterAsAudience(playerinfo) } } } onEnterAsAudience(user:{id:number,logo:string,nickname:string}) { if(this._getSeatByUserId(user.id)) { return } let ws = this._server.getAnyWebSocket() let info = this.getInfo() ws?.send_enter_table(this._id,user.id,info) user = {id:user.id,logo:user.logo,nickname:user.nickname} this.addAudience(user) } protected _trySitdown(playerinfo:PlayerInfo) { let coin = playerinfo.point if(this._ctdm.virtual_coin&&this._ctdm.virtual_coin>0) { coin = this._ctdm.virtual_coin } let crystal = playerinfo.crystal let user_id = playerinfo.id if(this._ctdm.total_num>0 &&this._ctdm.total_num-this._cur_num<2) { return EErrorCode.Not_Allow_Enter_Near_Last } if(this._state!=ns_card.ETableState.Waiting &&!this._ctdm.can_enter_after) { return EErrorCode.Not_Allow_Enter_Table } if(this._ctdm.fee_type==ns_card.EFeeType.AA) { let table_crystal = this._server.cfg.AA[this._ctdm.total_num] if(crystal=this._ctdm.min_user) { this._tryTriggerPrepare() } } return } protected _tryStandUp(user_id:number) { if(this._state!=ns_card.ETableState.Waiting &&this._state!=ns_card.ETableState.Prepare) { return false } let st = this._getSeatByUserId(user_id) if(!st) { return false } return true } /** * 角色请求离开座位 * @param user_id */ onStandUp(user_id:number) { let ret = this._tryStandUp(user_id) if(!ret) { return false } let st = this._getSeatByUserId(user_id) this.addAudience(st.playerInfo) this._seats[st.index]=null this._server.seatMgr.removeSeat(user_id) --this._cur_user_num this.broadcast("standup_bc",this._id,st.playerInfo.id) return true } /** * 三状态,true,可以离开,false已经处理了,其他错误代码,为不能离开或出错 * @param user_id 角色id号 */ protected _tryLeaving(user_id) { let rs={ret:null,errcode:null} if(this.isAudience(user_id)) { this.removeAudience(user_id) rs.ret = false return rs } let seat = this._getSeatByUserId(user_id) if(!seat) { rs.errcode = EErrorCode.No_Seat_By_User_Id return rs } if(this._state!=ns_card.ETableState.Waiting &&!this._ctdm.can_leave) { rs.errcode = EErrorCode.Not_Allow_Leave_Table return rs } if(seat.isState(ECardSeatState.Waiting)) { rs.ret=true return rs } if(this._state!=ns_card.ETableState.End &&this._state!=ns_card.ETableState.Waiting &&this._state!=ns_card.ETableState.Prepare) { if(this._ctdm.can_any_leave) { seat.state = ECardSeatState.Leaving this.onAuto(seat.id,true) rs.ret = false return rs } else { rs.errcode = EErrorCode.Not_Allow_Leave_Table return rs } } rs.ret = true return rs } //玩家 async onLeave(user_id:number) { let rs = this._tryLeaving(user_id) if(rs.ret!=true) { if(rs.ret==false) { return } else//errcode { return rs.errcode } } let seat = this._getSeatByUserId(user_id) if(!this._is_record_coin_every_battle) { await this._processCoin(user_id) } this._seats[seat.index]=null this._server.seatMgr.removeSeat(user_id) --this._cur_user_num let ws = this.server.getAnyWebSocket() ws?.send_leave_table(user_id,this._id) let user_id_list = _.without(this.bc_user_id_list,user_id) if(user_id_list.length>0) { ws?.send_leave_table_bc(this._id,user_id_list,user_id) } if(this._cur_user_num<=0) { await this.onTableEnd() } else { if(this._cur_user_num { if(st) { st.clear() st.state = ECardSeatState.Preparing } }) this._onPrepareTimeout() return false } else { if(this._state == ns_card.ETableState.End)//不放在onEnd中处理的原因是,有可能end状态进来坐下的人依然是等待状态 { this._seats.forEach((st)=> { if(!st) { return } st.clear() }) } this._state = ns_card.ETableState.Prepare this._timer.setTimeout(this._onPrepareTimeout.bind(this),this._server.cfg.Prepare_Timeout_Time) let ws = this._server.getAnyWebSocket() ws?.send_begin_prepare_bc(this._timer.deadline,this.bc_user_id_list) } return false } protected async _onPrepareTimeout() { this._timer.clear() let prepare_num = 0 let ws = this._server.getAnyWebSocket() for(let i=0;i=this._ctdm.min_user&&this._ctdm.auto_start) { this.onStart() } } /** * 成功之后通知客户端并且返回true * 失败之后返回false */ onPrepare(user_id) { if(this._state!=ns_card.ETableState.Prepare &&this._state!=ns_card.ETableState.Waiting) { return false } let seat = this._getSeatByUserId(user_id) if(seat.isState(ECardSeatState.Preparing)) { return false } seat.state = ECardSeatState.Preparing let ws = this._server.getAnyWebSocket() ws?.send_prepare_bc(user_id,this.bc_user_id_list) //全部都已经准备 if(this.prepare_list.length==this._cur_user_num) { //开局人数大于最高要求人数则尝试直接开局 if(this._cur_user_num>=this._ctdm.max_user&&this._ctdm.auto_start) { this._onPrepareTimeout() return true } } return true } protected _onStartProcess() { this._seats.forEach((seat)=> { if(!seat) { return } seat.onStart() }) } /** * * @param user_id 请求的人 */ onRobotMatch(user_id:number) { if(this._state > ns_card.ETableState.Prepare) { return EErrorCode.Cur_State_No_Robot } if(user_id!=this._creator.id) { return EErrorCode.Not_Creator } if(!this._is_support_robot) { return EErrorCode.Not_Support_Robot } let num = _.random(this._ctdm.min_user,this._ctdm.max_user) num -= this._cur_user_num if(num<=0) { return EErrorCode.No_Need_Robot } if(this._server.robotMgr) { this._server.robotMgr.questRobotsToTable(num,this.id) } } //游戏开始 async onStart() { //不是等待开始阶段就不能开始游戏 if(this._state!=ns_card.ETableState.Waiting &&this._state!=ns_card.ETableState.Prepare &&this._state!=ns_card.ETableState.End) { return EErrorCode.Can_Not_Start_For_Cur_State } let prepare_num = this.prepare_list.length if(prepare_num0&&this._cur_num==this._ctdm.total_num) { this.onTableEnd() return EErrorCode.Table_Ended } this._begin_time = new Date().getTime() //对后坐下的玩家扣费 if(this._cur_num>1 &&this._ctdm.fee_type==ns_card.EFeeType.AA) { let table_crystal = this._server.cfg.AA[this._ctdm.total_num] this._seats.forEach((seat)=> { if(!seat) { return } if(!seat.isFee) { this._doFeeForOne(seat.id,table_crystal) } }) } ++this._cur_num//当前局加一 this._onStartProcess() let ws = this._server.getAnyWebSocket() let table_info = this.getInfo() this.broadcastOther("start_game_bc",table_info) for(let i=0;i0) { //非携带的虚拟金币不用写回数据库 return } let _processOne=async (seat:CardSeat)=> { let dt_point = 0 if(brd) { dt_point=brd.userrecords[seat.id].dtpoint } else { dt_point=seat.dtpoint } let pre_coin = 0 let aft_coin = 0 if(!this._club) { let user = await GExtUserSer.getById(seat.id) pre_coin = user.coin let sr = await GExtUserSer.updateOne({$inc:{coin:dt_point}},{user_id:seat.id}) if(sr.errcode||sr.rs.modifiedCount==0) { return } user = await GExtUserSer.getById(seat.id) aft_coin = user.coin } else { let userclub = await GClubSer.getUserClub(seat.id,this._club.id) pre_coin = userclub.coin let errcode = await GClubSer.addCoin(seat.id,this._club.id,dt_point) if(errcode) { return } userclub = await GClubSer.getUserClub(seat.id,this._club.id) aft_coin = userclub.coin } await GCoinRecordSer.addGame(seat.id,this.server.name,this._room_info._id,this._room_info.club_id,pre_coin,aft_coin,dt_point) } let promises=[] //目前只记录积分场 if(this._ctdm.table_type==ns_card.ETableType.Score) { if(user_id) { let seat = this._getSeatByUserId(user_id) if(!seat.isRobot) { let po = _processOne(seat) promises.push(po) } } else if(brd) { for(let uid in brd.userrecords) { let seat = this._getSeatByUserId(uid) if(seat.isRobot) { continue } let po = _processOne(seat) promises.push(po) } } else { for(let index in this._seats) { let seat = this._seats[index] if(!seat||seat.isRobot) { continue } let po = _processOne(seat) promises.push(po) } } } for(let i=0;i { if(!st||st.isState(ECardSeatState.Waiting)) { return } if(st.auto) { st.state=ECardSeatState.Leaving } if(st.isRobot) { all_robot=false } }) } //积分场全是机器人就不要记录了 if(!all_robot&&this._ctdm.game_type==ns_card.ETableType.Score) { let user_id_list = _.keys(brd.userrecords) this._saveRecord(user_id_list) if(this._is_record_coin_every_battle) { await this._processCoin(null,brd) } } if((this._cur_num>0&&this._cur_num==this._ctdm.total_num) ||(this._room_info&&this._room_info.dismiss)) { ws?.send_game_result_bc(this.bc_user_id_list,brd,this._timer.deadline) this.onTableEnd() return } else { this._timer.setTimeout(this._tryTriggerPrepare.bind(this),this._server.cfg.Next_Game_Timeout_Time) ws?.send_game_result_bc(this.bc_user_id_list,brd,this._timer.deadline) } //更新玩法 if(this._need_update_game_cfg&&this._room_info) { this._need_update_game_cfg=false let room = this._server.roomMgr.getRoomById(this._room_info._id) if(room) { let cfg = room.cfgCtdm if(cfg) { for(let key in cfg) { if(this._ctdm[key]!=undefined) { this._ctdm[key]=cfg[key] } } } } } this._seats.forEach(async (st)=> { if(!st) { return } st.onEnd() if(st.isState(ECardSeatState.Leaving)) { await this.onLeave(st.id) } else if(st.point=this._ctdm.max_user) { return -1 } let seats = new Array() for(let i=0;i=this._ctdm.max_user) { //人员已满 return -1 } for(let i=0;i