<style> .markdown-body { max-width: 1280px; } .blue { color: blue ; } </style> --- title: 'Yahu Server架構' ---[TOC] # Server 架構: ## 程式流程 #### GameLobby配桌完後先執行 tableObj.gameStatus = STATUS_BET_TIME; // 切成押注 tableObj.readFishScript(tableObj.gameMode); // 產魚腳本 tableObj.runLogicStart(); // 啟動計時器 ## 進入遊戲和離開遊戲, 使用的檔案 |檔案名稱|動作|觸發函式|內容| |-------- | --------|--------|--------| |gameHandler | 遊戲封包 | Handler.prototype.enterGame = function(msg, session, callback) | 進入遊戲封包=>檢查服務器是否在維護中<br> 檢查是否已在遊玩其他遊戲<br> 查詢或創建是否有gameuser<br> 更新登入紀錄<br> 寫入進入遊戲紀錄<br> 取得玩家金幣數量<br> 建立gameUser<br> | |gameUserManager | 會員管理 | GameUserManager.join = function(userId, yahuId, money) | 進入大廳時, 建立會員物件和寫入Redis => 'GameUsers', 離開遊戲刪除redis | |gameUser | 會員管理 | var GameUser = function(pomelo_app, userId, yahuId, money) | 創造GameUser 物件 | |gameLobby | 配桌系統 | GameLobby.enterGame = function(yahuId, lobbyId, callback) | 進入遊戲時, 將玩家放入配對列表 self.enterGameList[yahuId] = lobbyId | |gameLobby | 配桌系統 | GameLobby.matchHandling = function(self) | 定時排程, 處理配桌工作(不斷呼叫自己的遞迴函式) | |gameTable | 桌內活動 | GameTable.joinTable = function(gameUser, pos, callback) | 處理加入此桌工作 | |gameTable | 桌內活動 | GameTable.RandGrpID = function() | 產生局號(格式為隨機26+9), grpId=IK-K7V9Y | | | | | | |gameHandler | 遊戲封包 | Handler.bet = function(msg, session, callback) | 發射子彈, 單純蒐集封包資料, 沒處理邏輯 | |gameTable | 桌內活動 | GameTable.playerBet = function(pos, fortAngle, betLevel, bulletKind, lockFishId, callback) | 處理射子彈邏輯, 扣除金額產生,子彈編號, USER_SHOT | | | | | | |gameTable | 桌內活動 | GameTable.playerCatchFish = function(pos, bulletId, fishIdList, linkFishList, callback) | 玩家中魚處理邏輯, BULLET_HIT_FISH | |gameTable | 桌內活動 | GameTable.runResult = function(pos, useMinPool, bulletIsNull, bulletData, validFishId, validFishDataId, validLinkFishId, validLinkFishDataId, linkageExplosionEnabled, linkFishEnabled, pirateShipEnabled) | 進行中魚計算 | | | | | | |gameHandler | 遊戲封包 | Handler.leaveTable = function(msg, session, callback) | 呼叫大廳的函式 gameLobby.leaveGame | |gameTable | 桌內活動 | GameTable.userLeave = function (pos) | userLeaveFlag[pos]=true, 最後一個離開玩家,先將table上鎖 | |gameUserManager | 會員管理 | GameUserManager.join = function(userId, yahuId, money) | 離開大廳時, 刪除Redis => 'GameUsers' | ## 程式物件說明 GameUser(描述登入會員的基本物件) 說明 | 欄位名稱 | 資料型態 | 細節 | 內容 | | -------- | -------- | -------- |-------- | | app | Obj | pomelo_app | 跟pomelo物件綁定, 可以取得相關物件 this.app.get("key"); | | pushService | Obj | this.PushService = this.app.get('PushService') | 配置訊息管理器 | | gameId | Obj | this.app.get("name") | (遊戲配置)server name | | gameConfig | Obj | conf_sys.GAME_DATA[this.gameId] | Game Configuration | | gameCategoryId | Obj | this.gameConfig.game_category_id | 待補 | | userId | string | this.userId = userId | GPK平台的userId | | yahuId | int | this.yahuId = yahuId | yahu自己生產的userId| | money | float | this.money = money | 看資料庫存的是float | | lobbyId | Obj | this.lobbyId = 0 | User所在的大廳編號| | tableId | string | this.tableId = "" | User所在桌子編號 | GameUser(函式說明) | 函式名稱 | 細節 | 內容 | | -------- | -------- | -------- |-------- | | charge | function(data, callback) | 線上充值<br> 會送出 self.pushService.pushByUID(self.yahuId, "CHARGE", message, utils.doNothing) | GameUserManager(管理登入會員的物件) 說明 | 欄位名稱 | 資料型態 | 細節 | 內容 | | -------- | -------- | -------- |-------- | | app | Obj | this.app = pomelo_app | 同上 | | gameId | Obj | this.gameId = gameId | 登入的遊戲 | | gpkRedis | Obj | this.gpkRedis = this.app.get('gpkRedis') | redis控制器 | | gameUsers | Obj | this.gameUsers = {} | 登入的會員物件 | GameUserManager(函式說明) | 函式名稱 | 細節 | 內容 | | -------- | -------- | -------- |-------- | | clearRds | function() | 清除redis內的資料 key='GameUsers', 依照該玩家的yahuId去清除所有資料 | | join | function(userId, yahuId, money) | 會員加入 | | get | function(yahuId) | 取得會員 | | leave | function(yahuId, callback) | 會員離開, 清除redis內的資料 key=‘GameUsers’, 依照該玩家的yahuId去清除資料 | | count | function() | 取得目前會員數 | GameLobby(描述進入和離開遊戲的基本物件) 說明 | 欄位名稱 | 資料型態 | 細節 | 內容 | | -------- | -------- | -------- |-------- | | app | Obj | pomelo_app | 同上 | | pushService | Obj | this.PushService = this.app.get('PushService') | 配置訊息管理器 | | gameId | Obj | this.gameId = this.app.get("name") | server id | | gameConfig | Obj | this.gameConfig = conf_sys.GAME_DATA[this.gameId] | Game Configuration | | theme | Obj | this.theme = theme; | | | gpkRedis | Obj | this.gpkRedis = this.app.get("gpkRedis") | redis控制器 | | enterGameList | Obj | this.enterGameList = {} | 遊戲配對相關結構 | | redisKey | Obj | this.redisKey = this.gameConfig.redisKey | Table ID相關設定 | | tableIDPrefix | Obj | this.tableIDPrefix = utils.dateFormat(new Date(), "yyyyMMdd") | Table ID相關設定 | | tableIdx | int | this.tableIdx = 0 | Table ID相關設定 | | lobbyNum | int | this.lobbyNum = this.gameConfig.lobbyNum | config/system.json 內的描述的大廳數量 | | lobbyUsers | Obj | this.lobbyUsers[i] = [] | 大廳內的User陣列 | | lobbyTables | Obj | this.lobbyTables[i] = {} | 大廳內的桌子陣列 | | match_mate_timer | Obj | this.match_mate_timer = null | | GameLobby(函式說明) | 函式名稱 | 細節 | 內容 | | -------- | -------- | -------- |-------- | | start | function() | 啟動定時器, 一秒觸發 'matchHandling' 函式 一次 | | enterGame | function(yahuId, lobbyId, callback) | 將玩家放入配對列表, self.enterGameList[yahuId] = lobbyId; | | matchHandling | function(self) | 處理配對列表 1: 使用 TABLE_LIMIT:+GameId 搜尋redis內的資料<br> 2:取得目前進入遊戲結構列表<br> 3:處理配對 tableObj.joinTable<br> | | leaveGame | function(yahuId, lobbyId, tableId, callback) | 離開遊戲檢查相關結構做清除<br> 1: tableObj.userLeave(pos); // 清除GameTable的玩家<br> 2: self.lobbyUsers[lobbyId] //清除玩家在廳內的狀態<br> 3: 清除玩家GameUserManager資料 | | broadcastHandling | function(lobbyId, data) | 處理廣播訊息<br> 訊息塞入 self.PushService.pushByList(self.lobbyUsers[lobbyId], "GAME_BROADCAST", data, utils.doNothing) | | charge | function (yahuId, lobbyId, tableId, amount, callback) | 線上充值, 呼叫GameTable的儲值函式 tableObj.charge(pos, amount) | GameTable(遊戲實際流程和邏輯的物件) 說明 | 欄位名稱 | 資料型態 | 細節 | 內容 | | -------- | -------- | -------- |-------- | | app | Obj | pomelo_app | 同上 | | pushService | Obj | this.PushService = this.app.get('PushService') | 配置訊息管理器 | | gameLogManager | Obj | this.gameLogManager = this.app.get('GameLogManager') | log物件, 寫賽果進logDb功能 | | tableId | Obj | this.tableId = tableId | 設定桌號 | | lobbyId | Obj | this.lobbyId = lobbyId | 設定大廳Id | | theme | Obj | this.theme = theme | 關卡描述物件 | | gameStatus | int | this.gameStatus = STATUS_WAIT_PLAY | 遊戲狀態 1:遊戲中 2:押注時間 3:新局 4:等待展演轉場表演 | | betTime | int | this.betTime = 300 | 押注時間 | | showTime | int | this.showTime = 3 | 展演時間, 目前三秒 | | maxLaserGun | Obj | this.maxLaserGun = this.theme.Controll_Value.Laser_max_execute_rate | 雷射砲啟動倍數上限 | | laserGunRate | Obj | this.laserGunRate = this.theme.Controll_Value.Laser_pool_ratio | 雷射砲抽取%數 | | poolProb | Obj | this.poolProb = this.theme.Controll_Value.All_pool_rate | 進水量 | | normalPoolRate | Obj | this.normalPoolRate = this.theme.Controll_Value.Normal_pool_rate | 一般水池%數 | | armsPoolRate | Obj | this.armsPoolRate = this.theme.Controll_Value.Weapon_pool_rate | 特殊武器水池%數 | | maxWin | Obj | this.maxWin = this.theme.Controll_Value.Max_win | 最大贏分限制 | | betRange | Obj | this.betRange = this.theme.Bet_Ratio[lobbyId.toString()] | 注碼範圍 | | betHitValue | Obj | this.betHitValue = this.theme.Bet_Hit_Value[lobbyId.toString()] | 注碼對應魚量 | | broadcastList | Obj | self.broadcastList.push(data) | 廣播列表陣列, 分全廳和該桌廣播 | | | | | | | maxUserNum | int | this.maxUserNum = 4 | 玩家上限, 魚機四人 | | YahuIdList | Array | this.YahuIdList = [] | 同桌玩家ID陣列 | | lockState | bool | this.lockState = false | Table上鎖狀態(若上鎖中,不分配玩家進入此桌)(BOSS出場,海盜船出場都會上鎖) | | leaveLockState | bool | this.leaveLockState = false | 最後一個玩家離開時的上鎖狀態 | | fishIdCount | int | this.fishIdCount = 0 | 魚ID Count計數器 | | gameMode | int | this.gameMode = NORMAL_MODE | 遊戲模式 1:普通 2:陣型 3:海盜船 | | gameNextMode | int | this.gameNextMode = 0 | 下一局遊戲模式 | | gameMap | Obj | this.gameMap = parseInt(Math.random() * this.theme.BG_Control.formation.BG_count + 1) | 遊戲地圖 | | gameNextMap | Obj | this.gameNextMap = 0 | 下一局遊戲地圖 | | gmaeSong | Obj | this.gmaeSong = parseInt(Math.random() * this.theme.BG_Control.formation.BGM_count + 1) | 遊戲音樂 | | gameNextSong | int | this.gameNextSong = 0 | 下一局遊戲音樂 | | gameModeCount | int | this.gameModeCount = 0 | 局數計數 | | bossEndTime |int | this.bossEndTime = 0 | BOSS結束時間 | | bossCome | bool | this.bossCome = false | BOSS出現 | | onRunTimer | Obj | this.onRunTimer = null | 遊戲計時器 | | | | | | | runCount | int | this.runCount = 1 | timer count | | fishScriptTime | int | this.fishScriptTime = 0 | 出魚腳本timer count的最大值 | | fishScript | Obj | this.fishScript = {} | 出魚腳本 | | fishScriptWithTime | Obj | this.fishScriptWithTime = {} |出魚腳本ID對應上的時間 | | allDeadFish | Obj | this.allDeadFish = {} | 全部死魚物件 | | bossScript | Obj | this.bossScript = {} | boss腳本物件 | | | | | | | userPoolMin | Obj | this.userPoolMin = {} | 玩家個人小水池 | | userPoolMax | Obj | this.userPoolMax = {} | 玩家個人大水池 | | userBulletData | Obj | this.userBulletData = {}; | 玩家所有子彈輸贏資訊(統計這發子彈打死什麼魚, 贏分和賠率多少, 最後定時寫入賽果內) | | userBulletDataNow | Obj | this.userBulletDataNow = {} | 玩家目前場上子彈資訊(單純紀錄場上存在的子彈資訊) | | userCatchFishData | Obj | this.userCatchFishData = {} | 玩家打死魚相關資訊 | GameTable(函式說明) | 函式名稱 | 細節 | 內容 | | -------- | -------- | -------- |-------- | | findTableEmptyPos | function() | 尋找Table空位 | | findTablePos | function (yahuId) | 尋找玩家在Table中的Pos | | joinTable | function (gameUser, pos, callback) | 玩家進入Table<br> 1:加入前最後確認是否有上鎖<br> 2:取得小水池<br> 3:取得大水池<br> 4:入桌基本資料初始化<br> 5:PushService.pushByUID Key='USER_JOIN_TABLE'<br> 6:廣播給所有user table中所有user狀況 key='TABLE_USER_INFO' | | charge | function (pos, amount) | 玩家遊戲內充值 PushService.pushByUID Key='CHARGE'<br> | | userLeave | function (pos) | 玩家離開遊戲<br> 1:移除self.YahuIdList陣列 <br> 2:設定離開旗標 self.userLeaveFlag[pos] = true<br> | | userLeaveHandling | function (pos) | 玩家離開遊戲實際處理<br> 1:檢查中魚狀態, 如果有不允許往下執行<br> 2:檢查寫賽果狀態, 如果有不允許往下執行<br> 3:刪除場上子彈+同步給場上玩家<br> 4:冰凍狀態解除+同步給場上玩家<br> 5:緩速狀態解除+同步給場上玩家<br> 6:寫賽果<br> 7:清除剩餘資料 self.userXXXXX = xxxx<br> 8:PushService 通知玩家離開 key='USER_LEAVE_TABLE'<br> 9:PushService 通知其他玩家哪個位置的玩家離開 key='LEAVE_POS'<br> | | playerUseItem | function (pos, itemType, callback) | 玩家使用道具<br> 1:確認gameStatus是否為可以使用道具狀態<br> 2:確認gameMode是否為可以使用道具狀態<br> 3:確認道具是否為可使用狀態,若是處於冰凍狀態所有道具都不可使用<br> 4:緩速要是玩家自己的在啟用中則不能再次使用<br> 5:確認道具數量<br> 6:減去道具數量<br> 7:啟用效果<br> 8:同步給場上玩家 PushService.pushByList key='USE_ITEM' <br> | | playerBet | function (pos, fortAngle, betLevel, bulletKind, lockFishId, callback) | 玩家發射子彈 | | playerCatchFish | function (pos, bulletId, fishIdList, linkFishList, callback) | 玩家中魚處理 | | runResult | function (pos, useMinPool, bulletIsNull, bulletData, validFishId, validFishDataId, validLinkFishId, validLinkFishDataId, linkageExplosionEnabled, linkFishEnabled, pirateShipEnabled) | 中魚計算 | | readFishScript | function (fishScriptMode) | 產出出魚腳本 | | randomRunFish | function (timeCount, kindAppearList) | 產出魚物件 (一般模式) | | onRunLogic | function (self) | 遊戲計時器 | | runLogicStart | function() | 啟動計時器 | | broadcastHandling | function() | 處理廣播訊息 | | writeRecord | function(pos) | 寫賽果 | | userPointCompute | function(pos, computePoint, action) | user point 更動 | | userDisplayPointCompute | function((pos, computePoint, action) | user display point 更動 | | RandGrpID | function() | 產生局號 | # 傳給Client的封包 ### Cmd=TABLE_USER_INFO (玩家進入遊戲) | 欄位名稱 | 資料型態 | 內容 | | -------- | -------- | -------- | | tablePosNickName | Obj | 登入會員資料(有四個玩家資料) | | tablePosPoint | Obj | 登入會員點數(有四個玩家資料) | | tableLaserGunValue | Obj | 雷射炮充能狀態轉為百分比(有四個玩家資料) | ``` json { "tablePosNickName":{ "1":"Leo", "2":null, "3":null, "4":null}, "tablePosPoint":{ "1":998194, "2":0, "3":0, "4":0}, "tableLaserGunValue":{ "1":0, "2":0, "3":0, "4":0} } ``` ### Cmd=FISH_APPEAR (生產一般魚) | 欄位名稱 | 資料型態 | 內容 | | -------- | -------- | -------- | | id | int | 腳本中的ID | | data_id | int | 產魚腳本資料中的Fish_id | | fish_kind | string | 魚種 | | time | int | 魚出現的時間 | | speed | int | 游動速度 | | sub_id | int | 魚群用ID | | path_id | int | 游動路徑Id | - [魚種表](#魚種表) ``` json { "appearFishData":[ {"id":1,"data_id":4,"fish_kind":"F","time":2,"speed":103,"sub_id":0,"path_id":1010}, {"id":2,"data_id":8,"fish_kind":"F","time":2,"speed":114,"sub_id":1,"path_id":1071}, {"id":3,"data_id":8,"fish_kind":"F","time":2,"speed":109,"sub_id":2,"path_id":1071}, {"id":4,"data_id":8,"fish_kind":"F","time":2,"speed":110,"sub_id":3,"path_id":1071}, {"id":5,"data_id":8,"fish_kind":"F","time":2,"speed":115,"sub_id":4,"path_id":1071}, {"id":6,"data_id":8,"fish_kind":"F","time":2,"speed":114,"sub_id":5,"path_id":1071} ] } ``` ### Cmd=USER_SHOT (射子彈) | 欄位名稱 | 資料型態 | 內容 | | -------- | -------- | -------- | | userPos | int | 座位Id | | bulletId | int | 子彈ID 規則是 yahuId + 流水號 | | fortAngle | int | 子彈角度 | | betLevel | int | 子彈等級 | | userPoint | int | 腳本中的ID | | userLaserGunValue | int | 雷射槍的百分比能量 | | dirllMaxCount | int | 鑽頭炮相關資料(待查)| | lockFishId | int | 待查 | ``` json { "userPos":"1", "bulletId":"2-1", "fortAngle":1.0061878773508814, "betLevel":1,"bulletKind":1, "userPoint":999999, "userLaserGunValue":0, "dirllMaxCount":0, "lockFishId":-1 } ``` ### Cmd=BULLET_HIT_FISH (玩家中魚處理) | 欄位名稱 | 資料型態 | 內容 | | -------- | -------- | -------- | | userPos | int | 座位Id | | bulletId | int | 子彈ID 規則是 yahuId + 流水號 | | FishId | int | 打中魚的FishId | ``` json { "userPos":"1", "bulletId":"2-1", "fishId":4 } ``` ### Cmd=CATCH_FISH (中魚計算, 魚死會收到此封包) | 欄位名稱 | 資料型態 | 內容 | | -------- | -------- | -------- | | userPos | int | 座位Id | | fishDeadCount | int | 魚死資料的數量 | | fishDeadData | int | 魚死資料 | | userPoint | int | 座位目前的金額 | ``` json { "userPos":"1", "fishDeadCount":1, "fishDeadData":{ "4":{"data_id":8,"fish_kind":"F","win":8,"dead_odds":0,"get_item":false} }, "userPoint":1000007 } ``` ### Cmd=READY_NEXT_MODE (出魚結束,同步換關訊息) 一秒會送一次, 共五次給Client, nextSec 5~1 的封包 | 欄位名稱 | 資料型態 | 內容 | | -------- | -------- | -------- | | nextMode | int | 下一關的出魚模式 遊戲模式 1:普通魚 2:陣型魚 3:海盜船 | | nextSec | int | 下一關的秒數 | ``` json { "nextMode": 1, "nextSec": 5 } ``` # 產魚流程: ### 遊戲狀態 | 名稱 | 資料內容 | 內容 | | -------- | -------- | -------- | | STATUS_WAIT_PLAY | 1 | 剛從大廳進入桌時, 會瞬間切到此狀態 | | STATUS_BET_TIME | 2 | 玩家可以押注, 此流程也會按照產魚腳本來產魚 | | STATUS_NEW_GAME | 3 | 另起新局 | | STATUS_WAIT_SHOW | 4 | 展演時間 | ### 遊戲模式 | 名稱 | 資料內容 | 內容 | | -------- | -------- | -------- | | NORMAL_MODE | 1 | 一般產魚 | | FORMATION_MODE | 2 | 生產魚陣 | | PIRATE_SHIP_MODE | 3 | 生產海盜船 | 其實就是 gameMode, 或是產魚模式 #### 遊戲最大單局時間為300秒, 爾後會根據最後一隻魚來動態調整每局時間 ``` javascript /* 產出出魚腳本 * fishScriptMode 是目前的產魚模式 */ function readFishScript( fishScriptMode ){ this.betTime和, 來決定每一秒要出什麼魚 = 300; // 起始產魚腳本最大秒數 // 根據betTime和theme.json, 來決定每一秒要出什麼魚 (i=偏移秒數) for (var i = 1; i <= self.betTime; i++) { // 根據模式來決定目前產魚的方式 switch(fishScriptMode) { case NORMAL_MODE: // 一般產魚 // 決定出魚邏輯區 (根據 Group_Appear_Data 來決定) // 使用randomRunFish函式, 塞入該秒數的出魚陣列(randomRunFish) // 將魚物件塞入腳本結構中 self.fishScript[fishIds[j]] = randomRunFish[fishIds[j]]; // 將fish id塞入產魚時間計數結構中 self.fishScriptWithTime[randomRunFish[fishIds[j]].time].push(fishIds[j]); // 預製產魚腳本時, 不斷更新該腳本最大時間計數, 方便判斷何時要進入下一局 'STATUS_NEW_GAME' if (randomRunFish[fishIds[j]].time > self.fishScriptTime) self.fishScriptTime = randomRunFish[fishIds[j]].time; break; case FORMATION_MODE: // 陣型魚模式 break; case PIRATE_SHIP_MODE: // 海盜船模式 break; } } } ``` 剛進遊戲桌內時, 設定遊戲邏輯計時器, 每秒觸發一次 ``` javascript tableObj.readFishScript(tableObj.gameMode); // 產魚腳本 tableObj.runLogicStart(); // 啟動計時器(1秒觸發一次) ``` 計時器內的 onRunLogic 主流程(簡化) ``` javascript // step-1 如果有玩家, 就開始遊戲流程的切換 if (self.YahuIdList.length) { if (self.gameStatus == STATUS_BET_TIME) { // 一般押注時間 // step-a 執行廣播訊息 (每兩個count才執行一次) if (self.runCount % 2 == 0) self.broadcastHandling(); // 檢查道具狀態(如果狀態時間超過,就同步給場上玩家) if (道具時間超過) { self.PushService.pushByList(self.YahuIdList, "ITEM_EFFECT_END", msg, utils.doNothing); } // 冰凍狀態下停止出魚 if ( isFrozen == false ){ // 丟骰子隨機決定是否要出 'FISH_BOSS' 魚(有符合基本出boss魚條件) if (如果要出Boss魚) { // 出BOSS時, 房間會上鎖, 不允許其他玩家進入 self.lockState = true; self.bossCome = true; // 塞入BOSS專用腳本 self.bossScript[scriptData.id] = scriptData; } // 出魚腳本出魚 var scriptFishIds = self.fishScriptWithTime[self.runCount]; // 取得目前秒數的出魚資料 for (var i = 0; i < scriptFishIds.length; i++) { // 讀取產魚陣列 var scriptFishId = scriptFishIds[i]; var scriptFishData = self.fishScript[scriptFishId]; // 塞入準備出魚陣列 appearFishData.push(scriptFishData); } // 有出普通魚,同步給場上玩家 if (appearFishData.length) { var msg = { "appearFishData": appearFishData } self.PushService.pushByList(self.YahuIdList, "FISH_APPEAR", msg, utils.doNothing); } // 有出BOSS,同步給場上玩家 if (appearBossData.length) { var msg = { "appearBossData": appearBossData } self.PushService.pushByList(self.YahuIdList, "BOSS_APPEAR", msg, utils.doNothing); } // 準備出下一次關卡, if (self.runCount == self.fishScriptTime) { // 如果 腳本計時器 == 最後一隻魚的生產時間, 就準備換下一關卡 if (self.gameMode == NORMAL_MODE) { // 如果目前是一般出魚模式時 if (8局以上有20%機率出海盜船 或 15局以上的話必出海盜船){ // 20%出海盜船 self.gameNextMode = PIRATE_SHIP_MODE; }else{ // 80%出陣型魚 self.gameNextMode = FORMATION_MODE; } }else{ // 一般模式外下一局都會回到一般模式 self.gameNextMode = NORMAL_MODE; } // 出魚結束,同步換關訊息 if (self.runCount > self.fishScriptTime) { self.PushService.pushByList(self.YahuIdList, "READY_NEXT_MODE", msg, utils.doNothing); } // 這邊等待5個tick讓client做結束處理 if (self.runCount >= self.fishScriptTime + 5) { self.gameStatus = STATUS_NEW_GAME; // 切成開新局狀態 } } } self.runCount++; // 腳本計時器累加 } else if (self.gameStatus == STATUS_NEW_GAME) { // 另啟新局 // 根據桌內人數, 依序寫入賽果跟判斷是否廣播 for (var i = 1; i <= self.maxUserNum; i++) { // step-a 寫賽果(如果沒有玩家要離開) self.writeRecord(i); // step-b 如果上一關是海盜船, 且玩家贏分 > x分, 就廣播 self.broadcastList.push(data); // 如果是海盜船關卡結束, 進行房間解鎖 self.lockState = false; } // step-c 通知切換下一局 self.gameMode = self.gameNextMode self.PushService.pushByList(self.YahuIdList, "NEXT_MODE", msg, utils.doNothing); // step-d 強制解除冰凍和緩速狀態, 並且廣播給同桌玩家 self.PushService.pushByList(self.YahuIdList, "ITEM_EFFECT_END", msg, utils.doNothing); // step-e 讀取下一次的產魚腳本 self.readFishScript(self.gameMode); // step-f 切成 展演時間 流程 self.gameStatus = STATUS_WAIT_SHOW; // 切成展演狀態 } else if (self.gameStatus == STATUS_WAIT_SHOW) { // 展演時間 (刷海浪) self.runCount++; // 腳本計時器累加 // step-a 五秒後切到 STATUS_BET_TIME 流程 self.gameStatus = STATUS_BET_TIME; } } // step-2 如果有玩家的 userLeaveFlag(離開旗標), 就執行userLeaveHandling(離開函式) for (var i = 1; i <= self.maxUserNum; i++) { if (self.userLeaveFlag[i]) { self.userLeaveHandling(i); } } // step-3 此桌都沒玩家時的table初始化 // step-4 重新設定計時器 self.onRunTimer = setTimeout(self.onRunLogic, ON_RUN_TIME, self); ``` 產魚過程 ``` javascript 1.Server啟動時, 程式讀取產魚描述檔案 theme.json 2.readFishScript 來決定, 每秒出什麼魚, 出魚資料塞入 self.fishScript 陣列內 出魚時間塞入 self.fishScriptWithTime 陣列內 3.冰凍狀態下不出魚 4.在主流程(onRunLogic) 遊戲狀態是 STATUS_BET_TIME 且 非冰凍狀態下, 會根據 self.fishScript 和 self.fishScriptWithTime 來決定出魚資料 出魚資料塞入 appearFishData 陣列 5.根據 appearFishData 來依序出魚, 並且廣播給Client self.PushService.pushByList(self.YahuIdList, "FISH_APPEAR", msg, utils.doNothing); 6 FISH_BOSS魚有點特例, 在主流程中再另行隨機判斷 塞入 self.bossScript 陣列內 // Boss預告 self.PushService.pushByList(self.YahuIdList, "BOSS_COME", {"fishDataId": fishDataId}, utils.doNothing); // 生產Boss self.PushService.pushByList(self.YahuIdList, "BOSS_APPEAR", msg, utils.doNothing); ``` ### 魚種表 | 魚種名稱 | 資料內容 | 內容 | | -------- | -------- | -------- | | FISH_BOSS | A | BOSS魚 | | FISH_SMALL_BOSS | B | 小BOSS魚 | | FISH_BIG | C | 大型魚(含組合魚) | | FISH_ODDS | D | 倍率魚 | | FISH_MIDDLE | E | 中型魚 | | FISH_SMALL | F | 小型魚 | | FISH_SPECIAL | G | 特殊魚種(道具魚) | | FISH_PIRATE_SHIP | S | 海盜船 | ### [theme.json 介紹](https://hackmd.io/@leoliuYahu/rJ98__4gI) # 打中魚流程: ## 收到封包時,會呼叫玩家中魚處理函式 玩家中魚處理 ``` javascript /* * @param {Number} pos 玩家座位Id * @param {String} bulletId 子彈ID * @param {Array} fishIdList 打中的魚的列表 (魚的流水ID) * @param {Array} linkFishList 待查 * @param {function} callback 有錯誤時的 ErrorCallBackFunction */ GameTable.prototype.playerCatchFish = function (pos, bulletId, fishIdList, linkFishList, callback) { // 移除重複的fish id // 取出第一隻魚的data id // 檢查是否為有效子彈 bulletData = self.userBulletDataNow[pos][bulletId]; if (bulletData) { // 有效子彈 // 確認使用的水池(根據押注等級數值來判斷使用大小水池) if (betLevel > BET_5) useMinPool = false; // 子彈命中廣播 var msg = { "userPos": pos, "bulletId": bulletId, "fishId": fishIdList[0], }; self.PushService.pushByList(self.YahuIdList, "BULLET_HIT_FISH", msg, utils.doNothing); if (如果是鑽頭炮) { // 水池進水(有分大小水池,只貼局部) // 小水池 // 一般水池進水公式 self.userPoolMin[pos].normal_values += (betPoint * self.poolProb / 100) * self.normalPoolRate / 100; // 武器水池進水公式 self.userPoolMin[pos].arms_values += (betPoint * self.poolProb / 100) * self.armsPoolRate / 100; } .... 省略 } // 針對無效子彈清除結構 // 針對抓取魚資料做處理(製作有效魚陣列 validFishId 和 validFishDataId) for (var i = 0; i < fishIdList.length; i++) { .... 省略 // 加入有效魚中 validFishId.push(fishScriptData.id); validFishDataId.push(fishScriptData.data_id); } .... 省略 // 進行 ********* 中魚計算 ********* var result = self.runResult( ... ) // 鑽頭砲相關處理 if ( 如果是鑽頭炮 ){ // 武器贏分累加 self.drillStatus[pos].win += result.armsWin; // 武器水池扣水 if (useMinPool) self.userPoolMin[pos].arms_values -= result.armsWin; else self.userPoolMax[pos].arms_values -= result.armsWin; // 鑽頭砲結束 if (self.drillStatus[pos].count == self.drillStatus[pos].max) { // 製作廣播結構 var data = { "broadcastId": DRILL_WIN_POINT, "msgData": { "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3), "win": self.drillStatus[pos].win } } self.broadcastList.push(data); } } // 連環炸彈相關處理 if (self.linkageExplosionStatus[pos][bulletId]) { // 武器贏分累加 self.linkageExplosionStatus[pos][bulletId].win += result.armsWin; // 武器水池扣水 if (useMinPool) self.userPoolMin[pos].arms_values -= result.armsWin; else self.userPoolMax[pos].arms_values -= result.armsWin; // 連環炸彈結束 if( 目前的count == 最大的max ){ // 製作廣播結構 var data = { "broadcastId": CHAIN_BOMB_WIN_POINT, "msgData": { "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3), "win": self.linkageExplosionStatus[pos][bulletId].win } } self.broadcastList.push(data); } } // 沒死魚不送封包 // 雷射砲廣播處理 // 針對死魚做處理 for (var i = 0; i < deadFishIds.length; i++) { // 製作死魚資料 var data = { "bullet_id": bulletId, "bet_point": betPoint, "bullet_kind": bulletKind, "data_id": result.fishDeadData[deadFishIds[i]].data_id, "fish_kind": result.fishDeadData[deadFishIds[i]].fish_kind, "win": result.fishDeadData[deadFishIds[i]].win, "kill_pos": pos } // 記錄整桌死魚物件排除海盜船 // 記錄玩家個人打死魚 self.userCatchFishData[pos][deadFishIds[i]] = data; // 道具處理 if (result.fishDeadData[deadFishIds[i]].get_item) { isGetItem = true; // 獲得道具旗標 if (data.data_id == SP_FROZEN) { //SP_FROZEN 最多使用次數 = 3 self.userItemNum[pos][FROZEN]++; } else if (data.data_id == SP_SLOW) { //SLOW 最多使用次數 =3 self.userItemNum[pos][SLOW]++; } else if (data.data_id == SP_DRILL) { //DRILL 最多使用次數 = 1 self.userItemNum[pos][DRILL]++; } } // 打死small boss var msg = { "broadcastId": SMALL_BOSS_DEAD, "msgData": { "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3), "fish_id": data.data_id, "win": result.allWin + result.armsWin } } self.broadcastList.push(msg); // 打中海盜船 if (data.fish_kind == FISH_PIRATE_SHIP) { // 武器水池扣水 if (useMinPool) self.userPoolMin[pos].arms_values -= result.armsWin; else self.userPoolMax[pos].arms_values -= result.armsWin; } // 打死水雷 if (data.data_id == SP_PARTIAL_BOMB) { var msg = { "broadcastId": PARTIAL_BOMB_WIN_POINT, "msgData": { "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3), "win": result.armsWin } } self.broadcastList.push(msg); } // 打死魚雷 if (data.data_id == SP_FULL_SCREEN_BOMB) { var msg = { "broadcastId": PARTIAL_BOMB_WIN_POINT, "msgData": { "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3), "win": result.armsWin } } self.broadcastList.push(msg); // 武器水池扣水 if (useMinPool) self.userPoolMin[pos].arms_values -= result.armsWin; else self.userPoolMax[pos].arms_values -= result.armsWin; } // 打死BOSS if (data.fish_kind == FISH_BOSS) { // 清除房間鎖狀態 self.lockState = false; // BOSS來襲結束,同步給場上玩家 self.PushService.pushByList(self.YahuIdList, "BOSS_COME_END", {}, utils.doNothing); if (一定贏分以上進行廣播){ var msg = { "broadcastId": BOSS_DEAD, "msgData": { "user_id": "******" + self.userUserIds[pos].slice(self.userUserIds[pos].length - 3), "fish_id": data.data_id, "win": result.allWin } } self.broadcastList.push(msg); } } // 打死連環炸彈 if (data.data_id == SP_CHAIN_BOMB) { ... 省略 // 同步給場上玩家 var msg = { "userPos": pos, "bulletId": bulletId, "fishId": parseInt(deadFishIds[i]), "explosionCount": explosionCount, "explosionX": explosionX, "explosionY": explosionY }; self.PushService.pushByList(self.YahuIdList, "CHAIN_BOMB_DATA", msg, utils.doNothing); } // 有取得道具,同步玩家自己的道具狀態 if (isGetItem) { var msg = { "userItemNum": self.userItemNum[pos] }; self.PushService.pushByUID(self.userYahuIds[pos], "USER_ITEM_NUM", msg, utils.doNothing); } } // 贏分加入本金 self.userPointCompute(pos, result.allWin + result.armsWin, ADD); self.userDisplayPointCompute(pos, result.allWin + result.armsWin, ADD); // 總贏分 self.userTotalWin[pos] += result.allWin + result.armsWin; // 將總倍率以及總贏分放入有效子彈資料 self.userBulletData[pos][bulletId].win += result.allWin + result.armsWin; self.userBulletData[pos][bulletId].total_odds += result.totalOdds; // 死魚同步給場上玩家 var msg = { "userPos": pos, "fishDeadCount": deadFishIds.length, "fishDeadData": result.fishDeadData, "userPoint": self.userDisplayPoint[pos], }; self.PushService.pushByList(self.YahuIdList, "CATCH_FISH", msg, function(err,data) { return utils.invokeCallback(callback, null, null); }); } ``` 備註1: [水池進水公式請參考水庫進水公式](#水庫進水公式) 中魚計算 ``` javascript /* * @param {int} pos 玩家座位Id * @param {Number} useMinPool 玩家小水池 * @param {bool} bulletIsNull 是否為無效子彈旗標 * @param {Object} bulletData 子彈資料 * @param {Number} validFishId 這一發子彈打中的..所有魚的流水Id(有效魚) * @param {Number} validFishDataId 這一發子彈打中的..所有魚的Fish_id(有效魚) * @param {Number} validLinkFishId 待補 * @param {Number} validLinkFishDataId 待補 * @param {Number} linkageExplosionEnabled 待補 * @param {Number} linkFishEnabled 待補 * @param {Number} pirateShipEnabled 待補 打到海盜船旗標 * @return {Object} result 回傳此次擊殺的結果 */ GameTable.prototype.runResult = function( ... 省略 ){ // 無效子彈,直接送回無效狀況 // 整理所有魚的賠率 for (var i = 0; i < validFishDataId.length; i++) { fishRatio[validLinkFishDataId[i]] = parseInt(Math.random() * self.theme.Fish_Data[validLinkFishDataId[i]].Ratio_Random + self.theme.Fish_Data[validLinkFishDataId[i]].Bet_ratio); } // 取出水池值 & 水池修正相關資料 if (useMinPool) { // 使用小水池 poolValues = self.userPoolMin[pos].normal_values; poolArmsValues = self.userPoolMin[pos].arms_values; if (poolValues > 0) { // (大魚及小魚)(正向) normalBigContainer = self.theme.Normal_Fish_Pool.Positive.small.big_fish.container; normalSmallContainer = self.theme.Normal_Fish_Pool.Positive.small.small_fish.container; // 依人數取得修正資料(分 3人同桌 4人同桌, 取不同的RatioFix ) ... 省略 // 如果是兩人取得 【正向】 普通子彈對應的RatioFix normalBigRatioFix = self.theme.Normal_Fish_Pool.Positive.small.big_fish.ratio_2; // 兩人修正-大魚 normalSmallRatioFix = self.theme.Normal_Fish_Pool.Positive.small.small_fish.ratio_2; // 兩人修正-小魚 }else{ // (大魚及小魚)(負向) ... 省略 // 如果是兩人取得 【負向】 普通子彈對應的RatioFix normalBigRatioFix = self.theme.Normal_Fish_Pool.Negative.small.big_fish.ratio_2; normalSmallRatioFix = self.theme.Normal_Fish_Pool.Negative.small.small_fish.ratio_2; } // 武器水池修正資料 // 判斷水位 spContainer = self.theme.Special_Fish_Pool.Positive.small.container; // 如果是兩人取得 【正向】 武器對應的RatioFix spRatioFix = self.theme.Special_Fish_Pool.Positive.small.ratio_2; }else{ // 使用大水池 poolValues = self.userPoolMax[pos].normal_values; poolArmsValues = self.userPoolMax[pos].arms_values; // 一般水池修正資料 (正水池/負水池) if (poolValues > 0) { // 判斷水位 (大魚及小魚)(正向) normalBigContainer = self.theme.Normal_Fish_Pool.Positive.big.big_fish.container; normalSmallContainer = self.theme.Normal_Fish_Pool.Positive.big.small_fish.container; // 依人數取得修正資料(分 3人同桌 4人同桌, 取不同的RatioFix ) ... 省略 // 如果是兩人取得 【正向】 對應的RatioFix normalBigRatioFix = self.theme.Normal_Fish_Pool.Positive.big.big_fish.ratio_2; normalSmallRatioFix = self.theme.Normal_Fish_Pool.Positive.big.small_fish.ratio_2; }else{ // 判斷水位 (大魚及小魚)(負向) normalBigContainer = self.theme.Normal_Fish_Pool.Negative.big.big_fish.container; normalSmallContainer = self.theme.Normal_Fish_Pool.Negative.big.small_fish.container; ... 省略 同上 } // 武器水池修正資料 // 判斷水位 ... 省略 // 依人數取得修正資料 spRatioFix = self.theme.Special_Fish_Pool.Positive.big.ratio_2; } // 計算擊中海盜船(待補) if (如果打中海盜船) { var pirateShipWeight = self.theme.Pirate_Ship.Random_Gap; // ???? for (var i = 0; i < pirateShipWeight.length; i++) { if (randWeight < pirateShipWeight[i]) { fishRatio[FISH_PIRATE_SHIP_DATA_ID] = self.theme.Pirate_Ship.Reward_Ratio[i]; break; } } } // 確認大小水池取出MAX BET maxBet = self.userPoolXXX[pos].max_bet; // 整理所有魚的致死 for (var i = 0; i < validFishDataId.length; i++) { // 取出魚的資料 var fishData = self.theme.Fish_Data[validFishDataId[i]]; // 資料不存在,放入初始致死率 if(!fishKillRate[validFishDataId[i]]){ fishKillRate[validFishDataId[i]] = fishData.Kill_rate; // 資料不存在,放入初始致死率 } // 不同魚使用不同水池 if(fishData.Fish_group == FISH_SMALL || fishData.Fish_group == FISH_MIDDLE) { // 小型魚 & 中型魚使用小魚一般水池修正資料 for (var j = 0; j < normalSmallContainer.length; j++) { if (isTrue) { if (poolValues > 0) fishKillRate[validFishDataId[i]] = parseInt(fishKillRate[validFishDataId[i]] * normalSmallRatioFix[j] / 10); else fishKillRate[validFishDataId[i]] = parseInt(fishKillRate[validFishDataId[i]] * 10 / normalSmallRatioFix[j]); // 水庫下降, 代表玩家輸了, 開始調高致死率 } } } else if (fishData.Fish_group != FISH_PIRATE_SHIP && fishData.Fish_id != SP_DRILL && fishData.Fish_id != SP_CHAIN_BOMB && fishData.Fish_id != SP_FULL_SCREEN_BOMB && fishData.Fish_id != SP_PARTIAL_BOMB) { // 大型魚使用大於水池修正資料 (除去海盜船/鑽頭砲/連環/全屏/區域) } else { } } .... 待補.... } ``` ### 致死率 ### 一網捕多魚機率 ``` javascript for (var i = 0; i < validFishDataId.length; i++) { // 原本的致死率 / 魚網捕的魚隻數 fishKillRate[validFishDataId[i]] = parseInt( fishKillRate[validFishDataId[i]] / validFishDataId.length ); } 假設有一隻魚 odd = 5 RTP = 98% 魚原始死機率 = 19.6% 的機率會死 如果一次捕獲兩隻 所以第一隻魚的死亡率 => 19.6 /2 = 9.8% 所以第二隻魚的死亡率 => 19.6 /2 = 9.8% 期望值為 9.8 + 9.8 = 19.6 如果一次捕獲三隻 所以第一隻魚的死亡率 => 19.6 /3 = 6.5% 所以第二隻魚的死亡率 => 19.6 /3 = 6.5% 所以第二隻魚的死亡率 => 19.6 /3 = 6.5% 期望值為 6.5 + 6.5 + 6.5 = 19.6 如果捕獲不同賠率的魚 Odd=5 Odd=20 A魚原始死機率 = 19.6% B魚原始死的機率 = 4.9% 如果一次捕獲兩隻 所以第一隻魚的死亡率 => 19.6 /2 = 9.8% 所以第二隻魚的死亡率 => 4.9 /2 = 2.45% 期望值為 9.8 + 2.45 = 12.25 ``` ### 致死率會根據水庫水位動態調整 # 水庫: ## 水庫控制項 theme.json 內, Controll_Value 裡面的控制變數 | 名稱 | 數值 | 內容 | | -------- | -------- | -------- | | All_pool_rate | 96 | 進水量 = All_pool_rate = self.poolProb | | Normal_pool_rate | 85 | 普通子彈水池%數, 控制 normal_values | | Weapon_pool_rate | 15 | 特殊武器水池%數, 控制 arms_values | Normal_Fish_Pool( 根據個人水池的正跟負, 取得不同的修正值, 此值會影響修正魚的致死率 ) | 名稱 | 數值 | 內容 | | -------- | -------- | -------- | Normal_Fish_Pool( 根據個人水池的正跟負, 取得不同的修正值, 此值會影響修正魚的致死率 ) | 名稱 | 數值 | 內容 | | -------- | -------- | -------- | | spRatioFix | [10,10,12,15,20] | 特殊武器致死率修正, 有分 2人3人4人的修正值 | 水庫和機率的規則 ``` javascript 1.規則1 theme.json 裡面的 Fish_Data 的 Kill_rate(致死率) 我發現好像偏低 都是 已 rtp 0.75 去算的 ( 例如 綠魚 條紋魚 大眼魚.... 美人魚) 2.規則2 if 個人水池 > 0 { // 個人水池大於零, 代表玩家輸錢, 所以增加致死率, 方便玩家打死魚 原本致死率 = 原本致死率 * (正向小水池修正因子 / 10) }else{ // 個人水池小於零, 代表玩家贏錢, 所以縮小致死率, 讓玩家很難打死魚 原本致死率 = 原本致死率 * (10 / 負向小水池修正因子) } 3.規則3 一網捕多魚修正 將 【原本魚的致死率 / 捕獲的魚數量 】 去做衰減 4.致死率超過80%一律以80%計 ``` ## 水庫進水公式 ``` javascript // 一般水池 self.userPoolMin[pos].normal_values += (betPoint * self.poolProb / 100) * self.normalPoolRate / 100; // 武器水池 self.userPoolMin[pos].arms_values += (betPoint * self.poolProb / 100) * self.armsPoolRate / 100; 假設 子彈一發是1點 (Bet=1) 一般水池進水量 = 1 * 96/100 * 85/100 = 1 * 0.96 * 0.85 = 0.816 武器水池進水量 = 1 * 96/100 * 15/100 = 1 * 0.96 * 0.15 = 0.144 ``` ## 個人水庫修正公式 ``` javascript // 扣除個人一般水池 allWin是此魚的總贏分 if (useMinPool) self.userPoolMin[pos].normal_values -= allWin; else self.userPoolMax[pos].normal_values -= allWin; ``` ## 水庫流程 armsWin = 武器總贏分 allWin = 一般魚總贏分 1. 在判斷是有效子彈後, 先根據 [水庫進水公式](#水庫進水公式), 把值灌進大小水池和武器水池內 a.self.userPoolMin[pos].normal_values += (betPoint * self.poolProb / 100) * self.normalPoolRate / 100; // 個人水池 b.self.userPoolMin[pos].arms_values += (betPoint * self.poolProb / 100) * self.armsPoolRate / 100; // 武器水池 3. 呼叫 runResult, 進行中魚計算 a. <span class="blue">根據目前水庫, 取得相對應的機率修正值</span> (normalBigRatioFix, normalSmallRatioFix) b. <span class="blue">整理所有魚的初步致死機率</span> fishKillRate[validFishDataId[i]] = fishData.Kill_rate c. <span class="blue">根據水庫水位調整魚的致死率, 水庫有水時</span> fishKillRate[validFishDataId[i]] = parseInt(fishKillRate[validFishDataId[i]] * normalSmallRatioFix[j] / 10); d. <span class="blue">根據水庫水位調整魚的致死率, 水庫沒水時</span> fishKillRate[validFishDataId[i]] = parseInt(fishKillRate[validFishDataId[i]] * 10 / normalSmallRatioFix[j]); e. <span class="blue">道具的致死率跟水庫修正</span> fishKillRate[validFishDataId[i]] = parseInt(fishKillRate[validFishDataId[i]] * spRatioFix[j] / 10); e. <span class="blue">跑 cnt < 2 迴圈, </span> for (var cnt = 0; cnt < 2; cnt++) a. <span class="blue">一網捕多魚的致死率修正, 該魚致死率/捕獲數, 平均分散在每隻捕獲魚</span> fishKillRate[validFishDataId[i]] = parseInt( fishKillRate[validFishDataId[i]] / validFishDataId.length ); b1. 執行runResult時, 武器水池修正 c. 執行runResult時, 最後算出allWin時, 再從個人水池內 扣回去 self.userPoolMin[pos].normal_values -= allWin; 6. 離開runResult後, 判斷如果是鑽頭炮射擊, 進行武器大小水池修正 self.userPoolMin[pos].arms_values -= result.armsWin; 7. 離開runResult後, 判斷如果是連環炸彈, 進行武器大小水池修正 self.userPoolMin[pos].arms_values -= result.armsWin; 8. 離開runResult後, 檢查死魚陣列, 判斷如果打死海盜船, 進行武器大小水池修正 self.userPoolMin[pos].arms_values -= result.armsWin; 9. 離開runResult後, 檢查死魚陣列, 判斷如果打死魚雷, 進行武器大小水池修正 self.userPoolMin[pos].arms_values -= result.armsWin; 水庫的精華是 1.打魚沒死, 水庫水位上升 2.打死魚, 水庫慢慢下降 水庫水位影響 魚的致死率 根據水庫水位, 取得不同的參數來調整 致死率 (有分 大魚, 小魚, 遊戲桌內[2,3,4]人 ) 當 (武器水位 - 武器的贏分) < 0, 變成負值時, 發不出錢時, 會 armOver = true 此時會重跑一次 致死率判斷魚死不死的迴圈, 然後將調整致死率=0, 就讓他再重跑一次流程 3.道具打中的每一隻魚, 致死率=0 4.炸彈類型/海盜船 都打不死, 每一隻魚, 致死率=0 基本上武器水池發不出獎就是直接不發武器水池的獎 # 資料庫結構: ## gpk_center (管理會員)資料庫 gamelist 遊戲列表 | 欄位名稱 | 資料型態 | 說明 | | -------- | -------- | -------- | | game_id | VARCHAR(10) | 主Key, 遊戲Id | | cn_name | VARCHAR(64) | 簡中名稱 | | en_name | VARCHAR(64) | 英文名稱 | | enable | TINYINT(1) | 是否啟用 | | game_category_id | TINYINT(3) | 遊戲類型ID | | chost | VARCHAR(64) | 前端服務器IP | | cport | INT(4) | 前端服務器PORT | | th_name | VARCHAR(64) | 泰文名稱 | | tw_name | VARCHAR(64) | 繁中名稱 | item 玩家持有道具列表 | 欄位名稱 | 資料型態 | 說明 | | -------- | -------- | -------- | | id | VARCHAR(10) | 主Key, Id, auto_increment 自動增長 | | count | DECIMAL(18,2) | 玩家遊戲點數(float) | | item_id | TINYINT(3) | 道具ID | | user_id | VARCHAR(20) | 玩家ID | play_log 玩遊戲下注紀錄列表 | 欄位名稱 | 資料型態 | 說明 | | -------- | -------- | -------- | | id | VARCHAR(255) | 流水號 | | agent_id | VARCHAR(255) | 代理商AGENT ID | | app_id | VARCHAR(10) | 遊戲ID | | bet_amount | DECIMAL(18,2) | 注額 | | bet_num | SMALLINT(5) | 投注倍數 | | bonus | DECIMAL(18,2) | 附加遊戲彩金 | | cost | DECIMAL(18,2) | 總投注 | | contribution | DECIMAL(18,6) | 貢獻金 | | payoff | DECIMAL(18,2) | 總損益 | | free_count | SMALLINT(5) | 剩餘免費遊戲次數 | | game_category_id | TINYINT(3) | 遊戲類型 | | game_id | BIGINT(20) | 遊戲ID | | game_table_idx | TINYINT(3) | 使用樣本庫ID | | result_type | TINYINT(3) | 樣本結果 | | jackpot_score | DECIMAL(18,2) | JP彩金 | | linked_score | DECIMAL(18,2) | 連線彩金 | | payoff_time | TIMESTAMP | 派彩時間 | | status | TINYINT(3) | 注單狀態 | | total_score | DECIMAL(18,2) | 總贏分 | | user_id | VARCHAR(20) | 投注玩家ID | | wagers_time | TIMESTAMP | 投注時間 | | date | DATE | 投注日期 | | coefficient | SMALLINT(5) | 得分倍數 | transactionlog 玩家點數交易紀錄(平台)列表 | 欄位名稱 | 資料型態 | 說明 | | -------- | -------- | -------- | | id | VARCHAR(255) | 交易序號 | | transfer_type | TINYINT(3) | 交易類型 | | user_id | VARCHAR(20) | 交易使用者ID | | amount | DECIMAL(18,2) | 交易金額 | | bef_money | DECIMAL(18,2) | 交易前金額 | | aft_money | DECIMAL(18,2) | 交易後金額 | | create_date | TIMESTAMP | 交易時間 | | status | TINYINT(3) | 交易狀態 | transactionlog 玩家點數交易紀錄(平台)列表 | 欄位名稱 | 資料型態 | 說明 | | -------- | -------- | -------- | | id | VARCHAR(20) | 玩家ID | | yahu_id | INT(11) | 亞虎玩家ID | | agent | VARCHAR(20) | 代理商AGENT ID | | enabled | TINYINT(1) | 帳號是否啟用 | | last_login | DATETIME | 最後登錄時間 | | last_quit | DATETIME | 最後離線時間 | | ip | VARCHAR(15) | 最後登錄IP | | currency | VARCHAR(5) | 幣別 | | update_date_time | TIMESTAMP | 更新時間 | | create_date_time | TIMESTAMP | 帳號建立時間 | ## fish_lord (魚王)資料庫 entergamelog 進入遊戲紀錄列表 | 欄位名稱 | 資料型態 | 說明 | | -------- | -------- | -------- | | user_id | VARCHAR(20) | 玩家ID | | lobby | TINYINT(3) | 進入的大廳Id | | create_date_time | TIMESTAMP | 玩家ID | game_play_log 遊戲下注紀錄列表 | 欄位名稱 | 資料型態 | 說明 | | -------- | -------- | -------- | | id | VARCHAR(255) | 流水號 | | agent_id | VARCHAR(255) | 代理商AGENT ID | | app_id | VARCHAR(10) | 遊戲ID | | bet_amount | DECIMAL(18,2) | 注額 | | bet_num | SMALLINT(5) | 投注倍數 | | bonus | DECIMAL(18,2) | 附加遊戲彩金 | | cost | DECIMAL(18,2) | 總投注 | | contribution | DECIMAL(18,6) | 貢獻金 | | payoff | DECIMAL(18,2) | 總損益 | | game_category_id | TINYINT(3) | 遊戲類型 | | fish_data_id | TINYINT(3) | 擊中魚ID | | jackpot_score | DECIMAL(18,2) | JP彩金 | | linked_score | DECIMAL(18,2) | 連線彩金 | | payoff_time | TIMESTAMP | 派彩時間 | | status | TINYINT(3) | 注單狀態 | | total_score | DECIMAL(18,2) | 總贏分 | | user_id | VARCHAR(20) | 投注玩家ID | | wagers_time | TIMESTAMP | 投注時間 | | grp_id | VARCHAR(16) | 局號ID | | date | DATE | 投注日期 | | coefficient | SMALLINT(5) | 得分倍數 | gamepool 遊戲水池數據列表 | 欄位名稱 | 資料型態 | 說明 | | -------- | -------- | -------- | | user_id | VARCHAR(20) | 玩家ID | | max_bet | BIGINT(20) | 水池 Max Bet | | arms_values | DECIMAL(18,2) | 特殊武器水池值 | | normal_values | DECIMAL(18,2) | 一般水池值 | gameuser 遊戲玩家數據列表 | 欄位名稱 | 資料型態 | 說明 | | -------- | -------- | -------- | | id | VARCHAR(20) | 玩家ID | | total_bet_amount | VARCHAR(20) | 總投注 | | total_payoff | VARCHAR(20) | 總收穫 | | enabled | VARCHAR(20) | 啟用 | | update_date_time | TIMESTAMP | | | create_date_time | TIMESTAMP | |