├── backend ├── logs │ └── .keep ├── stop_all.sh ├── config │ ├── config.cluster │ ├── example │ │ ├── config.cluster │ │ ├── config.redis │ │ ├── config.mysql │ │ ├── config.nodes │ │ └── yuncheng.cfg │ ├── config.redis │ ├── config.mysql │ ├── config.nodes │ ├── yuncheng.cfg │ └── rocket.cfg ├── protos │ ├── CGGame.pb │ ├── Rocket.pb │ ├── YunCheng.pb │ ├── Makefile │ ├── Rocket.proto │ ├── YunCheng.proto │ └── CGGame.proto ├── start_all.sh ├── client │ ├── start.sh │ ├── WaitList.lua │ ├── main.lua │ ├── config.lua │ ├── Delegate.lua │ ├── AuthUtils.lua │ ├── RemoteSocket.lua │ └── LoginHelper.lua ├── AgentServer │ ├── start.sh │ ├── main.lua │ ├── config.lua │ ├── TcpAgent.lua │ ├── WebAgent.lua │ └── WatchDog.lua ├── MainServer │ ├── start.sh │ ├── main.lua │ ├── config.lua │ ├── DBService.lua │ ├── MySQLService.lua │ └── RedisService.lua ├── managers │ ├── nodeoff.lua │ ├── gateoff.lua │ ├── hall_stat.lua │ └── off_notice.lua ├── HallServer │ ├── start.sh │ ├── config.lua │ ├── main.lua │ ├── BotAgent.lua │ └── HallService.lua ├── Helpers │ ├── FilterHelper.lua │ ├── TaskHelper.lua │ ├── DebugHelper.lua │ ├── StringHelper.lua │ ├── PacketHelper.lua │ ├── TableHelper.lua │ ├── DBHelper.lua │ └── ClusterHelper.lua ├── Stages │ ├── GameServer.lua │ ├── Const_Rocket.lua │ ├── Mode_Rocket.lua │ └── StageInterface.lua ├── Services │ ├── NodeStat.lua │ ├── NodeLink.lua │ ├── NodeInfo.lua │ └── ProtoTypes.lua ├── Algos │ ├── Queue.lua │ ├── Box.lua │ ├── SeatArray.lua │ ├── NumArray.lua │ ├── NumSet.lua │ ├── QuadTree.lua │ └── PriorityQueue.lua └── Classes │ └── Const_YunCheng.lua ├── .gitignore ├── .gitmodules ├── make.sh ├── README.md └── DB_CGGame.sql /backend/logs/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/stop_all.sh: -------------------------------------------------------------------------------- 1 | pkill -9 skynet 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.tmp 3 | tmp 4 | .DS_Store 5 | 6 | -------------------------------------------------------------------------------- /backend/config/config.cluster: -------------------------------------------------------------------------------- 1 | __nowaiting = true 2 | 3 | -------------------------------------------------------------------------------- /backend/config/example/config.cluster: -------------------------------------------------------------------------------- 1 | __nowaiting = true 2 | 3 | -------------------------------------------------------------------------------- /backend/protos/CGGame.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhouWeikuan/kuanli_server/HEAD/backend/protos/CGGame.pb -------------------------------------------------------------------------------- /backend/protos/Rocket.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhouWeikuan/kuanli_server/HEAD/backend/protos/Rocket.pb -------------------------------------------------------------------------------- /backend/protos/YunCheng.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhouWeikuan/kuanli_server/HEAD/backend/protos/YunCheng.pb -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "skynet"] 2 | path = skynet 3 | url = https://github.com/ZhouWeikuan/skynet 4 | [submodule "pbc"] 5 | path = pbc 6 | url = https://github.com/ZhouWeikuan/pbc 7 | -------------------------------------------------------------------------------- /backend/config/config.redis: -------------------------------------------------------------------------------- 1 | ---! sentinel config 2 | hosts = { 3 | "127.0.0.1", 4 | "127.0.0.1", 5 | "127.0.0.1", 6 | } 7 | 8 | port = 26379 9 | expire = 8 * 60 * 60 10 | 11 | -------------------------------------------------------------------------------- /backend/start_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sh MainServer/start.sh node3 1 & 4 | sleep 3 5 | sh AgentServer/start.sh node1 1 & 6 | sleep 3 7 | sh HallServer/start.sh node2 1 ./config/yuncheng.cfg & 8 | -------------------------------------------------------------------------------- /backend/config/example/config.redis: -------------------------------------------------------------------------------- 1 | ---! sentinel config 2 | hosts = { 3 | "192.168.0.121", 4 | "192.168.0.122", 5 | "192.168.0.123", 6 | } 7 | 8 | port = 26379 9 | expire = 8 * 60 * 60 10 | 11 | -------------------------------------------------------------------------------- /backend/config/config.mysql: -------------------------------------------------------------------------------- 1 | DB_Conf = { 2 | host="127.0.0.1", 3 | port=3306, 4 | database="DB_CGGame", 5 | user="root", 6 | password="123456", 7 | max_packet_size = 1024 * 1024, 8 | } 9 | 10 | Hosts = { 11 | "127.0.0.1", 12 | "127.0.0.1", 13 | "127.0.0.1", 14 | } 15 | 16 | -------------------------------------------------------------------------------- /backend/config/example/config.mysql: -------------------------------------------------------------------------------- 1 | DB_Conf = { 2 | host="127.0.0.1", 3 | port=3306, 4 | database="DB_CGGame", 5 | user="jerry", 6 | password="123456", 7 | max_packet_size = 1024 * 1024, 8 | } 9 | 10 | Hosts = { 11 | "192.168.0.121", 12 | "192.168.0.122", 13 | "192.168.0.123", 14 | } 15 | 16 | -------------------------------------------------------------------------------- /backend/client/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### example 4 | ### sh client/start.sh 1 5 | 6 | export ClientNo=$1 7 | 8 | if [[ "x$ClientNo" == "x" ]] 9 | then 10 | echo "You must set ClientNo" 11 | exit 12 | fi 13 | 14 | echo "start client/start.sh $ClientNo " 15 | 16 | ../skynet/skynet client/config.lua $ClientNo 17 | 18 | -------------------------------------------------------------------------------- /backend/protos/Makefile: -------------------------------------------------------------------------------- 1 | PROTO_PATH=. 2 | 3 | .PHONY : all clean 4 | 5 | # PROTOS = CGGame Jinhua NiuNiu Tractor Landlord Guandan Battleship BlackJack Holdem \ 6 | Ludo Xiangqi Chess Gomoku CNChecker Mahjong13 Mahjong2 AnQing YunCheng \ 7 | Snake CellGame Rocket 8 | 9 | PROTOS = CGGame YunCheng \ 10 | Rocket 11 | 12 | all : $(foreach v, $(PROTOS), $(v).pb) 13 | 14 | %pb:%proto 15 | @rm -f $@ 16 | @echo "generate $@" 17 | @protoc -I$(PROTO_PATH) -o $(PROTO_PATH)/$@ $(PROTO_PATH)/$< 18 | 19 | clean : 20 | @echo "clean all pb files" 21 | @rm -f *.pb 22 | -------------------------------------------------------------------------------- /backend/config/example/config.nodes: -------------------------------------------------------------------------------- 1 | AgentServer = { 2 | debugPort = 8000, 3 | nodePort = 8100, 4 | tcpPort = 8200, 5 | webPort = 8300, 6 | maxIndex = 1, 7 | } 8 | 9 | MainServer = { 10 | debugPort = 8500, 11 | nodePort = 8550, 12 | maxIndex = 1, 13 | } 14 | 15 | HallServer = { 16 | debugPort = 9000, 17 | nodePort = 9500, 18 | maxIndex = 5, 19 | } 20 | 21 | MySite = { 22 | -- private addrs, public addrs 23 | node1 = {"192.168.0.121", "192.168.0.121"}, 24 | node2 = {"192.168.0.122", "192.168.0.122"}, 25 | node3 = {"192.168.0.123", "192.168.0.123"}, 26 | } 27 | 28 | -------------------------------------------------------------------------------- /backend/AgentServer/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### example 4 | ### sh AgentServer/start.sh node1 1 5 | 6 | ## NodeName = node1 7 | ## ServerNo = 1 8 | 9 | ### so we can use getenv in skynet 10 | export NodeName=$1 11 | export ServerNo=$2 12 | 13 | ### check valid for NodeName, ServerNo 14 | if [[ "x$NodeName" == "x" || "x$ServerNo" == "x" ]] 15 | then 16 | echo "You must set NodeName and ServerNo" 17 | exit 18 | fi 19 | 20 | echo "NodeName = $NodeName, ServerKind = AgentServer, ServerNo = $ServerNo" 21 | 22 | ### so we can distinguish different skynet processes 23 | ../skynet/skynet AgentServer/config.lua $NodeName $ServerNo 24 | 25 | -------------------------------------------------------------------------------- /backend/MainServer/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### example 4 | ### sh MainServer/start.sh node1 1 5 | 6 | ## NodeName = node1 7 | ## ServerNo = 1 8 | 9 | ### so we can use getenv in skynet 10 | export NodeName=$1 11 | export ServerNo=$2 12 | 13 | ### check valid for NodeName, ServerNo 14 | if [[ "x$NodeName" == "x" || "x$ServerNo" == "x" ]] 15 | then 16 | echo "You must set NodeName and ServerNo" 17 | exit 18 | fi 19 | 20 | echo "NodeName = $NodeName, ServerKind = MainServer, ServerNo = $ServerNo" 21 | 22 | ### so we can distinguish different skynet processes 23 | ../skynet/skynet MainServer/config.lua $NodeName $ServerNo 24 | 25 | -------------------------------------------------------------------------------- /backend/managers/nodeoff.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------ 2 | ---! @file 3 | ---! @brief 通知NodeLink服务退出, 4 | ---! 所有监控NodeLink的都可以收到这个信号 5 | ------------------------------------------------------ 6 | 7 | ---! 依赖库 8 | local skynet = require "skynet" 9 | 10 | ---! 服务的启动函数 11 | skynet.start(function() 12 | ---! 初始化随机数 13 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 14 | 15 | ---! 通知NodeInfo, 退出NodeLink 16 | local NodeInfo = skynet.uniqueservice("NodeInfo") 17 | skynet.call(NodeInfo, "lua", "nodeOff") 18 | 19 | ---! 启动好了,没事做就退出 20 | skynet.exit() 21 | end) 22 | 23 | -------------------------------------------------------------------------------- /backend/config/config.nodes: -------------------------------------------------------------------------------- 1 | -- AgentServer: maxIndex 99 for each node [0, 99] 2 | AgentServer = { 3 | debugPort = 8000, 4 | nodePort = 8100, 5 | tcpPort = 8200, 6 | webPort = 8300, 7 | maxIndex = 1, 8 | } 9 | 10 | -- MainServer: maxIndex 49 for each node [0, 49] 11 | MainServer = { 12 | debugPort = 8500, 13 | nodePort = 8550, 14 | maxIndex = 1, 15 | } 16 | 17 | -- HallServer: maxIndex 499 for each node [0, 499] 18 | HallServer = { 19 | debugPort = 9000, 20 | nodePort = 9500, 21 | maxIndex = 5, 22 | } 23 | 24 | MySite = { 25 | -- private addrs, public addrs 26 | node1 = {"127.0.0.1", "127.0.0.1"}, 27 | node2 = {"127.0.0.1", "127.0.0.1"}, 28 | node3 = {"127.0.0.1", "127.0.0.1"}, 29 | } 30 | 31 | -------------------------------------------------------------------------------- /backend/HallServer/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### example 4 | ### sh HallServer/start.sh node1 1 config/game1.cfg 5 | 6 | ## NodeName = node1 7 | ## ServerNo = 1 8 | ## HallConfig = "game.cfg" 9 | 10 | ### so we can use getenv in skynet 11 | export NodeName=$1 12 | export ServerNo=$2 13 | export HallConfig=$3 14 | 15 | ### check valid for NodeName, ServerNo, HallConfig 16 | if [[ "x$NodeName" == "x" || "x$ServerNo" == "x" || "x$HallConfig" == "x" ]] 17 | then 18 | echo "You must set NodeName, ServerNo and HallConfig" 19 | exit 20 | fi 21 | 22 | echo "NodeName = $NodeName, ServerName = HallServer, ServerNo = $ServerNo, HallConfig=$HallConfig" 23 | 24 | ### so we can distinguish different skynet processes 25 | ../skynet/skynet HallServer/config.lua $NodeName $ServerNo $HallConfig 26 | 27 | -------------------------------------------------------------------------------- /backend/client/WaitList.lua: -------------------------------------------------------------------------------- 1 | local skynet = skynet or require "skynet" 2 | 3 | local class = {mt = {}} 4 | class.mt.__index = class 5 | 6 | class.create = function () 7 | local self = {} 8 | setmetatable(self, class.mt) 9 | self.pause_list = {} 10 | 11 | return self 12 | end 13 | 14 | class.resume = function (self) 15 | local co = table.remove(self.pause_list) 16 | if not co then 17 | return 18 | end 19 | 20 | if skynet.init then 21 | skynet.wakeup(co) 22 | else 23 | coroutine.resume(co) 24 | end 25 | end 26 | 27 | class.pause = function (self) 28 | local co = coroutine.running() 29 | table.insert(self.pause_list, co) 30 | 31 | if skynet.init then 32 | skynet.wait(co) 33 | else 34 | coroutine.yield() 35 | end 36 | end 37 | 38 | return class 39 | 40 | -------------------------------------------------------------------------------- /backend/client/main.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------ 2 | ---! @file 3 | ---! @brief AgentServer的启动文件 4 | ------------------------------------------------------ 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | local cluster = require "skynet.cluster" 9 | 10 | ---! helper class 11 | local clsHelper = require "ClusterHelper" 12 | local Delegate = require "Delegate" 13 | 14 | local delegate = nil 15 | 16 | local function main_loop () 17 | delegate:stage_login() 18 | end 19 | 20 | local function tickFrame () 21 | while true do 22 | delegate:tickFrame() 23 | skynet.sleep(10) 24 | end 25 | end 26 | 27 | ---! 服务的启动函数 28 | skynet.start(function() 29 | ---! 初始化随机数 30 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 31 | 32 | delegate = Delegate.create() 33 | 34 | skynet.fork(tickFrame) 35 | skynet.fork(main_loop) 36 | end) 37 | 38 | -------------------------------------------------------------------------------- /backend/managers/gateoff.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------ 2 | ---! @file 3 | ---! @brief InfoServer的启动文件 4 | ------------------------------------------------------ 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | 9 | local function gate_off () 10 | local watchdog = skynet.uniqueservice("WatchDog") 11 | skynet.send(watchdog, "lua", "gateOff") 12 | end 13 | 14 | ---! 服务的启动函数 15 | skynet.start(function() 16 | ---! 初始化随机数 17 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 18 | 19 | local srv = skynet.uniqueservice("NodeInfo") 20 | local kind = skynet.call(srv, "lua", "getConfig", "nodeInfo", "serverKind") 21 | 22 | if kind == "AgentServer" then 23 | gate_off() 24 | else 25 | print("gateoff should not run in server kind: ", kind) 26 | end 27 | 28 | skynet.sleep(20) 29 | 30 | -- 启动好了,没事做就退出 31 | skynet.exit() 32 | end) 33 | 34 | -------------------------------------------------------------------------------- /backend/managers/hall_stat.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------ 2 | ---! @file 3 | ---! @brief InfoServer的启动文件 4 | ------------------------------------------------------ 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | 9 | 10 | local function log_hall_stat () 11 | local hall = skynet.uniqueservice("HallService") 12 | skynet.send(hall, "lua", "logStat") 13 | end 14 | 15 | ---! 服务的启动函数 16 | skynet.start(function() 17 | ---! 初始化随机数 18 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 19 | 20 | local srv = skynet.uniqueservice("NodeInfo") 21 | local kind = skynet.call(srv, "lua", "getConfig", "nodeInfo", "serverKind") 22 | 23 | if kind == "HallServer" then 24 | log_hall_stat() 25 | else 26 | print("hall_stat should not run in server kind: ", kind) 27 | end 28 | 29 | skynet.sleep(20) 30 | 31 | -- 启动好了,没事做就退出 32 | skynet.exit() 33 | end) 34 | 35 | -------------------------------------------------------------------------------- /backend/config/yuncheng.cfg: -------------------------------------------------------------------------------- 1 | ---! action game is 3xxx, card game is 2xxx, board game is 1xxx 2 | GameId = 2013 3 | Version = "20180901" 4 | LowestVersion = "20180901" 5 | HallName = "运城斗地主" 6 | Low = 400 -- enable if lower than this 7 | High = 800 -- disable if higher than this 8 | MaxConnections = 1000 9 | GameMode = 1 -- 0, test, 1 newbie, 2 middle, 3 senior 10 | 11 | ---! the following are for room interface 12 | Interface = "RoomInterface" 13 | DBTableName = "TYunChengUser" 14 | 15 | JoinPlaying = false -- allow to join playing game, default join wait_start game 16 | AutoReady = false 17 | AutoConfirm = false 18 | 19 | ---! the following are config for game class 20 | GameClass = "Table_YunCheng" 21 | MinPlayer = 3 22 | MaxPlayer = 3 23 | BestPlayer = 3 24 | BotName = "BotPlayer_YunCheng" 25 | BotNum = 5 26 | 27 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | dir=$(cd `dirname $0`; pwd) 4 | echo $dir 5 | echo $1 6 | 7 | if [ ! $1 ]; then 8 | cmd='help' 9 | else 10 | cmd=$1 11 | fi 12 | 13 | if [ $cmd = "all" ];then 14 | git submodule update --init --recursive 15 | 16 | echo -e "\n make pbc" 17 | cd $dir/pbc && pwd 18 | make all 19 | echo -e "\n make pbc binding" 20 | cd $dir/pbc/binding/lua53 && pwd 21 | make all 22 | 23 | echo -e "\n make skynet" 24 | cd $dir/skynet && pwd 25 | make linux 26 | 27 | elif [ $cmd = "clean" ];then 28 | echo -e "\n clean skynet" 29 | cd $dir/skynet && pwd 30 | make clean 31 | 32 | echo -e "\n clean pbc" 33 | cd $dir/pbc && pwd 34 | make clean 35 | 36 | echo -e "\n clean pbc binding" 37 | cd $dir/pbc/binding/lua53 && pwd 38 | make clean 39 | else 40 | echo "make help" 41 | echo "make.sh all" 42 | echo "make.sh clean" 43 | fi 44 | 45 | -------------------------------------------------------------------------------- /backend/config/example/yuncheng.cfg: -------------------------------------------------------------------------------- 1 | ---! action game is 3xxx, card game is 2xxx, board game is 1xxx 2 | GameId = 2013 3 | Version = "20180901" 4 | LowestVersion = "20180901" 5 | HallName = "运城斗地主" 6 | Low = 400 -- enable if lower than this 7 | High = 800 -- disable if higher than this 8 | MaxConnections = 1000 9 | GameMode = 1 -- 0, test, 1 newbie, 2 middle, 3 senior 10 | 11 | ---! the following are for room interface 12 | Interface = "RoomInterface" 13 | DBTableName = "TYunChengUser" 14 | 15 | JoinPlaying = false -- allow to join playing game, default join wait_start game 16 | AutoReady = false 17 | AutoConfirm = false 18 | 19 | ---! the following are config for game class 20 | GameClass = "Table_YunCheng" 21 | MinPlayer = 3 22 | MaxPlayer = 3 23 | BestPlayer = 3 24 | BotName = "BotPlayer_YunCheng" 25 | BotNum = 5 26 | 27 | -------------------------------------------------------------------------------- /backend/Helpers/FilterHelper.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------- 2 | ---! @file 3 | ---! @brief 对table进行过滤或者判断 4 | --------------------------------------------------- 5 | 6 | ---! FilterHelper 模块定义 7 | local class = {} 8 | 9 | ---! @brief 以k-v的方式 过滤集合 10 | ---! @return 返回所有合适的k-v表 11 | local function filterSet (set, filterFunc) 12 | local ret = {} 13 | for k, v in pairs(set) do 14 | if filterFunc(k, v) then 15 | ret[k] = v 16 | end 17 | end 18 | return ret 19 | end 20 | class.filterSet = filterSet 21 | 22 | ---! @brief 过滤数组里的元素 23 | ---! @return 返回所有符合要求的元素数组 24 | local function filterArray(array, filterFunc) 25 | local ret = {} 26 | for k, v in ipairs(array) do 27 | if filterFunc(v) then 28 | table.insert(ret, v) 29 | end 30 | end 31 | return ret 32 | end 33 | class.filterArray = filterArray 34 | 35 | ---! @brief 判断元素是否在数组里 36 | local function isElementInArray (ele, arr) 37 | if arr == nil then 38 | return false 39 | end 40 | 41 | for k,v in pairs(arr) do 42 | if ele == v then 43 | return true 44 | end 45 | end 46 | return false 47 | end 48 | class.isElementInArray = isElementInArray 49 | 50 | return class 51 | 52 | -------------------------------------------------------------------------------- /backend/Stages/GameServer.lua: -------------------------------------------------------------------------------- 1 | local skynet = skynet or require "skynet" 2 | -- use skynet.init to determine server or client 3 | 4 | local protoTypes = require "ProtoTypes" 5 | local packetHelper = (require "PacketHelper").create("protos/CGGame.pb") 6 | 7 | local class = {mt = {}} 8 | class.mt.__index = class 9 | 10 | class.create = function (hallInterface) 11 | local self = {} 12 | setmetatable(self, class.mt) 13 | 14 | self.hallInterface = hallInterface 15 | self.config = hallInterface.config 16 | 17 | return self 18 | end 19 | 20 | ---! @brief 发送给玩家的游戏数据 21 | ---! @brief 发送游戏数据到用户 22 | ---! @param uid 用户Id 23 | ---! @param subType 数据类型 24 | ---! @param data 数据内容 25 | class.SendGameDataToUser = function (self, code, subType, data) 26 | if not subType or subType == 0 then 27 | print(debug.traceback()) 28 | end 29 | local packet = packetHelper:makeProtoData(protoTypes.CGGAME_PROTO_MAINTYPE_GAME, subType, data); 30 | self.hallInterface:gamePacketToUser(packet, code) 31 | end 32 | 33 | ---!@brief TODO 注释函数作用 34 | ---!@param userInfo 用户信息 35 | ---!@param data TODO 注释data意义 36 | class.handleGameData = function (self, userInfo, gameType, data) 37 | print ("Unknown game data, subType = ", gameType) 38 | end 39 | 40 | return class 41 | 42 | -------------------------------------------------------------------------------- /backend/Helpers/TaskHelper.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------- 2 | ---! @file 3 | ---! @brief 任务处理辅助 4 | --------------------------------------------------- 5 | 6 | ---! 依赖库 skynet 7 | local skynet = require "skynet" 8 | local queue = require "skynet.queue" 9 | 10 | ---! 顺序序列 11 | local critical = nil 12 | 13 | ---! TaskHelper 模块定义 14 | local class = {} 15 | 16 | ---! @brief 执行异步任务worker, 如果有callback,对返回值进行callback(ret)处理 17 | ---! @param worker 异步的任务 18 | ---! @param callback 任务完成时的回调 19 | ---! @note skynet服务可能会阻塞,尽量不要改变状态 20 | local function async_task(worker, callback) 21 | skynet.fork(function() 22 | local ret = worker() 23 | if callback then 24 | callback(ret) 25 | end 26 | end) 27 | end 28 | class.async_task = async_task 29 | 30 | ---! @brief 把worker加入执行序列 按顺序执行 31 | ---! @brief worker 需要执行的任务序列 一般是一个函数 32 | local function queue_task(worker) 33 | if not critical then 34 | critical = queue() 35 | end 36 | critical(worker) 37 | end 38 | class.queue_task = queue_task 39 | 40 | ---! @brief close a agent gate's socket 41 | ---! @param agent client's agent gate 42 | ---! @sock socket 43 | local function closeGateAgent(gate, sock) 44 | skynet.call(gate, "lua", "kick", sock) 45 | end 46 | class.closeGateAgent = closeGateAgent 47 | 48 | 49 | return class 50 | 51 | -------------------------------------------------------------------------------- /backend/MainServer/main.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------ 2 | ---! @file 3 | ---! @brief MainServer的启动文件 4 | ------------------------------------------------------ 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | local cluster = require "skynet.cluster" 9 | 10 | ---! 服务的启动函数 11 | skynet.start(function() 12 | ---! 初始化随机数 13 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 14 | 15 | local srv = skynet.uniqueservice("NodeInfo") 16 | skynet.call(srv, "lua", "initNode") 17 | 18 | ---! 启动debug_console服务 19 | local port = skynet.call(srv, "lua", "getConfig", "nodeInfo", "debugPort") 20 | assert(port >= 0) 21 | print("debug port is", port) 22 | skynet.newservice("debug_console", port) 23 | 24 | ---! 集群处理 25 | local list = skynet.call(srv, "lua", "getConfig", "clusterList") 26 | list["__nowaiting"] = true 27 | cluster.reload(list) 28 | 29 | local appName = skynet.call(srv, "lua", "getConfig", "nodeInfo", "appName") 30 | cluster.open(appName) 31 | 32 | ---! 启动 info :d 节点状态信息 服务 33 | skynet.uniqueservice("NodeStat") 34 | 35 | ---! 启动 NodeLink 服务 36 | skynet.newservice("NodeLink") 37 | 38 | ---! 启动 MainInfo 服务 39 | skynet.uniqueservice("MainInfo") 40 | 41 | ---! 启动用户信息的数据库服务 42 | skynet.newservice("DBService") 43 | 44 | 45 | ---! 完成初始化,退出本服务 46 | skynet.exit() 47 | end) 48 | 49 | -------------------------------------------------------------------------------- /backend/AgentServer/main.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------ 2 | ---! @file 3 | ---! @brief AgentServer的启动文件 4 | ------------------------------------------------------ 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | local cluster = require "skynet.cluster" 9 | 10 | ---! helper class 11 | local clsHelper = require "ClusterHelper" 12 | 13 | 14 | ---! 服务的启动函数 15 | skynet.start(function() 16 | ---! 初始化随机数 17 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 18 | 19 | ---! 启动NodeInfo 20 | local srv = skynet.uniqueservice("NodeInfo") 21 | skynet.call(srv, "lua", "initNode") 22 | 23 | ---! 启动debug_console服务 24 | local port = skynet.call(srv, "lua", "getConfig", "nodeInfo", "debugPort") 25 | assert(port >= 0) 26 | print("debug port is", port) 27 | skynet.newservice("debug_console", port) 28 | 29 | ---! 集群处理 30 | local list = skynet.call(srv, "lua", "getConfig", "clusterList") 31 | list["__nowaiting"] = true 32 | cluster.reload(list) 33 | 34 | local appName = skynet.call(srv, "lua", "getConfig", "nodeInfo", "appName") 35 | cluster.open(appName) 36 | 37 | ---! 启动 info :d 节点状态信息 服务 38 | skynet.uniqueservice("NodeStat") 39 | 40 | ---! 启动AgentWatch 41 | skynet.uniqueservice("WatchDog") 42 | 43 | ---! 启动 NodeLink 服务 44 | skynet.newservice("NodeLink") 45 | 46 | ---! 启动好了,没事做就退出 47 | skynet.exit() 48 | end) 49 | 50 | -------------------------------------------------------------------------------- /backend/client/config.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------- 2 | ---! @file 3 | ---! @brief client 的启动配置文件 4 | ---------------------------------- 5 | local _root = "./" 6 | local _skynet = _root.."../skynet/" 7 | 8 | ---------------------------------- 9 | ---! 自定义参数 10 | ---------------------------------- 11 | ClientNo = "$ClientNo" 12 | app_name = "client" .. ClientNo 13 | app_root = _root.. "client" .."/" 14 | 15 | ---------------------------------- 16 | ---! skynet用到的六个参数 17 | ---------------------------------- 18 | ---! 工作线程数 19 | thread = 4 20 | ---! 服务模块路径(.so) 21 | cpath = _skynet.."cservice/?.so" 22 | ---! 港湾ID,用于分布式系统,0表示没有分布 23 | harbor = 0 24 | ---! 后台运行用到的 pid 文件 25 | daemon = nil 26 | ---! 日志文件 27 | -- logger = nil 28 | logger = _root .. "/logs/" .. app_name .. ".log" 29 | ---! 初始启动的模块 30 | bootstrap = "snlua bootstrap" 31 | 32 | ---! snlua用到的参数 33 | lua_path = _skynet.."lualib/?.lua;"..app_root.."?.lua;".._root.."Algos/?.lua;".._root.."Helpers/?.lua;".._root.."Services/?.lua;".._root.."Classes/?.lua;".._root.."Stages/?.lua" 34 | lua_cpath = _skynet.."luaclib/?.so;"..app_root.."cservices/?.so" 35 | luaservice = _skynet.."service/?.lua;".. app_root .. "?.lua;" .._root.."Services/?.lua;".._root.."managers/?.lua" 36 | lualoader = _skynet.."lualib/loader.lua" 37 | start = "main" 38 | 39 | ---! snax用到的参数 40 | snax = _skynet.."service/?.lua;".. app_root .. "?.lua;" .._root.."Services/?.lua" 41 | 42 | -------------------------------------------------------------------------------- /backend/managers/off_notice.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------ 2 | ---! @file 3 | ---! @brief InfoServer的启动文件 4 | ------------------------------------------------------ 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | 9 | local function off_notice (target) 10 | local packetHelper = (require "PacketHelper").create("protos/CGGame.pb") 11 | local protoTypes = require "ProtoTypes" 12 | 13 | local aclInfo = { 14 | aclType = protoTypes.CGGAME_ACL_STATUS_NODE_OFF, 15 | } 16 | local data = packetHelper:encodeMsg("CGGame.AclInfo", aclInfo) 17 | local packet = packetHelper:makeProtoData(protoTypes.CGGAME_PROTO_MAINTYPE_BASIC, 18 | protoTypes.CGGAME_PROTO_SUBTYPE_ACL, data) 19 | 20 | skynet.call(target, "lua", "noticeAll", packet) 21 | end 22 | 23 | ---! 服务的启动函数 24 | skynet.start(function() 25 | ---! 初始化随机数 26 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 27 | 28 | local srv = skynet.uniqueservice("NodeInfo") 29 | local kind = skynet.call(srv, "lua", "getConfig", "nodeInfo", "serverKind") 30 | 31 | local target = nil 32 | if kind == "AgentServer" then 33 | target = skynet.uniqueservice("WatchDog") 34 | elseif kind == "HallServer" then 35 | target = skynet.uniqueservice("HallService") 36 | else 37 | print("off_notice should not run in server kind: ", kind) 38 | end 39 | 40 | if target then 41 | off_notice(target) 42 | end 43 | 44 | skynet.sleep(20) 45 | 46 | -- 启动好了,没事做就退出 47 | skynet.exit() 48 | end) 49 | 50 | -------------------------------------------------------------------------------- /backend/MainServer/config.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------- 2 | ---! @file 3 | ---! @brief MainServer的启动配置文件 4 | ---------------------------------- 5 | local _root = "./" 6 | local _skynet = _root.."../skynet/" 7 | 8 | ---! MainServer 用到的参数 从 命令行传的参数 9 | NodeName = "$NodeName" 10 | ServerKind = "MainServer" 11 | ServerNo = "$ServerNo" 12 | 13 | ---------------------------------- 14 | ---! 自定义参数 15 | ---------------------------------- 16 | app_name = NodeName .. "_" .. ServerKind .. ServerNo 17 | app_root = _root.. ServerKind .."/" 18 | 19 | ---------------------------------- 20 | ---! skynet用到的六个参数 21 | ---------------------------------- 22 | ---! 工作线程数 23 | thread = 4 24 | ---! 服务模块路径(.so) 25 | cpath = _skynet.."cservice/?.so" 26 | ---! 港湾ID,用于分布式系统,0表示没有分布 27 | harbor = 0 28 | ---! 后台运行用到的 pid 文件 29 | daemon = nil 30 | ---! 日志文件 31 | -- logger = nil 32 | logger = _root .. "/logs/" .. app_name .. ".log" 33 | ---! 初始启动的模块 34 | bootstrap = "snlua bootstrap" 35 | 36 | ---! snlua用到的参数 37 | lua_path = _skynet.."lualib/?.lua;"..app_root.."?.lua;".._root .."Algos/?.lua;".._root.."Helpers/?.lua" 38 | lua_cpath = _skynet.."luaclib/?.so;"..app_root.."cservices/?.so" 39 | luaservice = _skynet.."service/?.lua;".. app_root .. "?.lua;" .._root.."Services/?.lua;".._root.."managers/?.lua" 40 | lualoader = _skynet.."lualib/loader.lua" 41 | start = "main" 42 | 43 | ---! snax用到的参数 44 | snax = _skynet.."service/?.lua;".. app_root .. "?.lua;" .._root.."Services/?.lua" 45 | 46 | ---! cluster 用到的参数 47 | cluster = app_root.."../config/config.cluster" 48 | 49 | -------------------------------------------------------------------------------- /backend/AgentServer/config.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------- 2 | ---! @file 3 | ---! @brief AgentServer的启动配置文件 4 | ---------------------------------- 5 | local _root = "./" 6 | local _skynet = _root.."../skynet/" 7 | 8 | ---! AgentServer 用到的参数 从 命令行传的参数 9 | NodeName = "$NodeName" 10 | ServerKind = "AgentServer" 11 | ServerNo = "$ServerNo" 12 | 13 | ---------------------------------- 14 | ---! 自定义参数 15 | ---------------------------------- 16 | app_name = NodeName .. "_" .. ServerKind .. ServerNo 17 | app_root = _root.. ServerKind .."/" 18 | 19 | ---------------------------------- 20 | ---! skynet用到的六个参数 21 | ---------------------------------- 22 | ---! 工作线程数 23 | thread = 4 24 | ---! 服务模块路径(.so) 25 | cpath = _skynet.."cservice/?.so" 26 | ---! 港湾ID,用于分布式系统,0表示没有分布 27 | harbor = 0 28 | ---! 后台运行用到的 pid 文件 29 | daemon = nil 30 | ---! 日志文件 31 | -- logger = nil 32 | logger = _root .. "/logs/" .. app_name .. ".log" 33 | ---! 初始启动的模块 34 | bootstrap = "snlua bootstrap" 35 | 36 | ---! snlua用到的参数 37 | lua_path = _skynet.."lualib/?.lua;"..app_root.."?.lua;".._root .."Algos/?.lua;".._root.."Helpers/?.lua;".._root.."Services/?.lua" 38 | lua_cpath = _skynet.."luaclib/?.so;"..app_root.."cservices/?.so" 39 | luaservice = _skynet.."service/?.lua;".. app_root .. "?.lua;" .._root.."Services/?.lua;".._root.."managers/?.lua" 40 | lualoader = _skynet.."lualib/loader.lua" 41 | start = "main" 42 | 43 | ---! snax用到的参数 44 | snax = _skynet.."service/?.lua;".. app_root .. "?.lua;" .._root.."Services/?.lua" 45 | 46 | ---! cluster 用到的参数 47 | cluster = app_root.."../config/config.cluster" 48 | 49 | -------------------------------------------------------------------------------- /backend/HallServer/config.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------- 2 | ---! @file 3 | ---! @brief HallServer服务器的参数设置 4 | ---------------------------------- 5 | local _root = "./" 6 | local _skynet = _root.."../skynet/" 7 | 8 | ---! HallServer 用到的参数 从 命令行传的参数 9 | NodeName = "$NodeName" 10 | ServerKind = "HallServer" 11 | ServerNo = "$ServerNo" 12 | HallConfig = "$HallConfig" 13 | 14 | ---------------------------------- 15 | ---! 自定义参数 16 | ---------------------------------- 17 | app_name = NodeName .. "_" .. ServerKind .. ServerNo 18 | app_root = _root.. ServerKind .."/" 19 | 20 | ---------------------------------- 21 | ---! skynet用到的六个参数 22 | ---------------------------------- 23 | ---! 工作线程数 24 | thread = 4 25 | ---! 服务模块路径(.so) 26 | cpath = _skynet.."cservice/?.so" 27 | ---! 港湾ID,用于分布式系统,0表示没有分布 28 | harbor = 0 29 | ---! 后台运行用到的 pid 文件 30 | daemon = nil 31 | ---! 日志文件 32 | -- logger = nil 33 | logger = _root .. "/logs/" .. app_name .. ".log" 34 | ---! 初始启动的模块 35 | bootstrap = "snlua bootstrap" 36 | 37 | ---! snlua用到的参数 38 | lua_path = _skynet.."lualib/?.lua;"..app_root.."?.lua;".._root .."Algos/?.lua;".._root.."Classes/?.lua;".._root.."Helpers/?.lua;" .._root.."Stages/?.lua;".._root.."Services/?.lua" 39 | lua_cpath = _skynet.."luaclib/?.so;"..app_root.."cservices/?.so" 40 | luaservice = _skynet.."service/?.lua;".. app_root .. "?.lua;" .._root.."Services/?.lua;".._root.."managers/?.lua" 41 | lualoader = _skynet.."lualib/loader.lua" 42 | start = "main" 43 | 44 | ---! snax用到的参数 45 | snax = _skynet.."service/?.lua;".. app_root .. "?.lua;" .._root.."Services/?.lua" 46 | 47 | ---! cluster 用到的参数 48 | cluster = app_root.."../config/config.cluster" 49 | 50 | -------------------------------------------------------------------------------- /backend/Services/NodeStat.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------- 2 | ---! @file NodeStat.lua 3 | ---! @brief 调试当前节点,获取运行信息 4 | -------------------------------------------------------------- 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | 9 | ---! 帮助 10 | local clsHelper = require "ClusterHelper" 11 | local strHelper = require "StringHelper" 12 | 13 | ---! MainServer信息 14 | local function main_info () 15 | local srv = skynet.uniqueservice("MainInfo") 16 | return skynet.call(srv, "lua", "getStat") 17 | end 18 | 19 | ---! AgentServer信息 20 | local function agent_info (nodeInfo, watchdog) 21 | local stat = skynet.call(watchdog, "lua", "getStat") 22 | local arr = {nodeInfo.appName} 23 | table.insert(arr, string.format("Web: %d", stat.web)) 24 | table.insert(arr, string.format("Tcp: %d", stat.tcp)) 25 | table.insert(arr, string.format("总人数: %d", stat.sum)) 26 | return strHelper.join(arr, "\t") 27 | end 28 | 29 | ---! 显示节点信息 30 | local function dump_info() 31 | local srv = skynet.uniqueservice("NodeInfo") 32 | local nodeInfo = skynet.call(srv, "lua", "getConfig", "nodeInfo") 33 | if nodeInfo.serverKind == clsHelper.kMainServer then 34 | return main_info() 35 | end 36 | 37 | local watchdog = skynet.call(srv, "lua", "getServiceAddr", clsHelper.kWatchDog) 38 | if watchdog ~= "" then 39 | return agent_info(nodeInfo, watchdog) 40 | end 41 | 42 | ---! HallServer信息 43 | local arr = {nodeInfo.appName} 44 | local conf = skynet.call(srv, "lua", "getConfig", clsHelper.kHallConfig) 45 | if conf ~= "" then 46 | table.insert(arr, conf.HallName) 47 | table.insert(arr, string.format("gameId: %d", conf.GameId)) 48 | end 49 | 50 | table.insert(arr, string.format("num: %d", nodeInfo.numPlayers)) 51 | local ret = strHelper.join(arr, "\t") 52 | return ret 53 | end 54 | 55 | skynet.start(function() 56 | skynet.info_func(dump_info) 57 | end) 58 | 59 | -------------------------------------------------------------------------------- /backend/config/rocket.cfg: -------------------------------------------------------------------------------- 1 | ---! action game is 3xxx, card game is 2xxx, board game is 1xxx 2 | GameId = 3003 3 | Version = "20180901" 4 | LowestVersion = "20180901" 5 | HallName = "导弹大作战" 6 | Low = 120 -- enable if lower than this 7 | High = 200 -- disable if higher than this 8 | MaxConnections = 240 9 | GameMode = 1 -- -1, test, 1 ffa, 2 team?flag? 10 | 11 | --! the following are for room interface 12 | Interface = "StageInterface" 13 | DBTableName = "TRocketUser" 14 | 15 | TickInterval = 4 -- 0.08 16 | 17 | --! the following are stage configs 18 | --! border must be 400 multiple 19 | BorderLeft = 0 20 | BorderRight = 20000 21 | BorderBottom = 0 22 | BorderTop = 20000 23 | 24 | ViewBaseWidth = 1920 25 | ViewBaseHeight = 1080 26 | 27 | StuffConfig = { 28 | [1200] = { -- rocket 29 | MaxSpeed = 26, 30 | MaxForce = 2.4, 31 | }, 32 | [1201] = { -- rocket 33 | MaxSpeed = 28, 34 | MaxForce = 2.6, 35 | }, 36 | [1100] = { -- plane 37 | MaxSpeed = 20, 38 | MaxForce = 3.0, 39 | }, 40 | } 41 | 42 | 43 | --! the following are config for game class 44 | GameStage = "Server_Rocket" 45 | PlayerTracker = "PlayerTracker_Rocket" 46 | ServerBotName = "BotServer_Rocket" 47 | 48 | ServerBotNum = 28 49 | 50 | StuffData = { 51 | [1000] = { -- rocket 52 | spawnInterval = 20, 53 | spawnAmount = 10, 54 | startAmount = 40, 55 | maxAmount = 100, 56 | }, 57 | [1001] = { -- star 58 | spawnInterval = 20, 59 | spawnAmount = 5, 60 | startAmount = 100, 61 | maxAmount = 300, 62 | }, 63 | [1002] = { -- shield 64 | spawnInterval = 20, 65 | spawnAmount = 3, 66 | startAmount = 3, 67 | maxAmount = 10, 68 | }, 69 | [1003] = { -- speedup 70 | spawnInterval = 20, 71 | spawnAmount = 3, 72 | startAmount = 3, 73 | maxAmount = 10, 74 | }, 75 | } 76 | 77 | -------------------------------------------------------------------------------- /backend/Algos/Queue.lua: -------------------------------------------------------------------------------- 1 | 2 | --! define class for PriorityQueue 3 | local class = {mt = {}} 4 | --! define class for PriorityQueue 5 | local Queue = class 6 | --! define class for PriorityQueue 7 | class.mt.__index = class 8 | 9 | ---! @brief create Queue 10 | ---! @return return a self table 11 | local function create() 12 | local self = {} 13 | setmetatable(self, class.mt) 14 | 15 | self.first = 1 16 | self.last = 0 17 | 18 | return self 19 | end 20 | class.create = create 21 | 22 | ---! @brief The first element to determine the queue 23 | ---! @return The first element of the queue 24 | local function front(self) 25 | local first = self.first 26 | if first > self.last then 27 | return nil 28 | else 29 | return self[first] 30 | end 31 | end 32 | class.front = front 33 | 34 | ---! @brief Take the element of the queue 35 | ---! @return The first element of the queue 36 | local function popFront(self) 37 | local first = self.first 38 | if first>self.last then 39 | return nil 40 | end 41 | local value = self[first] 42 | self[first] = nil 43 | self.first = first+1 44 | return value 45 | end 46 | class.popFront = popFront 47 | 48 | ---! @brief Judge the last element of the queue 49 | ---! @return The last element of the queue 50 | local function back(self) 51 | local last = self.last 52 | if self.first > last then 53 | return nil 54 | else 55 | return self[last] 56 | end 57 | end 58 | class.back = back 59 | 60 | ---! @brief Put an element in the last position of the queue 61 | ---! @param element the element to pushed in 62 | local function pushBack(self, element) 63 | self.last = self.last + 1 64 | self[self.last] = element 65 | end 66 | class.pushBack = pushBack 67 | 68 | ---! @brief Queue length 69 | ---! @param Queue length 70 | local function count(self) 71 | if self.first>self.last then 72 | return 0 73 | end 74 | local count = self.last-self.first+1 75 | return count 76 | end 77 | class.count = count 78 | 79 | local function clear(self) 80 | for i=self.first,self.last do 81 | self[i] = nil 82 | end 83 | self.first = 1 84 | self.last = 0 85 | end 86 | class.clear = clear 87 | class.reset = clear 88 | 89 | 90 | return Queue 91 | 92 | -------------------------------------------------------------------------------- /backend/Helpers/DebugHelper.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------- 2 | ---! @file 3 | ---! @brief 调试辅助库 4 | --------------------------------------------------- 5 | 6 | local filterHelper = require "FilterHelper" 7 | 8 | ---! DebugHelper 模块定义 9 | local class = {} 10 | 11 | ---! @brief 日志打印函数 12 | local function cclog (...) 13 | local skynet = require "skynet" 14 | skynet.error(string.format(...)) 15 | end 16 | class.cclog = cclog 17 | 18 | ---! @brief 打印表格信息 19 | local function printDeepTable (_origin_info, curDepth, _space_count, _printed_info) 20 | if not _space_count then 21 | _space_count = 1 22 | end 23 | if not _origin_info then 24 | _origin_info = {} 25 | end 26 | 27 | if type(_origin_info) ~= "table" then 28 | class.cclog("%s is not a table", tostring(_origin_info)) 29 | return 30 | end 31 | 32 | if not _printed_info then 33 | _printed_info = {} 34 | end 35 | 36 | --防止存在互相嵌套的表而无限打印 37 | local listTable = {} 38 | for k,v in pairs(_printed_info) do 39 | listTable[k] = v 40 | end 41 | table.insert(listTable, _origin_info) 42 | --防止存在互相嵌套的表而无限打印 43 | 44 | local pre = "" 45 | for i = 1, _space_count - 1 do 46 | pre = pre .. " " 47 | end 48 | class.cclog(pre .. "{") 49 | 50 | if curDepth and curDepth < 1 then 51 | class.cclog(pre .." ****over depth****") 52 | else 53 | if curDepth then 54 | curDepth = curDepth - 1 55 | end 56 | for k,v in pairs(_origin_info) do 57 | if type(v) == "table" then 58 | if filterHelper.isElementInArray(v, listTable) then 59 | class.cclog(pre .. " " .. tostring(k) .. " = " .. "tableCache") 60 | else 61 | class.cclog(pre .. " " .. tostring(k) .. " = ") 62 | printDeepTable(v, curDepth, _space_count + 1, listTable) 63 | end 64 | else 65 | local str = pre .. " " 66 | str = str .. tostring(k) .. " = " .. tostring(v) 67 | class.cclog(str) 68 | end 69 | end 70 | end 71 | 72 | class.cclog(pre .. "}") 73 | end 74 | class.printDeepTable = printDeepTable 75 | 76 | 77 | return class 78 | 79 | -------------------------------------------------------------------------------- /backend/Algos/Box.lua: -------------------------------------------------------------------------------- 1 | ---! 实现一个碰撞矩形 2 | local class = {mt = {}} 3 | class.mt.__index = class 4 | 5 | ---! center point for (x, y) with half width and half height(default half width if not set) 6 | class.create = function(x, y, hw, hh) 7 | hh = hh or hw 8 | local self = { 9 | leftX = x - hw, 10 | bottomY = y - hh, 11 | rightX = x + hw, 12 | topY = y + hh, 13 | } 14 | 15 | setmetatable(self, class.mt) 16 | return self 17 | end 18 | 19 | ---! 上下左右线 20 | class.createWithBox = function(l, r, b, t) 21 | local x = (l + r)/2 22 | local y = (b + t)/2 23 | local w = (r - l)/2 24 | local h = (t - b)/2 25 | 26 | return class.create(x, y, w, h) 27 | end 28 | 29 | class.debug = function(self) 30 | local debugHelper = require "DebugHelper" 31 | local box = self 32 | debugHelper.cclog(" (%f, %f, %f, %f)", box.leftX, box.rightX, box.bottomY, box.topY) 33 | end 34 | 35 | ---! 中心点位置 36 | class.getCenter = function(self) 37 | local x = (self.leftX + self.rightX) / 2 38 | local y = (self.bottomY + self.topY) / 2 39 | return x, y 40 | end 41 | 42 | ---! 宽 高 43 | class.getSize = function (self) 44 | local w = (self.rightX - self.leftX) 45 | local h = (self.topY - self.bottomY) 46 | 47 | return w,h 48 | end 49 | 50 | ---! 拆分成4个小矩形 51 | class.getSplits = function(self) 52 | local boxes = {} 53 | local w,h = self:getSize() 54 | local x,y = self:getCenter() 55 | 56 | local hw, hh = w/4, h/4 57 | 58 | table.insert(boxes, class.create(x+hw, y+hh, hw, hh)) 59 | table.insert(boxes, class.create(x-hw, y+hh, hw, hh)) 60 | table.insert(boxes, class.create(x-hw, y-hh, hw, hh)) 61 | table.insert(boxes, class.create(x+hw, y-hh, hw, hh)) 62 | 63 | return boxes 64 | end 65 | 66 | ---! 相交测试 67 | class.intersect = function(self, other) 68 | return self.leftX < other.rightX and self.rightX > other.leftX and self.bottomY < other.topY and self.topY > other.bottomY 69 | end 70 | 71 | ---! 矩形 包含测试 72 | class.containsBox = function(self, other) 73 | return self.leftX <= other.leftX and self.rightX >= other.rightX and self.bottomY <= other.bottomY and self.topY >= other.topY 74 | end 75 | 76 | ---! 点包含测试 77 | class.containsPoint = function(self, x, y) 78 | return (x >= self.leftX and x <= self.rightX) and (y >= self.bottomY and y <= self.topY) 79 | end 80 | 81 | return class 82 | 83 | -------------------------------------------------------------------------------- /backend/Helpers/StringHelper.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------- 2 | ---! @file 3 | ---! @brief 字符串辅助处理 4 | --------------------------------------------------- 5 | 6 | ---! 模块定义 7 | local class = {} 8 | 9 | ---! @brief 创建一个UUID 10 | local function uuid () 11 | local seed = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'} 12 | local tb = {} 13 | for i=1,32 do 14 | table.insert(tb, seed[math.random(1, 16)]) 15 | end 16 | local sid = table.concat(tb) 17 | return string.format('%s-%s-%s-%s-%s', 18 | string.sub(sid, 1, 8), 19 | string.sub(sid, 9,12), 20 | string.sub(sid, 13,16), 21 | string.sub(sid, 17,20), 22 | string.sub(sid, 21,32)) 23 | end 24 | class.uuid = uuid 25 | 26 | ---! @brief 分割字符串 27 | ---! @param text 被分割的字符串 28 | ---! @param regularExp 用来表示间隔的正则表达式 默认是空格区分 "[^%s]+" 29 | ---! @return 返回分割后的字符串数组 30 | local function split (text, regularExp) 31 | text = text or "" 32 | regularExp = regularExp or "[^%s]+" 33 | 34 | local arr = {} 35 | for w in string.gmatch(text, regularExp) do 36 | table.insert(arr, w) 37 | end 38 | return arr 39 | end 40 | class.split = split 41 | 42 | ---! @brief 合并字符串数组 43 | ---! @param arr 需要合并的字符串数组 44 | ---! @param sep 间隔符 45 | ---! @return 返回合并后的字符串 46 | local function join (arr, sep) 47 | arr = arr or {} 48 | sep = sep or " " 49 | 50 | local str = nil 51 | for _, txt in ipairs(arr) do 52 | txt = tostring(txt) 53 | if str then 54 | str = str .. sep .. txt 55 | else 56 | str = txt 57 | end 58 | end 59 | 60 | str = str or "" 61 | return str 62 | end 63 | class.join = join 64 | 65 | -- like 10.132.42.12 66 | class.isInnerAddr = function (addr) 67 | if not addr then 68 | return false 69 | end 70 | local checks = { 71 | {"10.0.0.0", "10.999.255.255"}, 72 | {"172.16.0.0", "172.31.999.255"}, 73 | {"192.168.0.0", "192.168.999.255"}, 74 | {"127.0.0.0", "127.999.255.255"}, 75 | } 76 | for _, one in ipairs(checks) do 77 | if addr >= one[1] and addr <= one[2] then 78 | return true 79 | end 80 | end 81 | return false 82 | end 83 | 84 | class.isNullKey = function (key) 85 | return (not key) or key == "" 86 | end 87 | 88 | return class 89 | 90 | -------------------------------------------------------------------------------- /backend/MainServer/DBService.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------ 2 | ---! @file 3 | ---! @brief DBService, 数据库服务,调用redis/mysql 4 | ------------------------------------------------------ 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | local cluster = require "skynet.cluster" 9 | 10 | ---! 帮助库 11 | local clsHelper = require "ClusterHelper" 12 | 13 | ---! 全局常量 14 | local redis_srv 15 | local mysql_srv 16 | 17 | ---! lua commands 18 | local CMD = {} 19 | 20 | ---! 单独执行 sql 命令 21 | function CMD.execDB (cmd) 22 | return skynet.call(mysql_srv, "lua", "execDB", cmd) 23 | end 24 | 25 | ---! 单独执行 redis 命令 26 | function CMD.runCmd (cmd, key, ...) 27 | return skynet.call(redis_srv, "lua", "runCmd", cmd, key, ...) 28 | end 29 | 30 | ---! load from mysql, and update redis too 31 | function CMD.loadDB (tableName, keyName, keyValue, noInsert) 32 | local ret = skynet.call(mysql_srv, "lua", "loadDB", tableName, keyName, keyValue, noInsert) 33 | skynet.call(redis_srv, "lua", "loadDB", tableName, keyName, keyValue, ret) 34 | return ret 35 | end 36 | 37 | ---! update to redis, and mysql; 直接覆盖 38 | function CMD.updateDB (tableName, keyName, keyValue, fieldName, fieldValue) 39 | local ret = skynet.call(mysql_srv, "lua", "updateDB", tableName, keyName, keyValue, fieldName, fieldValue) 40 | ret = ret and skynet.call(redis_srv, "lua", "updateDB", tableName, keyName, keyValue, fieldName, fieldValue) 41 | return ret 42 | end 43 | 44 | ---! 增量修改 45 | function CMD.deltaDB (tableName, keyName, keyValue, fieldName, deltaValue) 46 | local ret = skynet.call(mysql_srv, "lua", "deltaDB", tableName, keyName, keyValue, fieldName, deltaValue) 47 | ret = ret and skynet.call(redis_srv, "lua", "deltaDB", tableName, keyName, keyValue, fieldName, deltaValue) 48 | return ret 49 | end 50 | 51 | ---! 服务的启动函数 52 | skynet.start(function() 53 | ---! 初始化随机数 54 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 55 | 56 | ---! 注册skynet消息服务 57 | skynet.dispatch("lua", function(_,_, cmd, ...) 58 | local f = CMD[cmd] 59 | if f then 60 | local ret = f(...) 61 | if ret then 62 | skynet.ret(skynet.pack(ret)) 63 | end 64 | else 65 | skynet.error("unknown command ", cmd) 66 | end 67 | end) 68 | 69 | ---! 启动 redis & mysql 服务 70 | redis_srv = skynet.newservice("RedisService") 71 | mysql_srv = skynet.newservice("MySQLService") 72 | 73 | ---! 注册自己的地址 74 | local srv = skynet.uniqueservice(clsHelper.kNodeInfo) 75 | skynet.call(srv, "lua", "updateConfig", skynet.self(), clsHelper.kDBService) 76 | end) 77 | 78 | -------------------------------------------------------------------------------- /backend/HallServer/main.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------ 2 | ----! @file 3 | ----! @brief start service for HallServer 4 | ----! @author Zhou Weikuan hr@cronlygames.com 5 | ------------------------------------------ 6 | 7 | ---! core functions like skynet, skymgr, cluster 8 | local skynet = require "skynet" 9 | local cluster = require "skynet.cluster" 10 | 11 | ---! helper class 12 | local packetHelper = require "PacketHelper" 13 | local clsHelper = require "ClusterHelper" 14 | 15 | local function addBotAgents (config) 16 | local num = 53 17 | local players = {} 18 | for i=1,num do 19 | players[i] = i 20 | end 21 | for i=1,num do 22 | local t = math.random(1, num) 23 | players[i], players[t] = players[t], players[i] 24 | end 25 | 26 | skynet.sleep(100) 27 | local bn = config.BotNum or 0 28 | for i=1, bn do 29 | skynet.sleep(30) 30 | local bot = skynet.newservice("BotAgent") 31 | local uid = string.format("uid%05d", 1000 + players[i]) 32 | skynet.call(bot, "lua", "start", config.BotName, uid, config.TickInterval) 33 | end 34 | print("load ", bn, "client bots") 35 | end 36 | 37 | --! @brief start services 38 | skynet.start(function() 39 | ---! 初始化随机数 40 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 41 | 42 | ---! 启动NodeInfo 43 | local srv = skynet.uniqueservice("NodeInfo") 44 | skynet.call(srv, "lua", "initNode") 45 | 46 | ---! 启动debug_console服务 47 | local port = skynet.call(srv, "lua", "getConfig", "nodeInfo", "debugPort") 48 | assert(port >= 0) 49 | print("debug port is", port) 50 | skynet.newservice("debug_console", port) 51 | 52 | ---! 集群处理 53 | local list = skynet.call(srv, "lua", "getConfig", "clusterList") 54 | list["__nowaiting"] = true 55 | cluster.reload(list) 56 | 57 | local appName = skynet.call(srv, "lua", "getConfig", "nodeInfo", "appName") 58 | cluster.open(appName) 59 | 60 | ---! 启动 info :d 节点状态信息 服务 61 | skynet.uniqueservice("NodeStat") 62 | 63 | ---! 游戏配置读取 64 | ---! Hall config 65 | local conf = skynet.getenv("HallConfig") 66 | skynet.error("conf is ", conf) 67 | 68 | ---! 启动 HallService 服务 69 | local hall = skynet.uniqueservice("HallService") 70 | 71 | ---! 本房间的配置 72 | if conf then 73 | local config = packetHelper.load_config(conf) 74 | skynet.call(srv, "lua", "updateConfig", config, clsHelper.kHallConfig) 75 | skynet.call(hall, "lua", "createInterface", config) 76 | skynet.fork(function () 77 | addBotAgents(config) 78 | end) 79 | end 80 | 81 | ---! 启动 NodeLink 服务 82 | skynet.newservice("NodeLink") 83 | 84 | ---! 没事啦 休息去吧 85 | skynet.sleep(100 * 100) 86 | skynet.exit() 87 | end) 88 | 89 | -------------------------------------------------------------------------------- /backend/Algos/SeatArray.lua: -------------------------------------------------------------------------------- 1 | --------------------------------- 2 | --! @file 3 | --! @addtogroup SeatArray 4 | --! @brief a counted array SeatArray 5 | --! @author hr@cronlygames.com 6 | ----------------------------------- 7 | 8 | --! create the class name SeatArray 9 | --! create the class metatable 10 | local class = {mt = {}} 11 | class.mt.__index = class 12 | 13 | --! @brief The creator for SeatArray 14 | --! @return return the created SeatArray object 15 | local function create () 16 | local self = { 17 | count = 0, 18 | data = {}, 19 | } 20 | setmetatable(self, class.mt) 21 | 22 | return self 23 | end 24 | class.create = create 25 | 26 | --! @brief get an object from this numarray 27 | --! @param self the numarray 28 | --! @param idx the position to get the object, default at the last element 29 | local function getObjectAt (self, idx) 30 | return self.data[idx] 31 | end 32 | class.getObjectAt = getObjectAt 33 | 34 | --! @brief set an object to this numarray 35 | --! @param self the numarray 36 | --! @param obj the object to set or replace 37 | --! @param idx the position to set the object, default replace the last element 38 | local function setObjectAt (self, idx, obj) 39 | local old = self.data[idx] 40 | if old then 41 | self.count = self.count - 1 42 | end 43 | if obj then 44 | self.count = self.count + 1 45 | end 46 | 47 | self.data[idx] = obj 48 | end 49 | class.setObjectAt = setObjectAt 50 | 51 | --! @brief remove an object from this numarray 52 | --! @param self the numarray 53 | --! @param idx the position to remove the object, must not nil 54 | local function removeObjectAt (self, idx) 55 | local old = self.data[idx] 56 | if old then 57 | self.count = self.count - 1 58 | end 59 | self.data[idx] = nil 60 | end 61 | class.removeObjectAt = removeObjectAt 62 | 63 | --! @brief get the count for this numarray 64 | --! @param self the numarray 65 | local function getCount (self) 66 | return self.count 67 | end 68 | class.getCount = getCount 69 | 70 | --! @brief loop elements in this numarray to execute function [handler], 71 | -- until the function [handler] return true or all elements are checked 72 | --! @param self the numarray 73 | --! @param handler the executed function 74 | --! @note remember return true in handler for matched element 75 | local function forEach (self, handler) 76 | local data = self.data 77 | for idx,obj in pairs(data) do 78 | if handler(idx, obj) then 79 | return 80 | end 81 | end 82 | end 83 | class.forEach = forEach 84 | 85 | --! @brief reset the numarray 86 | local function clear (self) 87 | self.count = 0 88 | self.data = {} 89 | end 90 | class.clear = clear 91 | 92 | --! @brief reset the numarray 93 | class.reset = clear 94 | 95 | return class 96 | 97 | -------------------------------------------------------------------------------- /backend/protos/Rocket.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package Rocket; 4 | 5 | message UserStatus { 6 | optional int32 FUserCode = 1; 7 | optional int32 FScore = 2; 8 | optional int32 FCounter = 3; 9 | optional bytes FLastGameTime = 4; 10 | optional int32 FSaveDate = 5; 11 | optional int32 FSaveCount = 6; 12 | 13 | optional int32 gameSkin = 10; 14 | optional int32 status = 11; 15 | optional int32 killNum = 12; 16 | } 17 | 18 | // client to server 19 | message NicknameInfo { 20 | optional bytes nickname = 1; 21 | optional int32 skinId = 2; 22 | } 23 | 24 | // message DrawLine is same as MouseMove 25 | message Position { 26 | optional float x = 1; 27 | optional float y = 2; 28 | optional float s = 3; 29 | } 30 | 31 | message ActionInfo { 32 | optional int32 actionCode = 1; 33 | } 34 | 35 | message PlayerInfo { 36 | optional int32 playerId = 1; 37 | optional int32 currScore = 2; 38 | optional int32 histScore = 3; 39 | } 40 | 41 | message NoticeInfo { 42 | optional bytes name = 1; 43 | optional bytes killer = 2; 44 | 45 | optional int32 count = 3; // kill count, or piece count 46 | optional int32 nodeId = 4; // == 0, it is wall, otherwise mine 47 | } 48 | 49 | message BorderInfo { 50 | optional int32 left = 1; 51 | optional int32 right = 2; 52 | optional int32 bottom = 3; 53 | optional int32 top = 4; 54 | 55 | optional int32 sightViewWidth = 5; 56 | optional int32 sightViewHeight = 6; 57 | } 58 | 59 | message LeaderBoard { 60 | message Type_FFA { 61 | optional int32 nodeId = 1; 62 | optional bytes name = 2; 63 | optional int32 score = 3; 64 | } 65 | 66 | message Type_PieChart { 67 | optional int32 teamId = 1; 68 | optional float teamRatio = 2; 69 | } 70 | 71 | repeated Type_FFA ffa_data = 1; 72 | repeated Type_PieChart pie_data = 2; 73 | } 74 | 75 | message UpdateView { 76 | message DestroyNodes { 77 | optional int32 nodeId = 1; 78 | } 79 | 80 | message UpdateNodes { 81 | optional int32 nodeId = 1; 82 | optional int32 nodeType = 2; 83 | optional bytes name = 3; 84 | 85 | optional float pos_x = 5; 86 | optional float pos_y = 6; 87 | optional float moveAngle = 7; 88 | 89 | repeated int32 buffs = 10; 90 | } 91 | 92 | optional PlayerInfo playerInfo = 1; 93 | 94 | repeated DestroyNodes destroyNodes = 2; 95 | repeated int32 invalidNodes = 3; 96 | repeated UpdateNodes updateNodes = 4; 97 | } 98 | -------------------------------------------------------------------------------- /backend/Helpers/PacketHelper.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------- 2 | ---! @file 3 | ---! @brief 文件和网络读写,打包解包等 4 | --------------------------------------------------- 5 | 6 | ---! 依赖库 7 | local protobuf = require "protobuf" 8 | 9 | --! create the class metatable 10 | local class = {mt = {}} 11 | class.mt.__index = class 12 | 13 | local msgFiles = {} 14 | 15 | ---! PacketHelper 模块定义 16 | --! @brief The creator for PacketHelper 17 | --! @return return the created object 18 | local function create (protoFile) 19 | local self = {} 20 | setmetatable(self, class.mt) 21 | 22 | if protoFile then 23 | self:registerProtoName(protoFile) 24 | end 25 | 26 | return self 27 | end 28 | class.create = create 29 | 30 | ---! @brief make sure the protoFile is registered 31 | local function registerProtoName (self, protoFile) 32 | if not msgFiles[protoFile] then 33 | protobuf.register_file(protoFile) 34 | msgFiles[protoFile] = true 35 | end 36 | end 37 | class.registerProtoName = registerProtoName 38 | 39 | ---! @brief make a general proto data for client - server. 40 | local function makeProtoData (self, main, sub, body) 41 | local msg = { 42 | mainType = main, 43 | subType = sub, 44 | msgBody = body 45 | } 46 | 47 | local packet = protobuf.encode("CGGame.ProtoInfo", msg) 48 | return packet 49 | end 50 | class.makeProtoData = makeProtoData 51 | 52 | ---! 编码 53 | local function encodeMsg (self, msgFormat, packetData) 54 | return protobuf.encode(msgFormat, packetData) 55 | end 56 | class.encodeMsg = encodeMsg 57 | 58 | ---! 解码 59 | local function decodeMsg (self, msgFormat, packet) 60 | return protobuf.decode(msgFormat, packet) 61 | end 62 | class.decodeMsg = decodeMsg 63 | 64 | ---! 深度递归解码 65 | local function extractMsg (self, msg) 66 | protobuf.extract(msg) 67 | return msg 68 | end 69 | class.extractMsg = extractMsg 70 | 71 | 72 | ---! @brief 加载配置文件, 文件名为从 backend目录计算的路径 73 | local function load_config(filename) 74 | local f = assert(io.open(filename)) 75 | local source = f:read "*a" 76 | f:close() 77 | local tmp = {} 78 | assert(load(source, "@"..filename, "t", tmp))() 79 | 80 | return tmp 81 | end 82 | class.load_config = load_config 83 | 84 | ---! @brief 通过名称,创建类的对象 85 | ---! @param name 类名 86 | ---! @param ... 类的对象创建时所需要的其它参数 87 | ---! for hall interface: PacketHelper.createObject(conf.Interface, conf) 88 | ---! for game class: PacketHelper.createObject(conf.GameClass, conf) 89 | local function createObject(name, ...) 90 | local cls = require(name) 91 | if not cls then 92 | print("failed to load class", name) 93 | end 94 | 95 | return cls.create(...) 96 | end 97 | class.createObject = createObject 98 | 99 | 100 | return class 101 | 102 | -------------------------------------------------------------------------------- /backend/protos/YunCheng.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package YunCheng; 4 | 5 | message UserStatus { 6 | optional int32 FUserCode = 1; 7 | optional int32 status = 2; 8 | optional int32 tableId = 3; 9 | optional int32 seatId = 4; 10 | optional int32 tableStatus = 5; 11 | 12 | optional int32 FCounter = 6; 13 | optional int32 FScore = 7; 14 | optional int32 FWins = 8; 15 | optional int32 FLoses = 9; 16 | optional int32 FDraws = 10; 17 | optional bytes FLastGameTime = 11; 18 | optional int32 FSaveDate = 12; 19 | optional int32 FSaveCount = 13; 20 | 21 | optional int32 FAgentCode = 14; 22 | } 23 | 24 | message RoomDetails { 25 | optional int32 passCount = 1; // 几局 26 | optional int32 costCoins = 2; // 总花费金币, AA时分摊,别的时候一家支付 27 | optional int32 payType = 3; // 0, 房主, 1, AA, 2, 赢家 28 | optional int32 AAPrice = 4; // AA 时的价格 29 | 30 | optional int32 playRule = 6; // 普通=0, 花牌=1, 花牌时才有同色三炸可选 31 | optional int32 same3Bomb = 7; // 0, no, 1, same, 2 diff. playRule =0, same3Bomb=0; playRule = 1, same3Bomb = 0, 1 or 2. 32 | optional int32 bombMax = 8; // 3, 4, 5, 0表示无限 33 | optional int32 bottomScore = 9; // 1, 2, 5, 10 34 | } 35 | 36 | // 每一手牌 37 | message CardInfo { 38 | optional int32 seatId = 1; 39 | repeated int32 cards = 2; 40 | } 41 | 42 | message SeatInfo { 43 | optional int32 userCode = 1; 44 | optional int32 seatId = 2; 45 | 46 | optional int32 multiple = 3; 47 | 48 | 49 | repeated int32 handCards = 5; // we should hide them for others 50 | repeated int32 throwCards = 6; // 上一次打出的牌 51 | optional int32 scoreCard = 7; // 累计积分 52 | } 53 | 54 | // 每一轮的出牌情况 55 | message CallInfo { 56 | optional int32 seatId = 1; 57 | optional int32 callMult = 2; 58 | } 59 | 60 | message GameInfo { 61 | // waitMask, waitStatus is from GameTable 62 | optional int32 masterSeatId = 1; 63 | optional int32 curSeatId = 2; 64 | 65 | optional int32 bottomScore = 3; 66 | optional int32 bombCount = 4; 67 | repeated int32 bottomCards = 5; 68 | optional bool showBottoms = 6; 69 | 70 | repeated CardInfo histCards = 7; // 71 | 72 | optional CardInfo winCards = 8; 73 | repeated SeatInfo seatInfo = 9; // playingUsers 74 | 75 | optional int32 bombMax = 10; // 炸弹封顶数 76 | optional int32 same3Bomb = 11; 77 | } 78 | 79 | message GameOver { 80 | message OneSite { 81 | optional int32 seatId = 1; 82 | optional int32 deltaChips = 2; 83 | optional int32 deltaScore = 3; 84 | repeated int32 handCards = 4; 85 | }; 86 | 87 | repeated OneSite sites = 1; 88 | optional int32 resType = 2; // 0 正常 1 春天 89 | } 90 | 91 | -------------------------------------------------------------------------------- /backend/AgentServer/TcpAgent.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------- 2 | ---! @file 3 | ---! @brief tcp socket的客户连接 4 | -------------------------------------------------------------- 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | local socket = require "skynet.socket" 9 | 10 | ---! 帮助库 11 | local clsHelper = require "ClusterHelper" 12 | local taskHelper = require "TaskHelper" 13 | 14 | local AgentUtils = require "AgentUtils" 15 | 16 | ---! 全局变量 17 | local agentInfo = {} 18 | local agentUtil = nil 19 | 20 | ---! 回调和命令 21 | local utilCallBack = {} 22 | local CMD = {} 23 | 24 | ---! @brief start service 25 | function CMD.start (info) 26 | for k, v in pairs(info) do 27 | agentInfo[k] = v 28 | end 29 | 30 | skynet.error("CMD start called on fd ", agentInfo.client_fd) 31 | skynet.call(agentInfo.gate, "lua", "forward", agentInfo.client_fd) 32 | 33 | agentInfo.last_update = skynet.time() 34 | skynet.fork(function() 35 | local heartbeat = 3 -- 3 seconds to send heart beat 36 | local timeout = 10 -- 10 seconds to break 37 | while true do 38 | local now = skynet.time() 39 | if now - agentInfo.last_update >= timeout then 40 | agentUtil:kickMe() 41 | return 42 | end 43 | 44 | agentUtil:sendHeartBeat() 45 | skynet.sleep(heartbeat * 100) 46 | end 47 | end) 48 | 49 | return 0 50 | end 51 | 52 | ---! send protocal back to user socket 53 | function CMD.sendProtocolPacket (packet) 54 | if agentInfo.client_fd then 55 | local data = string.pack(">s2", packet) 56 | socket.write(agentInfo.client_fd, data) 57 | end 58 | end 59 | 60 | ---! @brief 通知agent主动结束 61 | function CMD.disconnect () 62 | agentUtil:hallReqQuit() 63 | 64 | if agentInfo.client_fd then 65 | socket.close(agentInfo.client_fd) 66 | end 67 | 68 | skynet.exit() 69 | end 70 | 71 | ---! handle socket data 72 | skynet.register_protocol { 73 | name = "client", 74 | id = skynet.PTYPE_CLIENT, 75 | unpack = function (msg, sz) 76 | return skynet.tostring(msg,sz) 77 | end, 78 | dispatch = function (session, address, text) 79 | skynet.ignoreret() 80 | 81 | agentInfo.last_update = skynet.time() 82 | 83 | local worker = function () 84 | agentUtil:command_handler(text) 85 | end 86 | 87 | xpcall( function() 88 | taskHelper.queue_task(worker) 89 | end, 90 | function(err) 91 | skynet.error(err) 92 | skynet.error(debug.traceback()) 93 | end) 94 | end 95 | } 96 | 97 | skynet.start(function() 98 | ---! 注册skynet消息服务 99 | skynet.dispatch("lua", function(_,_, cmd, ...) 100 | local f = CMD[cmd] 101 | if f then 102 | local ret = f(...) 103 | if ret then 104 | skynet.ret(skynet.pack(ret)) 105 | end 106 | else 107 | skynet.error("unknown command ", cmd) 108 | end 109 | end) 110 | 111 | agentInfo.agentSign = os.time() 112 | agentUtil = AgentUtils.create(agentInfo, CMD, utilCallBack) 113 | end) 114 | 115 | -------------------------------------------------------------------------------- /backend/AgentServer/WebAgent.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------- 2 | ---! @file 3 | ---! @brief web socket的客户连接 4 | -------------------------------------------------------------- 5 | 6 | ---! 7 | local skynet = require "skynet" 8 | 9 | ---! 10 | local clsHelper = require "ClusterHelper" 11 | local taskHelper = require "TaskHelper" 12 | 13 | local AgentUtils = require "AgentUtils" 14 | 15 | ---! 16 | local agentInfo = {} 17 | local agentUtil = nil 18 | local client_sock = nil 19 | 20 | local handler = {} 21 | function handler.on_open(ws) 22 | agentInfo.last_update = os.time() 23 | end 24 | 25 | function handler.on_message(ws, msg) 26 | agentInfo.last_update = os.time() 27 | 28 | local worker = function () 29 | agentUtil:command_handler(msg) 30 | end 31 | 32 | xpcall( function() 33 | taskHelper.queue_task(worker) 34 | end, 35 | function(err) 36 | skynet.error(err) 37 | skynet.error(debug.traceback()) 38 | end) 39 | end 40 | 41 | function handler.on_error(ws, msg) 42 | agentUtil:kickMe() 43 | end 44 | 45 | function handler.on_close(ws, code, reason) 46 | agentUtil:kickMe() 47 | end 48 | 49 | ---! 50 | local utilCallBack = {} 51 | 52 | ---! 53 | local CMD = {} 54 | 55 | ---! @brief start service 56 | function CMD.start (info, header) 57 | if client_sock then 58 | return 59 | end 60 | 61 | for k, v in pairs(info) do 62 | agentInfo[k] = v 63 | end 64 | 65 | local id = info.client_fd 66 | socket.start(id) 67 | pcall(function () 68 | client_sock = websocket.new(id, header, handler) 69 | end) 70 | if client_sock then 71 | skynet.fork(function () 72 | client_sock:start() 73 | end) 74 | end 75 | 76 | agentInfo.last_update = skynet.time() 77 | skynet.fork(function() 78 | local heartbeat = 3 -- 3 seconds to send heart beat 79 | local timeout = 10 -- 10 seconds to break 80 | while true do 81 | local now = skynet.time() 82 | if now - agentInfo.last_update >= timeout then 83 | agentUtil:kickMe() 84 | return 85 | end 86 | 87 | agentUtil:sendHeartBeat() 88 | skynet.sleep(heartbeat * 100) 89 | end 90 | end) 91 | 92 | return 0 93 | end 94 | 95 | ---! send protocal back to user socket 96 | function CMD.sendProtocolPacket (packet) 97 | if client_sock then 98 | client_sock:send_binary(packet) 99 | end 100 | end 101 | 102 | 103 | ---! @brief 通知agent主动结束 104 | function CMD.disconnect () 105 | agentUtil:hallReqQuit() 106 | 107 | if client_sock then 108 | client_sock:close() 109 | client_sock = nil 110 | end 111 | 112 | skynet.exit() 113 | end 114 | 115 | 116 | skynet.start(function() 117 | ---! 注册skynet消息服务 118 | skynet.dispatch("lua", function(_,_, cmd, ...) 119 | local f = CMD[cmd] 120 | if f then 121 | local ret = f(...) 122 | if ret then 123 | skynet.ret(skynet.pack(ret)) 124 | end 125 | else 126 | skynet.error("unknown command ", cmd) 127 | end 128 | end) 129 | 130 | agentInfo.agentSign = os.time() 131 | agentUtil = AgentUtils.create(agentInfo, CMD, utilCallBack) 132 | end) 133 | 134 | -------------------------------------------------------------------------------- /backend/client/Delegate.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | local WaitList = require "WaitList" 4 | local LoginHelper = require "LoginHelper" 5 | local AuthUtils = require "AuthUtils" 6 | 7 | local BotPlayer = require "BotPlayer_YunCheng" 8 | 9 | local protoTypes = require "ProtoTypes" 10 | local const = require "Const_YunCheng" 11 | 12 | ---! create the class metatable 13 | local class = {mt = {}} 14 | class.mt.__index = class 15 | 16 | 17 | ---! create delegate object 18 | class.create = function () 19 | local self = {} 20 | setmetatable(self, class.mt) 21 | 22 | self.exes = WaitList.create() 23 | 24 | class.initAuth() 25 | self.authInfo = AuthUtils.getAuthInfo() 26 | 27 | self.lastUpdate = skynet.time() 28 | self.login = LoginHelper.create(const) 29 | 30 | local agent = BotPlayer.create(self, self.authInfo) 31 | self.agent = agent 32 | 33 | return self 34 | end 35 | 36 | class.initAuth = function () 37 | AuthUtils.setItem(AuthUtils.keyPlayerId, "G:1293841824") 38 | AuthUtils.setItem(AuthUtils.keyPassword, "apple") 39 | AuthUtils.setItem(AuthUtils.keyNickname, "test") 40 | AuthUtils.setItem(AuthUtils.keyOSType, "client") 41 | AuthUtils.setItem(AuthUtils.keyPlatform, "client") 42 | end 43 | 44 | class.command_handler = function (self, user, packet) 45 | local login = self.login 46 | if login.remotesocket then 47 | login.remotesocket:sendPacket(packet) 48 | end 49 | end 50 | 51 | class.tickFrame = function (self) 52 | local now = skynet.time() 53 | local login = self.login 54 | --[[ 55 | if login:tickCheck(self) then 56 | -- local networkLayer = require "NetworkLayer" 57 | -- networkLayer.create(self) 58 | 59 | self.lastUpdate = now 60 | end 61 | --]] 62 | 63 | local delta = now - self.lastUpdate 64 | if delta > 3.0 then 65 | login:closeSocket() 66 | elseif delta > 1.0 then 67 | login:sendHeartBeat() 68 | end 69 | 70 | while login.remotesocket do 71 | local p = login.remotesocket:recvPacket() 72 | if p then 73 | self.lastUpdate = now 74 | self.agent:recvPacket(p) 75 | else 76 | break 77 | end 78 | end 79 | 80 | self.agent:tickFrame() 81 | 82 | self.exes:resume() 83 | end 84 | 85 | class.stage_login = function (self) 86 | local login = self.login 87 | login:getOldLoginList(true, true) 88 | 89 | self.exes:pause() 90 | 91 | local hasAgent = nil 92 | for k, v in pairs(login.agentList) do 93 | hasAgent = true 94 | break 95 | end 96 | if not hasAgent then 97 | print("no agent list found") 98 | return 99 | end 100 | 101 | skynet.sleep(100) 102 | login:tryConnect() 103 | self.exes:pause() 104 | 105 | skynet.sleep(100) 106 | login:getAgentList() 107 | self.exes:pause() 108 | 109 | skynet.sleep(100) 110 | self.agent:sendAuthOptions(protoTypes.CGGAME_PROTO_SUBTYPE_ASKRESUME) 111 | 112 | while not self.authOK do 113 | self.exes:pause() 114 | end 115 | print("auth OK!") 116 | 117 | login:tryGame() 118 | self.exes:pause() 119 | 120 | skynet.sleep(100) 121 | self.agent:sendSitDownOptions() 122 | self.exes:pause() 123 | 124 | return true 125 | end 126 | 127 | return class 128 | 129 | -------------------------------------------------------------------------------- /backend/client/AuthUtils.lua: -------------------------------------------------------------------------------- 1 | local skynet = skynet or require "skynet" 2 | local crypt = skynet.crypt or require "skynet.crypt" 3 | 4 | local tableHelper = require "TableHelper" 5 | local strHelper = require "StringHelper" 6 | 7 | local data = nil 8 | local path 9 | if skynet.init then 10 | path = "client/auth.tmp" 11 | else 12 | path = cc.FileUtils:getInstance():getWritablePath() .. "auth.tmp" 13 | end 14 | 15 | 16 | ---! create the class metatable 17 | local class = {} 18 | 19 | ---! class variables 20 | class.keyAgentList = "com.cronlygames.agentservers.list" 21 | class.keyHallCount = "com.cronlygames.hallservers.count" 22 | class.keyGameMode = "com.cronlygames.gameMode" 23 | 24 | class.base64AuthChallenge = "com.cronlygames.auth.challenge" 25 | class.base64AuthSecret = "com.cronlygames.auth.secret" 26 | class.keyAuthIndex = "com.cronlygames.auth.index" 27 | 28 | class.keyPlayerId = "com.cronlygames.auth.playerId" 29 | class.keyPassword = "com.cronlygames.auth.password" 30 | class.keyNickname = "com.cronlygames.auth.nickname" 31 | class.keyAvatarUrl = "com.cronlygames.auth.avatarUrl" 32 | class.keyAvatarId = "com.cronlygames.auth.avatarId" 33 | 34 | class.keyUserCode = "com.cronlygames.auth.usercode" 35 | 36 | class.keyAccessToken= "com.cronlygames.auth.accessToken" 37 | class.keyOSType = "com.cronlygames.auth.ostype" 38 | class.keyPlatform = "com.cronlygames.auth.platform" 39 | 40 | ---! class functions 41 | class.load = function () 42 | local f = io.open(path) 43 | if not f then 44 | data = {} 45 | return 46 | end 47 | local source = f:read "*a" 48 | f:close() 49 | data = tableHelper.decode(source) or {} 50 | end 51 | 52 | class.save = function () 53 | local f = io.open(path, "w") 54 | if not f then 55 | return 56 | end 57 | local text = tableHelper.encode(data) 58 | f:write(text) 59 | f:close() 60 | end 61 | 62 | class.getItem = function (key, def) 63 | if not data then 64 | class.load() 65 | end 66 | def = def or "" 67 | return data[key] or def 68 | end 69 | 70 | class.setItem = function (key, obj) 71 | if not data then 72 | class.load() 73 | end 74 | 75 | data[key] = obj or "" 76 | class.save() 77 | end 78 | 79 | class.getAuthInfo = function () 80 | local ret = {} 81 | ret.playerId = class.getItem(class.keyPlayerId) 82 | if strHelper.isNullKey(ret.playerId) then 83 | skynet.error("AuthInfo incomplete") 84 | skynet.error(debug.traceback()) 85 | end 86 | 87 | ret.userCode = class.getItem(class.keyUserCode) 88 | ret.playerId = class.getItem(class.keyPlayerId) 89 | ret.password = class.getItem(class.keyPassword) 90 | ret.nickname = class.getItem(class.keyNickname) 91 | ret.avatarUrl = class.getItem(class.keyAvatarUrl) 92 | ret.avatarId = class.getItem(class.keyAvatarId, 0) 93 | 94 | ret.accessToken = class.getItem(class.keyAccessToken) 95 | ret.osType = class.getItem(class.keyOSType) 96 | ret.platform = class.getItem(class.keyPlatform) 97 | 98 | ret.authIndex = class.getItem(class.keyAuthIndex, 0) 99 | ret.challenge = crypt.base64decode(class.getItem(class.base64AuthChallenge)) 100 | ret.secret = crypt.base64decode(class.getItem(class.base64AuthSecret)) 101 | 102 | return ret 103 | end 104 | 105 | return class 106 | 107 | -------------------------------------------------------------------------------- /backend/HallServer/BotAgent.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------ 2 | ---! @file 3 | ---! @brief hall agents 4 | ------------------------------------------------------------ 5 | 6 | ---! core libraries 7 | local skynet = require "skynet" 8 | 9 | ---! helpers 10 | local clsHelper = require "ClusterHelper" 11 | local packetHelper = (require "PacketHelper").create("protos/CGGame.pb") 12 | 13 | ---! headers 14 | local protoTypes = require "ProtoTypes" 15 | 16 | ---! variables 17 | local botPlayer = nil 18 | local service = nil 19 | 20 | local agentInfo = {} 21 | local tickInterval = 50 22 | 23 | ---! skynet service handlings 24 | local CMD = {} 25 | 26 | ---! @brief start service 27 | function CMD.start (botName, uid, TickInterval) 28 | if botName and botName ~= "" and botPlayer == nil then 29 | local nodeInfo = skynet.uniqueservice(clsHelper.kNodeInfo) 30 | local myInfo = skynet.call(nodeInfo, "lua", "getConfig", "nodeInfo") 31 | 32 | agentInfo.playerId = uid 33 | agentInfo.FUniqueID = uid 34 | agentInfo.FNickName = uid 35 | agentInfo.client_fd = -skynet.self() 36 | agentInfo.agent = skynet.self() 37 | agentInfo.appName = myInfo.appName 38 | agentInfo.agentSign = os.time() 39 | 40 | tickInterval = TickInterval or tickInterval 41 | 42 | botPlayer = packetHelper.createObject(botName, CMD, agentInfo) 43 | local ret, code = pcall(skynet.call, service, "lua", "joinGame", agentInfo) 44 | if ret then 45 | agentInfo.FUserCode = code 46 | botPlayer.selfUserCode = code 47 | end 48 | 49 | skynet.sleep(200) 50 | local info = { 51 | roomId = nil, 52 | seatId = nil, 53 | } 54 | local data = packetHelper:encodeMsg("CGGame.SeatInfo", info) 55 | pcall(skynet.call, service, "lua", "gameData", code, agentInfo.agentSign, protoTypes.CGGAME_PROTO_SUBTYPE_SITDOWN, data) 56 | end 57 | 58 | return 0 59 | end 60 | 61 | function CMD.command_handler (cmd, user, packet) 62 | local args = packetHelper:decodeMsg("CGGame.ProtoInfo", packet) 63 | if args then 64 | local cmd = args.mainType == protoTypes.CGGAME_PROTO_MAINTYPE_HALL and "hallData" or "gameData" 65 | pcall(skynet.call, service, "lua", cmd, agentInfo.FUserCode, agentInfo.agentSign, args.subType, args.msgBody) 66 | end 67 | end 68 | 69 | function CMD.sendProtocolPacket (packet) 70 | if botPlayer then 71 | botPlayer:recvPacket(packet) 72 | end 73 | end 74 | 75 | ---! @brief 通知agent主动结束 76 | function CMD.disconnect () 77 | skynet.exit() 78 | end 79 | 80 | ---! loop exec botPlayer tick frame 81 | local function loopTick () 82 | while true do 83 | if botPlayer then 84 | botPlayer:tickFrame(tickInterval * 0.01) 85 | end 86 | 87 | skynet.sleep(tickInterval) 88 | end 89 | end 90 | 91 | ---! @brief hall agent的入口函数 92 | skynet.start(function() 93 | ---! 注册skynet消息服务 94 | skynet.dispatch("lua", function(_,_, cmd, ...) 95 | local f = CMD[cmd] 96 | if f then 97 | local ret = f(...) 98 | if ret then 99 | skynet.ret(skynet.pack(ret)) 100 | end 101 | else 102 | skynet.error("unknown command ", cmd) 103 | end 104 | end) 105 | 106 | service = skynet.uniqueservice("HallService") 107 | skynet.fork(loopTick) 108 | end) 109 | 110 | -------------------------------------------------------------------------------- /backend/Services/NodeLink.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------- 2 | ---! @file NodeLink.lua 3 | ---! @brief 监控当前节点,察觉异常退出 4 | -------------------------------------------------------------- 5 | 6 | ---! 依赖 7 | local skynet = require "skynet" 8 | local cluster = require "skynet.cluster" 9 | 10 | local clsHelper = require "ClusterHelper" 11 | 12 | ---! 信息 13 | local nodeInfo = nil 14 | local theMainApp = nil 15 | local thisInfo 16 | 17 | ---! 保持远程节点,对方断线时切换 18 | local function holdMainServer(list) 19 | if theMainApp then 20 | return 21 | end 22 | 23 | for _, appName in ipairs(list) do 24 | local addr = clsHelper.cluster_addr(appName, clsHelper.kNodeLink) 25 | if addr then 26 | theMainApp = appName 27 | skynet.call(nodeInfo, "lua", "updateConfig", appName, clsHelper.kMainNode) 28 | 29 | local mainInfoAddr = clsHelper.cluster_addr(appName, clsHelper.kMainInfo) 30 | pcall(cluster.call, appName, mainInfoAddr, "regNode", thisInfo) 31 | 32 | skynet.fork(function() 33 | skynet.error("hold the main server", appName) 34 | pcall(cluster.call, appName, addr, "LINK", true) 35 | skynet.error("disconnect the main server", appName) 36 | 37 | theMainApp = nil 38 | skynet.call(nodeInfo, "lua", "updateConfig", nil, clsHelper.kMainNode) 39 | holdMainServer(list) 40 | end) 41 | return 42 | end 43 | end 44 | end 45 | 46 | ---! 向 MainServer 注册自己 47 | local function registerSelf () 48 | if theMainApp then 49 | return 50 | end 51 | 52 | thisInfo = skynet.call(nodeInfo, "lua", "getRegisterInfo") 53 | skynet.error("thisInfo.kind = ", thisInfo.kind) 54 | if thisInfo.kind == clsHelper.kMainServer then 55 | skynet.error("MainServer should not register itself", thisInfo.name) 56 | return 57 | end 58 | 59 | local list = skynet.call(nodeInfo, "lua", "getConfig", clsHelper.kMainServer) 60 | holdMainServer(list) 61 | end 62 | 63 | ---! 通讯 64 | local CMD = {} 65 | 66 | ---! 收到通知,需要向cluster里的MainServer注册自己 67 | function CMD.askReg () 68 | skynet.fork(registerSelf) 69 | return 0 70 | end 71 | 72 | ---! 通知在线人数更新 73 | function CMD.heartBeat (num) 74 | if not theMainApp then 75 | return 0 76 | end 77 | local mainAddr = clsHelper.cluster_addr(theMainApp, clsHelper.kMainInfo) 78 | return pcall(cluster.send, theMainApp, mainAddr, "heartBeat", thisInfo.kind, thisInfo.name, num) 79 | end 80 | 81 | ---! 收到通知,结束本服务 82 | function CMD.exit () 83 | skynet.exit() 84 | 85 | return 0 86 | end 87 | 88 | function CMD.LINK (hold) 89 | if hold then 90 | skynet.wait() 91 | end 92 | skynet.error("return from LINK") 93 | return 0 94 | end 95 | 96 | ---! 启动服务 97 | skynet.start(function() 98 | ---! 注册skynet消息服务 99 | skynet.dispatch("lua", function(_,_, cmd, ...) 100 | local f = CMD[cmd] 101 | if f then 102 | local ret = f(...) 103 | if ret then 104 | skynet.ret(skynet.pack(ret)) 105 | end 106 | else 107 | skynet.error("unknown command ", cmd) 108 | end 109 | end) 110 | 111 | ---! 向NodeInfo注册自己 112 | nodeInfo = skynet.uniqueservice("NodeInfo") 113 | skynet.call(nodeInfo, "lua", "nodeOn", skynet.self()) 114 | 115 | ---! 通知MainServer 116 | skynet.fork(registerSelf) 117 | end) 118 | 119 | -------------------------------------------------------------------------------- /backend/Services/NodeInfo.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------- 2 | ---! @file NodeInfo.lua 3 | ---! @brief 保存当前节点信息,供其它服务使用 4 | -------------------------------------------------------------- 5 | 6 | ---! 依赖 7 | local skynet = require "skynet" 8 | local cluster = require "skynet.cluster" 9 | 10 | local clsHelper = require "ClusterHelper" 11 | 12 | ---! 信息 13 | local info = {} 14 | 15 | ---! 接口 16 | local CMD = {} 17 | 18 | function CMD.initNode () 19 | clsHelper.parseConfig(info) 20 | 21 | return "" 22 | end 23 | 24 | function CMD.getServiceAddr(key) 25 | local ret = info[key] 26 | ret = ret or "" 27 | return ret 28 | end 29 | 30 | function CMD.getConfig (...) 31 | local args = table.pack(...) 32 | local ret = info 33 | for _, key in ipairs(args) do 34 | if ret[key] then 35 | ret = ret[key] 36 | else 37 | return "" 38 | end 39 | end 40 | 41 | ret = ret or "" 42 | return ret 43 | end 44 | 45 | function CMD.updateConfig (value, ...) 46 | local args = table.pack(...) 47 | local last = table.remove(args) 48 | local ret = info 49 | for _, key in ipairs(args) do 50 | local one = ret[key] 51 | if not one then 52 | one = {} 53 | ret[key] = one 54 | elseif type(one) ~= "table" then 55 | return "" 56 | end 57 | ret = one 58 | end 59 | 60 | ret[last] = value 61 | return "" 62 | end 63 | 64 | ---! 获得本节点的注册信息 65 | function CMD.getRegisterInfo () 66 | local nodeInfo = info.nodeInfo 67 | local ret = {} 68 | ret.kind = nodeInfo.serverKind 69 | ret.name = nodeInfo.appName 70 | ret.addr = nodeInfo.privAddr 71 | ret.port = nodeInfo.debugPort 72 | ret.numPlayers = nodeInfo.numPlayers 73 | 74 | ret.conf = info[clsHelper.kHallConfig] 75 | 76 | return ret 77 | end 78 | 79 | ---! 下线NodeLink 80 | local function doNodeOff () 81 | local old = info[clsHelper.kNodeLink] 82 | if old then 83 | skynet.send(old, "lua", "exit") 84 | info[clsHelper.kNodeLink] = nil 85 | end 86 | end 87 | 88 | ---! 实时监控NodeLink 89 | local function monitorMyNodeLink (nodeLink) 90 | pcall(skynet.call, nodeLink, "debug", "LINK") 91 | skynet.error("my nodelink is offline", nodeLink) 92 | if info[clsHelper.kNodeLink] == nodeLink then 93 | info[clsHelper.kNodeLink] = nil 94 | end 95 | end 96 | 97 | ---! 收到通知,NodeLink已经上线 98 | function CMD.nodeOn (nodeLink) 99 | doNodeOff() 100 | 101 | info[clsHelper.kNodeLink] = nodeLink 102 | skynet.fork(function() 103 | monitorMyNodeLink(nodeLink) 104 | end) 105 | 106 | return "" 107 | end 108 | 109 | ---! 获得下线通知 110 | function CMD.nodeOff () 111 | doNodeOff() 112 | 113 | ---! 通知 MainInfo, MainServer; 114 | ---! HallService, HallServer 115 | ---! watchDog, AgentServer 116 | local poss = {clsHelper.kMainInfo, clsHelper.kHallService, clsHelper.kWatchDog} 117 | for _, name in ipairs(poss) do 118 | old = info[name] 119 | if old then 120 | skynet.send(old, "lua", "nodeOff") 121 | end 122 | end 123 | 124 | return "" 125 | end 126 | 127 | 128 | ---! 启动函数 129 | skynet.start(function() 130 | cluster.register("NodeInfo", skynet.self()) 131 | ---! 注册skynet消息服务 132 | skynet.dispatch("lua", function(_,_, cmd, ...) 133 | local f = CMD[cmd] 134 | if f then 135 | local ret = f(...) 136 | if ret then 137 | skynet.ret(skynet.pack(ret)) 138 | end 139 | else 140 | skynet.error("unknown command ", cmd) 141 | end 142 | end) 143 | end) 144 | 145 | -------------------------------------------------------------------------------- /backend/Stages/Const_Rocket.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------- 2 | ---! @file 3 | ---! @brief 4 | ------------------------------------------------------ 5 | local const = {} 6 | setmetatable(const, { 7 | __index = function (t, k) 8 | return function() 9 | print("unknown field from const: ", k, t) 10 | end 11 | end 12 | }) 13 | 14 | const.GAMEID = 3003 15 | const.GAMEVERSION = 20180901 16 | const.LOWVERSION = 20180901 17 | 18 | const.epsilon = 1e-06 19 | 20 | const.kMaxFoodIndex = 11 21 | 22 | 23 | const.kModeMin = 0 24 | const.kModeMax = 2 25 | 26 | const.kModeTest = 0 27 | const.kModeFFA = 1 28 | 29 | const.kNoticeTypeKillings = 1 -- 击杀数目 30 | const.kNoticeTypeKilledBy = 2 -- 被人杀死 31 | const.kNoticeTypeSuicide = 3 -- 撞墙而死 32 | const.kNoticeTypeTopRank = 4 -- 最高排名 33 | 34 | 35 | -- node size range: [40, 160] scale: [0.5, 2.0] 36 | const.kNodeSize = 40 37 | 38 | -- const.kNodeDistance = 32.0 39 | const.kNodeInterval = 2 40 | const.kSpeedNormal = 15 41 | const.kIteratorNum = 1 42 | 43 | ----------- types define start ------------- 44 | const.kTypeUnknown = -1 45 | 46 | const.kTypeFoodStart = 1000 47 | const.kTypeFoodEnd = 1005 -- max 1099 48 | 49 | const.kTypeFood_Rocket = 1000 50 | const.kTypeFood_Star = 1001 51 | const.kTypeFood_Shield = 1002 52 | const.kTypeFood_Speed = 1003 53 | const.kTypeFood_Light = 1004 54 | const.kTypeFood_Harm = 1005 55 | 56 | 57 | const.kTypePlaneStart = 1100 58 | const.kTypePlaneEnd = 1100 -- max 1199 59 | 60 | const.kTypeRocketStart = 1200 61 | const.kTypeRocketEnd = 1200 -- max 1299 62 | 63 | const.isFoodType = function (nodeType) 64 | nodeType = nodeType and nodeType or const.kTypeUnknown 65 | return (nodeType >= const.kTypeFoodStart and nodeType <= const.kTypeFoodEnd) 66 | end 67 | 68 | const.isPlaneType = function (nodeType) 69 | nodeType = nodeType and nodeType or const.kTypeUnknown 70 | return (nodeType >= const.kTypePlaneStart and nodeType <= const.kTypePlaneEnd) 71 | end 72 | 73 | const.isRocketType = function (nodeType) 74 | nodeType = nodeType and nodeType or const.kTypeUnknown 75 | return (nodeType >= const.kTypeRocketStart and nodeType <= const.kTypeRocketEnd) 76 | end 77 | ---------- types define end -------------- 78 | 79 | ---! action mask for stageInfo.actionCode 80 | const.kActionMaskSpeed = 0x01 -- 加速 81 | const.kActionMaskShield = 0x02 -- 保护 82 | const.kActionMaskLight = 0x03 -- 闪电 83 | const.kActionMaskHarmed = 0x04 -- 被电到 84 | 85 | const.kCostSpeed = 5 86 | const.kCostShield = 10 87 | const.kCostLight = 5 88 | 89 | ---! game data, start from user define no. 100 90 | const.ROCKET_GAMEDATA_START = 100 -- 加入游戏,相当于join game? 91 | const.ROCKET_GAMEDATA_MOVE = 101 -- 移动方向,是绝对的,还是对当前方向有力道的改变(合成)? 92 | const.ROCKET_GAMEDATA_ACTIONS = 102 -- 更新操作码,根据actionMask来判断,比如是不是加速? 93 | const.ROCKET_GAMEDATA_SPECTATE = 103 -- 进入观察模式 94 | const.ROCKET_GAMEDATA_NICKNAME = 104 -- 设置昵称 95 | 96 | const.ROCKET_GAMEDATA_SETBORDER = 110 97 | const.ROCKET_GAMEDATA_ADDNODE = 111 -- PlayerInfo ? 你加入后服务器端发来你的消息 98 | const.ROCKET_GAMEDATA_UPDATEPOSITION = 112 99 | const.ROCKET_GAMEDATA_CLEARNODE = 113 -- 清除自己 100 | const.ROCKET_GAMEDATA_DRAWLINE = 114 101 | const.ROCKET_GAMEDATA_LEADERBOARD = 115 102 | const.ROCKET_GAMEDATA_UPDATENODES = 116 103 | 104 | 105 | --! acl status 106 | const.ROCKET_ACL_STATUS_VERSION_MISMATCH = 101 -- 版本不对 107 | 108 | 109 | return const 110 | 111 | -------------------------------------------------------------------------------- /backend/Algos/NumArray.lua: -------------------------------------------------------------------------------- 1 | --------------------------------- 2 | --! @file 3 | --! @addtogroup NumArray 4 | --! @brief a counted array NumArray 5 | --! @author hr@cronlygames.com 6 | ----------------------------------- 7 | 8 | --! create the class, etc. NumArray 9 | --! create the class metatable 10 | local class = {mt = {}} 11 | class.mt.__index = class 12 | 13 | 14 | --! @brief The creator for NumArray 15 | --! @return return the created NumArray object 16 | local function create () 17 | local self = { 18 | count = 0, 19 | data = {}, 20 | } 21 | setmetatable(self, class.mt) 22 | 23 | return self 24 | end 25 | class.create = create 26 | 27 | --! @brief add an object to this numarray self 28 | --! @param self the numarray 29 | --! @param obj the object to insert 30 | --! @param idx the position for inserted object, default to append at last 31 | local function insertObject (self, obj, idx) 32 | if idx then 33 | table.insert(self.data, idx, obj) 34 | else 35 | table.insert(self.data, obj) 36 | end 37 | self.count = self.count + 1 38 | end 39 | class.insertObject = insertObject 40 | 41 | --! @brief get an object from this numarray 42 | --! @param self the numarray 43 | --! @param idx the position to get the object, default at the last element 44 | local function getObjectAt (self, idx) 45 | idx = idx or self:getCount() 46 | return self.data[idx] 47 | end 48 | class.getObjectAt = getObjectAt 49 | 50 | --! @brief set an object to this numarray 51 | --! @param self the numarray 52 | --! @param obj the object to set or replace 53 | --! @param idx the position to set the object, default replace the last element 54 | local function setObjectAt (self, obj, idx) 55 | idx = idx or self:getCount() 56 | if idx >= 1 and idx <= self:getCount() then 57 | self.data[idx] = obj 58 | end 59 | end 60 | class.setObjectAt = setObjectAt 61 | 62 | --! @brief remove an object from this numarray 63 | --! @param self the numarray 64 | --! @param idx the position to remove the object, must not nil 65 | local function removeObjectAt (self, idx) 66 | table.remove(self.data, idx) 67 | self.count = self.count - 1 68 | end 69 | class.removeObjectAt = removeObjectAt 70 | 71 | --! @brief sort this numarray 72 | --! @param self the numarray 73 | --! @param cmp the comparator 74 | local function sort (self, cmp) 75 | table.sort(self.data, cmp) 76 | end 77 | class.sort = sort 78 | 79 | --! @brief get the count for this numarray 80 | --! @param self the numarray 81 | local function getCount (self) 82 | return self.count 83 | end 84 | class.getCount = getCount 85 | 86 | --! @brief get an random object from this numarray 87 | --! @param self the numarray 88 | --! @note different to NumSet:getRandomObject, this function is not very slow, acceptable 89 | local function getRandomObject (self) 90 | local idx = math.random(self:getCount()) 91 | return self:getObjectAt(idx) 92 | end 93 | class.getRandomObject = getRandomObject 94 | 95 | --! @brief get an raw data table from this numarray 96 | --! @param self the numarray 97 | --! @note don't use it unless you know what you are doing 98 | local function getData (self) 99 | return self.data 100 | end 101 | class.getData = getData 102 | 103 | --! @brief loop elements in this numarray to execute function [handler], 104 | -- until the function [handler] return true or all elements are checked 105 | --! @param self the numarray 106 | --! @param handler the executed function 107 | --! @note remember return true in handler for matched element 108 | local function forEach (self, handler) 109 | local data = self.data 110 | for _,obj in ipairs(data) do 111 | if handler(obj) then 112 | return 113 | end 114 | end 115 | end 116 | class.forEach = forEach 117 | 118 | --! @brief reset the NumArray 119 | local function clear (self) 120 | self.count = 0 121 | self.data = {} 122 | end 123 | class.clear = clear 124 | 125 | --! @brief reset the NumArray 126 | class.reset = clear 127 | 128 | return class 129 | 130 | -------------------------------------------------------------------------------- /backend/client/RemoteSocket.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local socket = require "client.socket" 3 | 4 | 5 | ---! create the class metatable 6 | local class = {mt = {}} 7 | class.mt.__index = class 8 | 9 | class.getTCPSocket = function(self, host, port) 10 | local sock 11 | xpcall(function () 12 | sock = socket.connect(host, port) 13 | end, 14 | function (err) 15 | skynet.error(err) 16 | end) 17 | 18 | if not sock then 19 | return nil 20 | end 21 | 22 | self.host = addr 23 | self.port = port 24 | 25 | return sock 26 | end 27 | 28 | ---! create delegate object 29 | local function create (addr, port) 30 | local self = {} 31 | setmetatable(self, class.mt) 32 | 33 | self.pack_len = nil 34 | self.partial = "" 35 | self.packet = nil 36 | 37 | self.sockfd = self:getTCPSocket(addr, port) 38 | if not self.sockfd then 39 | return nil 40 | end 41 | 42 | return self 43 | end 44 | class.create = create 45 | 46 | class.resetPartial = function (self) 47 | self.packet = nil 48 | self.pack_len = nil 49 | self.partial = "" 50 | end 51 | 52 | class.readHead = function (self) 53 | local flag = true 54 | xpcall(function () 55 | local partial = socket.recv(self.sockfd) 56 | if not partial or partial == "" then 57 | flag = false 58 | else 59 | self.partial = self.partial .. partial 60 | end 61 | if string.len(self.partial) >= 2 then 62 | local head = string.sub(self.partial, 1, 2) 63 | self.pack_len = string.unpack(">I2", head) 64 | self.partial = string.sub(self.partial, 3) 65 | self.packet = nil 66 | end 67 | end, 68 | function (err) 69 | flag = false 70 | print(err) 71 | self:abort() 72 | end) 73 | return flag 74 | end 75 | 76 | class.readBody = function (self) 77 | local flag = true 78 | xpcall(function () 79 | local partial = socket.recv(self.sockfd) 80 | if not partial or partial == "" then 81 | flag = false 82 | else 83 | self.partial = self.partial .. partial 84 | end 85 | if string.len(self.partial) >= self.pack_len then 86 | self.packet = string.sub(self.partial, 1, self.pack_len) 87 | self.partial = string.sub(self.partial, self.pack_len + 1) 88 | self.pack_len = nil 89 | end 90 | end, 91 | function (err) 92 | flag = false 93 | print(err) 94 | self:abort() 95 | end) 96 | return flag 97 | end 98 | 99 | --! @brief receive one valid packet from server 100 | --! @param self the remote socket 101 | --! @param delaySecond delay time, like 5.0, nil means no delay, -1.0 means blocked wait until some bytes arrive 102 | --! @param the packet or nil 103 | local function recvPacket (self, delaySecond) 104 | if not self.sockfd then 105 | return 106 | end 107 | 108 | while true do 109 | if self.packet then 110 | local p = self.packet 111 | self.packet = nil 112 | 113 | return p 114 | end 115 | 116 | if self.pack_len == nil then 117 | if not self:readHead() then 118 | return 119 | end 120 | else 121 | if not self:readBody() then 122 | return 123 | end 124 | end 125 | end 126 | end 127 | class.recvPacket = recvPacket 128 | 129 | ---! @breif send a packet to remote 130 | ---! @param pack is a valid proto data string 131 | local function sendPacket (self, pack) 132 | if not self.sockfd then 133 | return 134 | end 135 | 136 | pack = string.pack(">s2", pack) 137 | 138 | xpcall(function () 139 | socket.send(self.sockfd, pack) 140 | end, 141 | function (err) 142 | self:abort(err) 143 | end) 144 | end 145 | class.sendPacket = sendPacket 146 | 147 | class.isClosed = function (self) 148 | return (self.sockfd == nil) 149 | end 150 | 151 | ---! @brief close 152 | local function close (self, err) 153 | local c = self.sockfd 154 | if c then 155 | socket.close(c) 156 | end 157 | 158 | self:resetPartial() 159 | self.sockfd = nil 160 | end 161 | class.close = close 162 | class.abort = close 163 | 164 | 165 | return class 166 | 167 | -------------------------------------------------------------------------------- /backend/Helpers/TableHelper.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------- 2 | ---! @file 3 | ---! @brief table相关协助库 4 | --------------------------------------------------- 5 | 6 | ---! TableHelper 模块定义 7 | local class = {} 8 | 9 | ---! @brief 判断table是否为空 10 | local function isTableEmpty(table) 11 | local count = 0 12 | for k,v in pairs(table) do 13 | count = count + 1 14 | break 15 | end 16 | return count == 0 17 | end 18 | class.isTableEmpty = isTableEmpty 19 | 20 | ---! @brief 判断数组是否为空 21 | local function isArrayEmpty(table) 22 | local count = 0 23 | for k,v in ipairs(table) do 24 | count = count + 1 25 | break 26 | end 27 | return count == 0 28 | end 29 | class.isArrayEmpty = isArrayEmpty 30 | 31 | ---! @brief 复制数组部分 32 | local function cloneArray(arr) 33 | local test = {} 34 | for i, v in ipairs(arr) do 35 | test[i] = v 36 | end 37 | return test 38 | end 39 | class.cloneArray = cloneArray 40 | 41 | ---! @brief 深度复制table 42 | local function cloneTable(_table) 43 | local tar = {} 44 | for k,v in pairs(_table) do 45 | local vt = type(v) 46 | if vt == "table" then 47 | tar[k] = cloneTable(v) 48 | else 49 | tar[k] = v 50 | end 51 | end 52 | return tar 53 | end 54 | class.cloneTable = cloneTable 55 | 56 | ---! 合并数组 57 | local function mergeArray(dst, src) 58 | table.move(src, 1, #src, #dst + 1, dst) 59 | end 60 | class.mergeArray = mergeArray 61 | 62 | ---! @brief 把源table的内容复制到目标table, 63 | ---! 如果有keys数组, 以数组的元素为key进行复制 64 | local function copyTable(dstTable, srcTable, keys) 65 | if not srcTable then 66 | return 67 | end 68 | if keys then 69 | for _, k in ipairs(keys) do 70 | dstTable[k] = srcTable[k] 71 | end 72 | else 73 | for k, v in pairs(srcTable) do 74 | dstTable[k] = v 75 | end 76 | end 77 | end 78 | class.copyTable = copyTable 79 | 80 | --- encode & decode 81 | local function table_ser (tablevalue, tablekey, mark, assign) 82 | -- 标记当前table, 并记录其key名 83 | mark[tablevalue] = tablekey 84 | -- 记录表中各项 85 | local container = {} 86 | for k, v in pairs(tablevalue) do 87 | -- 序列化key 88 | local keystr = nil 89 | if type(k) == "string" then 90 | keystr = string.format("[\"%s\"]", k) 91 | elseif type(k) == "number" then 92 | keystr = string.format("[%d]", k) 93 | end 94 | 95 | -- 序列化value 96 | local valuestr = nil 97 | if type(v) == "string" then 98 | valuestr = string.format("\"%s\"", tostring(v)) 99 | elseif type(v) == "number" or type(v) == "boolean" then 100 | valuestr = tostring(v) 101 | elseif type(v) == "table" then 102 | -- 获得从根表到当前表项的完整key, tablekey(代表tablevalue的key), mark[v]代表table v的key 103 | local fullkey = string.format("%s%s", tablekey, keystr) 104 | if mark[v] then table.insert(assign, string.format("%s=%s", fullkey, mark[v])) 105 | else valuestr = class.table_ser(v, fullkey, mark, assign) 106 | end 107 | end 108 | 109 | if keystr and valuestr then 110 | local keyvaluestr = string.format("%s=%s", keystr, valuestr) 111 | table.insert(container, keyvaluestr) 112 | end 113 | end 114 | return string.format("{%s}", table.concat(container, ",")) 115 | end 116 | class.table_ser = table_ser 117 | 118 | local function encode (var) 119 | assert(type(var)=="table") 120 | -- 标记所有出现的table, 并记录其key, 用于处理循环引用 121 | local mark = {} 122 | -- 用于记录循环引用的赋值语句 123 | local assign = {} 124 | -- 序列化表, ret字符串必须与后面的loca ret=%s中的ret相同,因为这个ret可能也会组织到结果字符串中。 125 | local data = class.table_ser(var, "data", mark, assign) 126 | local data = string.format("local data=%s %s; return data", data, table.concat(assign, ";")) 127 | return data 128 | end 129 | class.encode = encode 130 | 131 | local function decode (data) 132 | local res = nil 133 | xpcall(function() 134 | local func = load(data) 135 | if func then 136 | res = func() 137 | end 138 | end, function(err) 139 | res = nil 140 | print(err) 141 | print(debug.traceback()) 142 | end) 143 | return res 144 | end 145 | class.decode = decode 146 | 147 | return class 148 | 149 | -------------------------------------------------------------------------------- /backend/HallServer/HallService.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------- 2 | ---! @file HallService 3 | ---! @brief 游戏大厅核心服务 4 | -------------------------------------------------------------- 5 | 6 | ---! 系统库 7 | local skynet = require "skynet" 8 | 9 | ---! 依赖库 10 | local clsHelper = require "ClusterHelper" 11 | local packetHelper = (require "PacketHelper").create("protos/CGGame.pb") 12 | 13 | ---! hall interface 14 | local hallInterface = nil 15 | local nodeInfo = nil 16 | 17 | local function getValidPlayer(code, sign) 18 | local player = hallInterface.onlineUsers:getObject(code) 19 | if not player then 20 | print("No such user found", code, sign) 21 | return 22 | elseif player.FUserCode ~= code or player.agentSign ~= sign then 23 | print("User info not match", player.FUserCode, code, player.agentSign, sign) 24 | return 25 | end 26 | return player 27 | end 28 | 29 | ---! 服务接口 30 | local CMD = {} 31 | 32 | function CMD.createInterface (conf) 33 | hallInterface = hallInterface or packetHelper.createObject(conf.Interface, conf) 34 | return 0 35 | end 36 | 37 | function CMD.agentQuit (code, sign) 38 | local player = getValidPlayer(code, sign) 39 | hallInterface:agentQuit(player) 40 | return 0 41 | end 42 | 43 | function CMD.joinHall (agentInfo) 44 | agentInfo.apiLevel = 0 45 | local userCode = hallInterface:addPlayer(agentInfo) 46 | hallInterface:SendHallText(userCode) 47 | return userCode 48 | end 49 | 50 | function CMD.hallData (code, sign, hallType, data) 51 | local player = getValidPlayer(code, sign) 52 | hallInterface:handleHallData(player, hallType, data) 53 | return 0 54 | end 55 | 56 | function CMD.joinGame (agentInfo) 57 | agentInfo.apiLevel = 1 58 | local userCode = hallInterface:addPlayer(agentInfo) 59 | hallInterface:SendGameText(userCode) 60 | return userCode 61 | end 62 | 63 | function CMD.clubData (code, sign, clubType, data) 64 | local player = getValidPlayer(code, sign) 65 | hallInterface:handleClubData(player, clubType, data) 66 | return 0 67 | end 68 | 69 | function CMD.roomData (code, sign, roomType, data) 70 | local player = getValidPlayer(code, sign) 71 | hallInterface:handleRoomData(player, roomType, data) 72 | return 0 73 | end 74 | 75 | function CMD.gameData (code, sign, gameType, data) 76 | local player = getValidPlayer(code, sign) 77 | hallInterface:handleGameData(player, gameType, data) 78 | return 0 79 | end 80 | 81 | function CMD.logStat () 82 | hallInterface:logStat() 83 | return 0 84 | end 85 | 86 | function CMD.noticeAll (msg) 87 | if not hallInterface then 88 | return 0 89 | end 90 | 91 | hallInterface.onlineUsers:forEach(function (player) 92 | hallInterface:sendPacketToUser(msg, player.FUserCode) 93 | end) 94 | 95 | return 1 96 | end 97 | 98 | local function game_loop() 99 | if hallInterface then 100 | skynet.timeout(hallInterface.tickInterval, game_loop) 101 | xpcall( function() 102 | hallInterface:tick(hallInterface.tickInterval/100) 103 | 104 | local cnt = hallInterface.onlineUsers:getCount() 105 | local bn = hallInterface.config.BotNum or 0 106 | cnt = cnt - bn 107 | skynet.call(nodeInfo, "lua", "updateConfig", cnt, "nodeInfo", "numPlayers") 108 | 109 | local ret, nodeLink = pcall(skynet.call, nodeInfo, "lua", "getServiceAddr", clsHelper.kNodeLink) 110 | if ret and nodeLink ~= "" then 111 | pcall(skynet.send, nodeLink, "lua", "heartBeat", cnt) 112 | end 113 | end, 114 | function(err) 115 | skynet.error(err) 116 | skynet.error(debug.traceback()) 117 | end) 118 | else 119 | skynet.timeout(100, game_loop) 120 | end 121 | end 122 | 123 | skynet.start(function() 124 | ---! 注册skynet消息服务 125 | skynet.dispatch("lua", function(_,_, cmd, ...) 126 | local f = CMD[cmd] 127 | if f then 128 | local ret = f(...) 129 | if ret then 130 | skynet.ret(skynet.pack(ret)) 131 | end 132 | else 133 | skynet.error("unknown command ", cmd) 134 | end 135 | end) 136 | 137 | ---! 获得NodeInfo 服务 注册自己 138 | nodeInfo = skynet.uniqueservice(clsHelper.kNodeInfo) 139 | skynet.call(nodeInfo, "lua", "updateConfig", skynet.self(), clsHelper.kHallService) 140 | 141 | ---! 游戏循环 142 | skynet.timeout(5, game_loop) 143 | end) 144 | 145 | -------------------------------------------------------------------------------- /backend/MainServer/MySQLService.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------ 2 | ---! @file 3 | ---! @brief DBService, 数据库服务,调用redis/mysql 4 | ------------------------------------------------------ 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | local mysql = require "skynet.db.mysql" 9 | 10 | ---! 帮助库 11 | local clsHelper = require "ClusterHelper" 12 | 13 | ---! 全局常量 14 | local hosts 15 | local conf 16 | local mysql_conn 17 | local wait_list = {} 18 | 19 | ---! 恢复之前的执行序列 20 | local function resume () 21 | while true do 22 | local co = table.remove(wait_list) 23 | if not co then 24 | return 25 | end 26 | skynet.wakeup(co) 27 | end 28 | end 29 | 30 | ---! do make 31 | local function do_make_mysql_conn (host) 32 | conf.host = host 33 | local db = mysql.connect(conf) 34 | if db then 35 | if mysql_conn then 36 | mysql_conn:disconnect() 37 | end 38 | mysql_conn = db 39 | resume() 40 | return true 41 | else 42 | skynet.error("can't connect to mysql ", conf.host, conf.port) 43 | end 44 | end 45 | 46 | ---! 连接到数据库 47 | local function make_mysql_conn () 48 | if mysql_conn then 49 | return 50 | end 51 | 52 | local ret 53 | repeat 54 | for _, h in ipairs(hosts) do 55 | ret = pcall(do_make_mysql_conn, h) 56 | if ret then 57 | break 58 | end 59 | end 60 | skynet.sleep(100) 61 | until ret 62 | end 63 | 64 | ---! mysql数据库连接不正常,暂停 65 | local function pause () 66 | skynet.fork(make_mysql_conn) 67 | local co = coroutine.running() 68 | table.insert(wait_list, co) 69 | skynet.wait(co) 70 | end 71 | 72 | ---! checked call sql cmd 73 | local function checked_call (cmd) 74 | while true do 75 | if not mysql_conn then 76 | pause() 77 | end 78 | local ret, val = pcall(mysql_conn.query, mysql_conn, cmd) 79 | if not ret then 80 | mysql_conn:disconnect() 81 | mysql_conn = nil 82 | pause() 83 | else 84 | return val 85 | end 86 | end 87 | end 88 | 89 | 90 | ---! lua commands 91 | local CMD = {} 92 | 93 | ---! exec sql command 94 | function CMD.execDB (cmd) 95 | return checked_call(cmd) 96 | end 97 | 98 | ---! load all from table 99 | function CMD.loadDB (tableName, keyName, keyValue, noInsert) 100 | local cmd = string.format("SELECT * FROM %s WHERE %s='%s' LIMIT 1", tableName, keyName, keyValue) 101 | local ret = checked_call(cmd) 102 | local row = ret[1] or {} 103 | 104 | ---! insert where there is no such key/value 105 | if not noInsert and not row[keyName] then 106 | skynet.error(keyName, "=", keyValue, "is not found in ", tableName, ", should insert") 107 | local ins = string.format("INSERT %s (%s) VALUES ('%s')", tableName, keyName, keyValue) 108 | ret = checked_call(ins) 109 | 110 | ret = checked_call(cmd) 111 | row = ret[1] or {} 112 | end 113 | 114 | return row 115 | end 116 | 117 | ---! update value 118 | function CMD.updateDB (tableName, keyName, keyValue, fieldName, fieldValue) 119 | local cmd = string.format("UPDATE %s SET %s='%s' WHERE %s='%s'", 120 | tableName, fieldName, fieldValue, keyName, keyValue) 121 | return checked_call(cmd) 122 | end 123 | 124 | ---! delta value 125 | function CMD.deltaDB (tableName, keyName, keyValue, fieldName, deltaValue) 126 | local cmd = string.format("UPDATE %s SET %s=%s+'%s' WHERE %s='%s'", 127 | tableName, fieldName, fieldName, 128 | deltaValue, keyName, keyValue) 129 | return checked_call(cmd) 130 | end 131 | 132 | ---! 服务的启动函数 133 | skynet.start(function() 134 | ---! 初始化随机数 135 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 136 | 137 | ---! 注册skynet消息服务 138 | skynet.dispatch("lua", function(_,_, cmd, ...) 139 | local f = CMD[cmd] 140 | if f then 141 | local ret = f(...) 142 | if ret then 143 | skynet.ret(skynet.pack(ret)) 144 | end 145 | else 146 | skynet.error("unknown command ", cmd) 147 | end 148 | end) 149 | 150 | ---! 加载配置 151 | local packetHelper = require "PacketHelper" 152 | local config = packetHelper.load_config("./config/config.mysql") 153 | conf = config.DB_Conf 154 | conf.on_connect = function (db) 155 | db:query("set charset utf8"); 156 | end 157 | 158 | hosts = config.Hosts 159 | 160 | ---! 注册自己的地址 161 | local srv = skynet.uniqueservice(clsHelper.kNodeInfo) 162 | skynet.call(srv, "lua", "updateConfig", skynet.self(), clsHelper.kMySQLService) 163 | end) 164 | 165 | -------------------------------------------------------------------------------- /backend/MainServer/RedisService.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------ 2 | ---! @file 3 | ---! @brief DBService, 数据库服务,调用redis/mysql 4 | ------------------------------------------------------ 5 | 6 | ---! 依赖库 7 | local skynet = require "skynet" 8 | local redis = require "skynet.db.redis" 9 | 10 | ---! 帮助库 11 | local clsHelper = require "ClusterHelper" 12 | 13 | ---! 全局常量 14 | local conf 15 | local redis_conn 16 | local wait_list = {} 17 | 18 | ---! 从哨兵那里获取真正的master 19 | local function get_master_info () 20 | local conn = { 21 | port = conf.port, 22 | } 23 | for _, h in ipairs(conf.hosts) do 24 | conn.host = h 25 | local ret, db = pcall(redis.connect, conn) 26 | if ret and db then 27 | local ret = db:sentinel("get-master-addr-by-name", "master") 28 | db:disconnect() 29 | return ret[1], ret[2] 30 | end 31 | end 32 | end 33 | 34 | ---! 恢复等候的队列 35 | local function resume () 36 | while true do 37 | local co = table.remove(wait_list) 38 | if not co then 39 | return 40 | end 41 | skynet.wakeup(co) 42 | end 43 | end 44 | 45 | ---! do make 46 | local function do_make_redis_conn () 47 | local conn = {} 48 | conn.host, conn.port = get_master_info () 49 | if not conn.host then 50 | return 51 | end 52 | 53 | local ret, db = pcall(redis.connect, conn) 54 | if ret and db then 55 | if redis_conn then 56 | redis_conn:disconnect() 57 | end 58 | redis_conn = db 59 | resume() 60 | return true 61 | else 62 | skynet.error("can't connect to master", conn.host, conn.port) 63 | end 64 | end 65 | 66 | ---! make a redis connection 67 | local function make_redis_conn () 68 | if redis_conn then 69 | return 70 | end 71 | 72 | local ret 73 | repeat 74 | ret = pcall(do_make_redis_conn) 75 | skynet.sleep(100) 76 | until ret 77 | end 78 | 79 | ---! wait for connection 80 | local function pause () 81 | skynet.fork(make_redis_conn) 82 | local co = coroutine.running() 83 | table.insert(wait_list, co) 84 | skynet.wait(co) 85 | end 86 | 87 | ---! run redis command in protected mode 88 | local function checked_call (cmd, key, ...) 89 | while true do 90 | if not redis_conn then 91 | pause() 92 | end 93 | local ret, val = pcall(redis_conn[cmd], redis_conn, key, ...) 94 | if not ret then 95 | skynet.error("redis cmd failed", cmd, key, ...) 96 | redis_conn:disconnect() 97 | redis_conn = nil 98 | pause() 99 | else 100 | return val or "" 101 | end 102 | end 103 | end 104 | 105 | ---! generate redis path 106 | local function format_path (tableName, keyName, keyValue) 107 | return string.format("DB_CGGame.%s.%s.%s", tableName, keyName, keyValue) 108 | end 109 | 110 | ---! lua commands 111 | local CMD = {} 112 | 113 | ---! run redis cmd 114 | function CMD.runCmd (cmd, key, ...) 115 | return checked_call(cmd, key, ...) 116 | end 117 | 118 | ---! load db from redis 119 | function CMD.loadDB (tableName, keyName, keyValue, info) 120 | local path = format_path(tableName, keyName, keyValue) 121 | for field, value in pairs(info) do 122 | local old = checked_call("HGET", path, field) 123 | checked_call("HSET", path, field, value) 124 | end 125 | return checked_call("EXPIRE", path, conf.expire) 126 | end 127 | 128 | ---! update to redis, and mysql; 直接覆盖 129 | function CMD.updateDB (tableName, keyName, keyValue, fieldName, fieldValue) 130 | local path = format_path(tableName, keyName, keyValue) 131 | checked_call("HSET", path, fieldName, fieldValue) 132 | return checked_call("EXPIRE", path, conf.expire) 133 | end 134 | 135 | ---! 增量修改 136 | function CMD.deltaDB (tableName, keyName, keyValue, fieldName, deltaValue) 137 | local path = format_path(tableName, keyName, keyValue) 138 | checked_call("HINCRBYFLOAT", path, fieldName, deltaValue) 139 | return checked_call("EXPIRE", path, conf.expire) 140 | end 141 | 142 | ---! 服务的启动函数 143 | skynet.start(function() 144 | ---! 初始化随机数 145 | math.randomseed( tonumber(tostring(os.time()):reverse():sub(1,6)) ) 146 | 147 | ---! 注册skynet消息服务 148 | skynet.dispatch("lua", function(_,_, cmd, ...) 149 | local f = CMD[cmd] 150 | if f then 151 | local ret = f(...) 152 | if ret then 153 | skynet.ret(skynet.pack(ret)) 154 | end 155 | else 156 | skynet.error("unknown command ", cmd) 157 | end 158 | end) 159 | 160 | ---! 加载配置 161 | local packetHelper = require "PacketHelper" 162 | conf = packetHelper.load_config("./config/config.redis") 163 | 164 | ---! 注册自己的地址 165 | local srv = skynet.uniqueservice(clsHelper.kNodeInfo) 166 | skynet.call(srv, "lua", "updateConfig", skynet.self(), clsHelper.kRedisService) 167 | end) 168 | 169 | -------------------------------------------------------------------------------- /backend/Algos/NumSet.lua: -------------------------------------------------------------------------------- 1 | --------------------------------- 2 | --! @file 3 | --! @addtogroup NumSet 4 | --! @brief a counted hash map NumSet 5 | --! @author Zhou Weikuan 3674327@qq.com 6 | ----------------------------------- 7 | 8 | --! reference to math library 9 | local math = math 10 | 11 | --! create the class, NumSet 12 | local class = {mt = {}} 13 | 14 | --! create the class metatable 15 | class.mt.__index = class 16 | 17 | --! @brief The creator for NumSet 18 | --! @return return the created NumSet object 19 | local function create () 20 | local self = { 21 | count = 0, 22 | data = {}, 23 | } 24 | setmetatable(self, class.mt) 25 | 26 | return self 27 | end 28 | class.create = create 29 | 30 | --! @brief add an object to this numset self 31 | --! @param self the numset 32 | --! @param obj the object to add 33 | --! @param key the key for added object, default the object itself 34 | --! @return 35 | local function addObject(self, obj, key) 36 | key = key or obj 37 | if not self.data[key] then 38 | self.count = self.count + 1 39 | end 40 | self.data[key] = obj 41 | end 42 | class.addObject = addObject 43 | 44 | --! @brief remove an object from this numset self 45 | --! @param self the numset 46 | --! @param obj the object to remove 47 | --! @param key the key for removed object, default the object obj 48 | local function removeObject(self, obj, key) 49 | key = key or obj 50 | if self.data[key] then 51 | self.count = self.count - 1 52 | end 53 | self.data[key] = nil 54 | end 55 | class.removeObject = removeObject 56 | 57 | --! @brief remove objects from this numset 58 | --! @param objs the objects to remove 59 | --! @param keys the corresponding keys for each object, i.e. key=keys[one object], or key = object by default 60 | --! @return return None. 61 | local function removeObjects (self, objs, keys) 62 | for _, obj in ipairs(objs) do 63 | local k = keys and keys[obj] or obj 64 | self:removeObject(obj, k) 65 | end 66 | end 67 | class.removeObjects = removeObjects 68 | 69 | --! @brief retrieve one object from this numset 70 | --! @param key the key for the object to retrieve 71 | --! @return return the object 72 | local function getObject (self, key) 73 | local obj = self.data[key] 74 | return obj 75 | end 76 | class.getObject = getObject 77 | 78 | --! @brief check one object if exists in the numset 79 | --! @param obj the object to check 80 | --! @param key the key for the object to check, default the object itself 81 | --! @return return true if the object with key exists 82 | local function hasObject (self, obj, key) 83 | key = key or obj 84 | local o = self.data[key] 85 | if o and o == obj then 86 | return true 87 | end 88 | end 89 | class.hasObject = hasObject 90 | 91 | --! @brief get one random object from the numset 92 | --! @return return the random object 93 | --! @note it is VERY slow, caution to use it 94 | local function getRandomObject (self) 95 | local idx = math.random(self:getCount()) 96 | local pos = 0 97 | local ret = nil 98 | self:forEach(function (obj) 99 | pos = pos + 1 100 | if pos == idx then 101 | ret = obj 102 | return true 103 | end 104 | end) 105 | 106 | return ret 107 | end 108 | class.getRandomObject = getRandomObject 109 | 110 | --! @brief get count of objects for the numset 111 | --! @return return the count, default 0 112 | local function getCount (self) 113 | return self.count 114 | end 115 | class.getCount = getCount 116 | 117 | --! @brief check whether this numset is equal to other numset 118 | --! @param other the other numset 119 | --! @return return true if their metatable, count, and elements all equal 120 | local function isEqual (self, other) 121 | if getmetatable(self) ~= getmetatable(other) then 122 | -- ed.cclog("metable not same") 123 | return nil 124 | end 125 | 126 | if self:getCount() ~= other:getCount() then 127 | -- ed.cclog("count not same") 128 | return nil 129 | end 130 | 131 | local ret = true 132 | self:forEach(function(obj) 133 | if not other:hasObject(obj) then 134 | -- ed.cclog("element not same") 135 | return nil 136 | end 137 | end) 138 | 139 | return ret 140 | end 141 | class.isEqual = isEqual 142 | 143 | --! @brief loop elements in this numset to execute function [handler], 144 | -- until the function [handler] return true or all elements are checked 145 | --! @param handler the function executed 146 | --! @return break if any element matches handler, or all elements checked 147 | local function forEach (self, handler) 148 | local data = self.data 149 | for _,obj in pairs(data) do 150 | if handler(obj) then 151 | return 152 | end 153 | end 154 | end 155 | class.forEach = forEach 156 | 157 | --! @brief reset the numset 158 | local function clear (self) 159 | self.count = 0 160 | self.data = {} 161 | end 162 | class.clear = clear 163 | 164 | --! reset the numset 165 | class.reset = clear 166 | 167 | return class 168 | 169 | -------------------------------------------------------------------------------- /backend/Stages/Mode_Rocket.lua: -------------------------------------------------------------------------------- 1 | local NumSet = require "NumSet" 2 | local const = require "Const_Rocket" 3 | 4 | local math = math 5 | 6 | ------------------------ GameMode ------------------------------ 7 | local class = {mt = {}} 8 | local GameMode = class 9 | class.mt.__index = class 10 | 11 | class.create = function () 12 | local self = {} 13 | setmetatable(self, class.mt) 14 | 15 | self.modeId = -1 16 | self.name = "Blank" 17 | 18 | self.specByLeaderboard = false -- false = spectate from player list instead of leaderboard 19 | 20 | return self 21 | end 22 | 23 | -- Override these 24 | class.onServerInit = function(self, gameServer) 25 | -- Called when the server starts 26 | gameServer.run = true 27 | end 28 | 29 | class.onTick = function(self, gameServer) 30 | -- Called on every game tick 31 | end 32 | 33 | class.onChange = function(self, gameServer) 34 | -- Called when someone changes the gamemode via console commands 35 | end 36 | 37 | class.onPlayerInit = function(self, player) 38 | -- Called after a player object is constructed 39 | end 40 | 41 | class.onPlayerSpawn = function(self, gameServer, player) 42 | -- Called when a player is spawned 43 | -- if not player.skin then 44 | -- player.skin = gameServer:getOneSkin() -- Random skin 45 | -- end 46 | gameServer:spawnPlayer(player) 47 | end 48 | 49 | class.onCellAdd = function(self, cell) 50 | -- Called when a player cell is added 51 | end 52 | 53 | class.onCellRemove = function(self, cell) 54 | -- Called when a player cell is removed 55 | end 56 | 57 | class.onCellMove = function(self, x1, y1,cell) 58 | -- Called when a player cell is moved 59 | end 60 | 61 | class.onCellCollide = function (self, nodeA, nodeB) 62 | -- Called when two player cell is collided 63 | end 64 | 65 | class.updateLB = function(self, gameServer) 66 | -- Called when the leaderboard update function is called 67 | end 68 | 69 | class.isSameTeam = function (self, teamA, teamB) 70 | return teamA == teamB 71 | end 72 | 73 | ------------------------ Mode_FFA ---------------------------------- 74 | local class = {mt = {}} 75 | local Mode_FreeForAll = class 76 | class.mt.__index = class 77 | 78 | local base = GameMode 79 | setmetatable(class, base.mt) 80 | 81 | class.create = function (...) 82 | local self = base.create(...) 83 | setmetatable(self, class.mt) 84 | 85 | self.modeId = 1 86 | self.name = "Free For All" 87 | self.specByLeaderboard = false -- true 88 | 89 | return self 90 | end 91 | 92 | -- Gamemode Specific Functions 93 | 94 | class.leaderboardAddSort = function(self, player, leaderboard) 95 | -- Adds the player and sorts the leaderboard 96 | local len = leaderboard:getCount() 97 | local loop = true 98 | while len >= 1 and loop do 99 | -- Start from the bottom of the leaderboard 100 | if player:getScore(false) <= leaderboard:getObjectAt(len):getScore(false) then 101 | leaderboard:insertObject(player, len+1) 102 | loop = false -- End the loop if a spot is found 103 | end 104 | len = len - 1 105 | end 106 | 107 | if loop then 108 | -- Add to top of the list because no spots were found 109 | leaderboard:insertObject(player, 1) 110 | end 111 | end 112 | 113 | -- Override 114 | class.updateLB = function(self, gameServer) 115 | local top 116 | local lb = gameServer.leaderboard 117 | -- Loop through all clients 118 | local handler = function(client) 119 | local tracker = client.playerTracker 120 | local playerScore = tracker:getScore(true) 121 | if tracker.plane then 122 | if lb:getCount() == 0 then 123 | -- Initial player 124 | lb:insertObject(tracker) 125 | elseif lb:getCount() < 10 then 126 | self:leaderboardAddSort(tracker,lb) 127 | else 128 | -- 10 in leaderboard already 129 | if playerScore > lb:getObjectAt(10):getScore(false) then 130 | lb:removeObjectAt(10) 131 | self:leaderboardAddSort(tracker,lb) 132 | end 133 | end 134 | end 135 | end 136 | gameServer.clients:forEach(handler) 137 | 138 | local info = { 139 | ffa_data = {}, 140 | } 141 | lb:forEach(function (tracker) 142 | local one = { 143 | nodeId = tracker.plane and tracker.plane.nodeId or nil, 144 | name = tracker:getName(), 145 | score = tracker:getScore(), 146 | } 147 | 148 | if not top then 149 | top = { 150 | name = one.name, 151 | score = one.score, 152 | } 153 | end 154 | 155 | table.insert(info.ffa_data, one) 156 | end) 157 | 158 | return info, top 159 | end 160 | 161 | 162 | ----------- return the game mode ------------------------------- 163 | GameMode.get = function(mode) 164 | local cls = Mode_FreeForAll 165 | 166 | return cls.create() 167 | end 168 | 169 | return GameMode 170 | 171 | -------------------------------------------------------------------------------- /backend/protos/CGGame.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package CGGame; 4 | 5 | /// @brief the main protocol message, we should parse out protoCode first, 6 | /// and then if possible, use other Message to parse out protoBody 7 | message ProtoInfo { 8 | optional int32 mainType = 1; 9 | optional int32 subType = 2; 10 | optional bytes msgBody = 3; 11 | } 12 | 13 | /// protoTypes.CGGAME_PROTO_SUBTYPE_MULTIPLE = 1 14 | message MultiBody { 15 | optional int32 curIndex = 1; 16 | optional int32 maxIndex = 2; 17 | optional bytes msgBody = 3; 18 | } 19 | 20 | /// protoTypes.CGGAME_PROTO_SUBTYPE_ACL = 2 21 | message AclInfo { 22 | optional int32 aclType = 1; 23 | optional bytes aclMsg = 2; 24 | } 25 | 26 | /// protoTypes.CGGAME_PROTO_SUBTYPE_HEARTBEAT = 3 27 | message HeartBeat { 28 | optional int32 fromType = 1; 29 | optional double timestamp = 2; 30 | optional bytes networkType = 3; /// WiFi, 3G, 4G, etc... 31 | } 32 | 33 | /// protoTypes.CGGAME_PROTO_SUBTYPE_AGENTLIST = 4 34 | message AgentList { 35 | message OneAgent { 36 | optional bytes name = 1; 37 | optional bytes addr = 2; 38 | optional int32 port = 3; 39 | optional int32 numPlayers = 4; 40 | } 41 | 42 | repeated OneAgent agents = 1; 43 | } 44 | 45 | /// protoTypes.CGGAME_PROTO_SUBTYPE_NOTICE = 5 46 | message NoticeInfo { 47 | optional int32 noticeType = 1; // 0, text, 1 app voice, amr-wb, 2, 48 | optional bytes noticeText = 2; 49 | } 50 | 51 | /// protoTypes.CGGAME_PROTO_MAINTYPE_AUTH = 10 52 | message AuthInfo { 53 | optional bytes playerId = 1; 54 | optional bytes password = 2; 55 | optional bytes challenge = 3; 56 | optional bytes clientkey = 4; 57 | optional bytes serverkey = 5; 58 | optional bytes hmac = 6; 59 | optional bytes etoken = 7; 60 | optional int32 authIndex = 8; 61 | } 62 | 63 | /// protoTypes.CGGAME_PROTO_SUBTYPE_HALLJOIN 64 | message HallInfo { 65 | optional int32 gameId = 1; 66 | optional int32 gameVersion = 2; 67 | optional int32 lowVersion = 3; 68 | optional int32 gameMode = 4; 69 | optional int32 numPlayers = 5; 70 | optional int32 maxPlayers = 6; 71 | 72 | optional int32 FUserCode = 10; 73 | optional bytes appName = 11; 74 | } 75 | 76 | /// protoTypes.CGGAME_PROTO_SUBTYPE_USERINFO 77 | message UserInfo { 78 | repeated bytes fieldNames = 1; 79 | 80 | optional int32 FUserCode = 2; // user code, different in games, to replace uid 81 | optional bytes FUniqueID = 3; // unique id, or uid, same for all games 82 | optional bytes FPassword = 4; 83 | optional bytes FNickName = 5; 84 | optional bytes FOSType = 6; // device: iPhone, iPad, Mac, Win, Linux, Android, 85 | optional bytes FPlatform = 7; // appstore: huawei, xiaomi, oppo, vivo, ... 86 | optional bytes FUserName = 8; 87 | 88 | optional bytes FMobile = 10; 89 | optional bytes FIDCard = 11; 90 | optional bytes FEmail = 12; 91 | optional bytes FRegTime = 13; 92 | 93 | optional bytes FLastIP = 15; 94 | optional bytes FLastLoginTime = 16; 95 | optional double FTotalTime = 17; 96 | 97 | optional int32 FAvatarID = 20; // avatar 3 98 | optional bytes FAvatarUrl = 21; // avatar 1 99 | optional bytes FAvatarData = 22; // avatar 2 100 | 101 | optional bytes FLocation = 25; 102 | optional double FLongitude = 26; 103 | optional double FLatitude = 27; 104 | optional double FAltitude = 28; 105 | 106 | optional double FNetSpeed = 30; 107 | } 108 | 109 | /// protoTypes.CGGAME_PROTO_SUBTYPE_SITDOWN 110 | message SeatInfo { 111 | optional int32 roomId = 1; 112 | optional int32 seatId = 2; 113 | optional int32 userCode = 3; 114 | } 115 | 116 | message WaitUserInfo { 117 | optional int32 tableStatus = 1; 118 | optional int32 timeout = 2; 119 | optional int32 waitMask = 3; 120 | } 121 | 122 | message ChatInfo { 123 | optional int32 gameId = 1; 124 | optional int32 chatType = 2; // 0, text, 1 app voice, amr-wb, 2, 125 | optional int32 listenerId = 3; // listener table's tableId 126 | optional int32 speekerCode = 4; // speeker's FUserCode 127 | optional bytes speakerNick = 5; 128 | optional bytes chatText = 6; 129 | } 130 | 131 | message RoomInfo { 132 | optional int32 roomId = 1; 133 | optional int32 expireTime = 2; 134 | optional int32 openTime = 3; 135 | optional int32 gameCount = 4; // gameCount == 0? 看roomDetails 136 | optional int32 ownerCode = 5; // owner's FUserCode 137 | optional bytes ownerName = 6; 138 | optional bytes roomDetails = 8; 139 | } 140 | 141 | message ExitInfo { 142 | optional int32 roomId = 1; 143 | optional int32 timeout = 2; 144 | optional int32 mask = 3; 145 | 146 | optional int32 seatId = 4; 147 | optional int32 ownerCode = 5; // owner's FUserCode 148 | } 149 | 150 | // sendTableMap 151 | message TableMapInfo { 152 | optional bytes field = 1; 153 | repeated int32 userCode = 2; // FUserCode 154 | repeated int32 seatId = 3; 155 | } 156 | 157 | message GiftInfo { 158 | optional bytes giftName = 1; 159 | optional int32 coinCost = 2; 160 | optional int32 srcSeatId = 3; 161 | optional int32 dstSeatId = 4; 162 | } 163 | 164 | message ShareBonusInfo { 165 | optional int32 maxCount = 1; 166 | optional int32 curCount = 2; 167 | optional int32 bonusNum = 3; 168 | } 169 | 170 | -------------------------------------------------------------------------------- /backend/Helpers/DBHelper.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------- 2 | ---! @file 3 | ---! @brief 数据库相关协助库 4 | --------------------------------------------------- 5 | 6 | ---! DBHelper 模块定义 7 | local class = {} 8 | 9 | ---! 时间戳 10 | class.timestamp = function () 11 | local ts = os.date('%Y-%m-%d %H:%M:%S') 12 | return ts 13 | end 14 | 15 | ---! 传入时间戳,获得相差天数 16 | class.getDiffDate = function (old, now) 17 | if not old or not now then 18 | return 10 19 | end 20 | 21 | local told = os.date("*t", old) 22 | local tnew = os.date("*t", now) 23 | 24 | told.hour = 0; told.min = 0; told.sec = 0 25 | tnew.hour = 0; tnew.min = 0; tnew.sec = 0 26 | 27 | local diff = os.difftime(os.time(tnew),os.time(told)) 28 | diff = diff / (24 * 60 * 60) 29 | diff = math.floor(diff) 30 | 31 | return diff 32 | end 33 | 34 | ---! 去掉不合法的字符,避免干扰sql语句 35 | class.trimSQL = function (text, length) 36 | if not text or type(text) ~= 'string' then 37 | return text 38 | end 39 | local pat = "^$()%[]*+?`'\"!;{}@"; 40 | for k=1,string.len(pat) do 41 | local one = string.sub(pat, k, k) 42 | one = "%" .. one 43 | text = string.gsub(text, one, ''); 44 | end 45 | 46 | if length then 47 | text = string.sub(text, 1, length); 48 | end 49 | 50 | return text 51 | end 52 | 53 | ---! 处理where的条件 对 值 进行处理 54 | local function kv_copy (obj) 55 | local ele = {} 56 | for k, v in pairs(obj) do 57 | ele[k] = class.trimSQL(v) 58 | end 59 | return ele 60 | end 61 | 62 | ---! 条件合法判断 63 | local function is_keys_ok (obj, keys) 64 | local ok = #keys >= 1 65 | if not ok then return ok end 66 | for _, k in pairs(keys) do 67 | if not obj[k] or obj[k] == '' then 68 | ok = nil 69 | end 70 | end 71 | 72 | return ok 73 | end 74 | 75 | ---! 形成 where 子句, {[k] = v, ...} 76 | class.getKeys = function (keys) 77 | local str = '' 78 | for k, v in pairs(keys) do 79 | local val = class.trimSQL(v) 80 | if str == '' then 81 | str = string.format("%s='%s'", k, val) 82 | else 83 | str = str .. string.format(" AND %s='%s'", k, val) 84 | end 85 | end 86 | 87 | return str 88 | end 89 | 90 | ---! 形成 where 子句 91 | local function get_where (ele, keys) 92 | local str = '' 93 | for _, k in ipairs(keys) do 94 | if ele[k] then 95 | if str == '' then 96 | str = string.format("%s='%s'", k, ele[k]) 97 | else 98 | str = str .. string.format(" AND %s='%s'", k, ele[k]) 99 | end 100 | end 101 | end 102 | 103 | return str 104 | end 105 | 106 | ---! 形成插入语句体 107 | local function get_insert_body (ele) 108 | -- () values () 109 | local ks = '' 110 | local vs = '' 111 | for k, v in pairs(ele) do 112 | if ks == '' then 113 | ks = k 114 | else 115 | ks = ks .. string.format(", %s", k) 116 | end 117 | 118 | if vs == '' then 119 | vs = string.format("'%s'", v) 120 | else 121 | vs = vs .. string.format(", '%s'", v) 122 | end 123 | end 124 | ks = "(" .. ks .. ")" 125 | vs = "(" .. vs .. ")" 126 | 127 | local str = ks .. " VALUES " .. vs 128 | return str 129 | end 130 | 131 | ---! 形成更新语句体 132 | local function get_update_body (ele) 133 | local str = '' 134 | for k, v in pairs(ele) do 135 | if str == '' then 136 | str = string.format("%s='%s'", k, v) 137 | else 138 | str = str .. string.format(", %s='%s'", k, v) 139 | end 140 | end 141 | 142 | return str 143 | end 144 | 145 | ---! 形成选择语句体 146 | local function get_select_body (ele) 147 | local str = '' 148 | for k, v in pairs(ele) do 149 | if str == '' then 150 | str = string.format("%s", k) 151 | else 152 | str = str .. string.format(", %s", k) 153 | end 154 | end 155 | if str == '' then 156 | str = '*' 157 | end 158 | return str 159 | end 160 | 161 | ---! 形成完整的插入语句 162 | class.insert_sql = function (tableName, obj, keys) 163 | local ele = kv_copy(obj) 164 | 165 | -- insert command 166 | local cmd = string.format("INSERT %s ", tableName) 167 | 168 | local body = get_insert_body(ele) 169 | 170 | local sql = cmd .. body 171 | 172 | if keys then 173 | if not is_keys_ok(obj, keys) then 174 | return nil 175 | end 176 | 177 | --- where condition 178 | local ws = get_where(ele, keys) 179 | sql = sql .. " WHERE " .. ws 180 | end 181 | 182 | return sql 183 | end 184 | 185 | ---! 形成完整的删除语句 186 | class.delete_sql = function (tableName, obj, keys) 187 | if not is_keys_ok(obj, keys) then 188 | return nil 189 | end 190 | 191 | local ele = kv_copy(obj) 192 | local ws = get_where(ele, keys) 193 | local cmd = string.format("DELETE FROM %s ", tableName) 194 | 195 | local sql = cmd .. " WHERE " .. ws 196 | return sql 197 | end 198 | 199 | ---! 形成完整的更新语句 200 | class.update_sql = function (tableName, obj, keys) 201 | if not is_keys_ok(obj, keys) then 202 | return nil 203 | end 204 | 205 | local ele = kv_copy(obj) 206 | local ws = get_where(ele, keys) 207 | local cmd = string.format("UPDATE %s ", tableName) 208 | local body = get_update_body(ele) 209 | 210 | local sql = cmd .. " SET " .. body .. " WHERE " .. ws 211 | return sql 212 | end 213 | 214 | ---! 形成完整的选择语句 215 | class.select_sql = function (tableName, obj, keys) 216 | if not is_keys_ok(obj, keys) then 217 | return nil 218 | end 219 | 220 | local ele = kv_copy(obj) 221 | local ws = get_where(ele, keys) 222 | local body = get_select_body(ele) 223 | local cmd = string.format("SELECT %s FROM %s ", body, tableName) 224 | 225 | local sql = cmd .. " WHERE " .. ws 226 | return sql 227 | end 228 | 229 | return class 230 | 231 | -------------------------------------------------------------------------------- /backend/Helpers/ClusterHelper.lua: -------------------------------------------------------------------------------- 1 | --------------------------------------------------- 2 | ---! @file 3 | ---! @brief 远程集群节点调用辅助 ClusterHelper 4 | --------------------------------------------------- 5 | 6 | ---! 依赖库 skynet 7 | local skynet = require "skynet" 8 | local snax = require "skynet.snax" 9 | local cluster = require "skynet.cluster" 10 | 11 | local packetHelper = require "PacketHelper" 12 | 13 | ---! ClusterHelper 模块定义 14 | local class = {} 15 | 16 | ---! 防止读nil的key 17 | setmetatable(class, { 18 | __index = function (t, k) 19 | return function() 20 | print("retrieve unknown field from ClusterHelper: ", k) 21 | end 22 | end 23 | }) 24 | 25 | ---! 节点类型常量 serverKind 26 | class.kNodeInfo = "NodeInfo" 27 | class.kNodeLink = "NodeLink" 28 | class.kMainInfo = "MainInfo" 29 | class.kMainNode = "MainNode" 30 | 31 | class.kAgentServer = "AgentServer" 32 | class.kHallServer = "HallServer" 33 | class.kMainServer = "MainServer" 34 | 35 | class.kHallConfig = "HallConfig" 36 | class.kHallService = "HallService" 37 | class.kDBService = "DBService" 38 | class.kMySQLService = "MySQLService" 39 | class.kRedisService = "RedisService" 40 | class.kWatchDog = "WatchDog" 41 | 42 | ---! 获取config.cluster列表和各服务器列表 43 | class.getAllNodes = function (cfg, info) 44 | local all = {class.kAgentServer, class.kHallServer, class.kMainServer} 45 | local ret = {} 46 | for nodeName, nodeValue in pairs(cfg.MySite) do 47 | for _, serverKind in ipairs(all) do 48 | local list = info[serverKind] or {} 49 | info[serverKind] = list 50 | 51 | if not nodeValue[2] and serverKind == class.kAgentServer then 52 | -- AgentServer must have public address 53 | else 54 | local srv = cfg[serverKind] 55 | for i=0,srv.maxIndex do 56 | local name = string.format("%s_%s%d", nodeName, serverKind, i) 57 | local value = string.format("%s:%d", nodeValue[1], srv.nodePort + i) 58 | ret[name] = value 59 | table.insert(list, name) 60 | end 61 | end 62 | end 63 | end 64 | return ret 65 | end 66 | 67 | ---! 获取当前节点信息 68 | class.getNodeInfo = function (cfg) 69 | local ret = {} 70 | ret.appName = skynet.getenv("app_name") 71 | ret.nodeName = skynet.getenv("NodeName") 72 | 73 | local node = cfg.MySite[ret.nodeName] 74 | ret.privAddr = node[1] 75 | ret.publAddr = node[2] 76 | 77 | ret.serverKind = skynet.getenv("ServerKind") 78 | ret.serverIndex = tonumber(skynet.getenv("ServerNo")) 79 | ret.serverName = ret.serverKind .. ret.serverIndex 80 | ret.numPlayers = 0 81 | 82 | local conf = cfg[ret.serverKind] 83 | assert(ret.serverIndex >= 0 and ret.serverIndex <= conf.maxIndex ) 84 | local all = {"debugPort", "tcpPort", "webPort"} 85 | for _, one in ipairs(all) do 86 | if conf[one] then 87 | ret[one] = conf[one] + ret.serverIndex 88 | end 89 | end 90 | 91 | return ret 92 | end 93 | 94 | ---! 解析集群内节点配置 95 | class.parseConfig = function (info) 96 | local cfg = packetHelper.load_config("./config/config.nodes") 97 | 98 | info.nodeInfo = class.getNodeInfo(cfg) 99 | info.clusterList = class.getAllNodes(cfg, info) 100 | end 101 | 102 | ----------------------------------------------------- 103 | ---! @brief create a handy proxy to skynet service on other cluster node 104 | ---! @note node is cluster app, addr is an address or @REG_NAME 105 | ----------------------------------------------------- 106 | local function skynet_proxy(node, addr) 107 | if not node then 108 | return 109 | end 110 | 111 | local ret, proxy = pcall(cluster.proxy, node, addr) 112 | if proxy == nil then 113 | skynet.error(node, " is not online") 114 | end 115 | 116 | return proxy 117 | end 118 | class.skynet_proxy = skynet_proxy 119 | 120 | ----------------------------------------------------------- 121 | ---! @brief via proxy, safely call to skynet service on other cluster node, 122 | ---! @note node is cluster app, addr is an address or @REG_NAME 123 | ---! handler should be like this: 124 | ---! @code 125 | ---! function(proxy) 126 | ---! skynet.call(proxy, "lua", name, addr, config) 127 | ---! end 128 | ---! @endcode 129 | ------------------------------------------------------------- 130 | local function proxy_call (node, addr, handler) 131 | local proxy = skynet_proxy(node, addr) 132 | if proxy then 133 | handler(proxy) 134 | end 135 | end 136 | class.proxy_call = proxy_call 137 | 138 | 139 | ---! get an address for remote service that register in appNode:NodeInfo 140 | class.cluster_addr = function (app, service) 141 | local proxyName = "@" .. class.kNodeInfo 142 | if service == class.kNodeInfo then 143 | return proxyName 144 | end 145 | 146 | local ret, addr = pcall(cluster.call, app, proxyName, "getServiceAddr", service) 147 | if not ret then 148 | skynet.error(app, "is not online") 149 | return 150 | elseif addr == "" then 151 | skynet.error(app, "can't get", service, "from NodeInfo's config") 152 | return 153 | end 154 | return addr 155 | end 156 | 157 | ---! get a proxy for cluster's node: app, serviceName: service 158 | class.cluster_proxy = function (app, service) 159 | local addr = class.cluster_addr(app, service) 160 | if not addr then 161 | return 162 | end 163 | 164 | proxy = class.skynet_proxy(app, addr) 165 | if not proxy then 166 | skynet.error(app, "or ", service, "is not online") 167 | end 168 | return proxy 169 | end 170 | 171 | ---! execute handler in cluster's node: app, serviceName: service 172 | class.cluster_action = function (app, service, handler) 173 | local proxy = class.cluster_proxy(app, service) 174 | if not proxy then 175 | return 176 | end 177 | 178 | handler(proxy) 179 | end 180 | 181 | class.getMainAppAddr = function (service) 182 | local nodeInfo = skynet.uniqueservice("NodeInfo") 183 | local appName = skynet.call(nodeInfo, "lua", "getConfig", class.kMainNode) 184 | if not appName then 185 | return 186 | end 187 | 188 | local addr = class.cluster_addr(appName, service) 189 | return appName, addr 190 | end 191 | 192 | 193 | return class 194 | 195 | -------------------------------------------------------------------------------- /backend/Algos/QuadTree.lua: -------------------------------------------------------------------------------- 1 | ---! 四叉树 2 | 3 | local NumSet = require "NumSet" 4 | local debugHelper = require "DebugHelper" 5 | 6 | local class = {mt = {}} 7 | class.mt.__index = class 8 | 9 | class.maxDepth = 6 10 | class.maxNodeCount = 16 11 | -- class.maxNodeCount = 2 12 | 13 | class.create = function(box, parent) 14 | local self = { 15 | box = box, 16 | parent = parent, 17 | children = nil, 18 | 19 | nodes = NumSet.create(), 20 | } 21 | 22 | if parent and parent.depth then 23 | self.depth = parent.depth + 1 24 | else 25 | self.depth = 0 26 | end 27 | 28 | setmetatable(self, class.mt) 29 | return self 30 | end 31 | 32 | class.checkObjectBox = function(self, box, nodeFunc, childFunc, checkIntersect) 33 | if not self.children then 34 | if self.nodes:getCount() > class.maxNodeCount then 35 | self:createChildren() 36 | end 37 | end 38 | 39 | if self.children then 40 | for _, tree in ipairs(self.children) do 41 | if tree.box:containsBox(box) then 42 | childFunc(tree) 43 | if not checkIntersect then 44 | return 45 | end 46 | elseif checkIntersect and tree.box:intersect(box) then 47 | childFunc(tree) 48 | end 49 | end 50 | end 51 | 52 | nodeFunc(self) 53 | end 54 | 55 | class.moveObjectsToChildren = function(self) 56 | if not self.children then 57 | return 58 | end 59 | 60 | local list = NumSet.create() 61 | self.nodes:forEach(function(obj) 62 | local alone = true 63 | for _, child in ipairs(self.children) do 64 | if child.box:containsBox(obj.box) then 65 | child:addObject(obj) 66 | alone = nil 67 | break 68 | end 69 | end 70 | 71 | if alone then 72 | list:addObject(obj) 73 | end 74 | end) 75 | 76 | self.nodes = list 77 | end 78 | 79 | class.createChildren = function(self) 80 | if self.depth >= class.maxDepth or self.children then 81 | return 82 | end 83 | 84 | self.children = {} 85 | local boxes = self.box:getSplits() 86 | for _, box in ipairs(boxes) do 87 | local tree = class.create(box, self) 88 | table.insert(self.children, tree) 89 | end 90 | 91 | self:moveObjectsToChildren() 92 | end 93 | 94 | class.isEmpty = function(self) 95 | return self.nodes:getCount() <= 0 and self.children == nil 96 | end 97 | 98 | class.checkChildren = function(self) 99 | if self.children then 100 | for _, child in ipairs(self.children) do 101 | if not child:isEmpty() then 102 | return 103 | end 104 | end 105 | self.children = nil 106 | end 107 | 108 | if self:isEmpty() and self.parent then 109 | self.parent:checkChildren() 110 | end 111 | end 112 | 113 | class.addNodeObject = function(self, entity) 114 | if not self.nodes:hasObject(entity) then 115 | entity.treeNode = self 116 | self.nodes:addObject(entity) 117 | end 118 | end 119 | 120 | class.addObject = function(self, entity) 121 | assert(entity.box ~= nil) 122 | self:checkObjectBox(entity.box, function(node) 123 | node:addNodeObject(entity) 124 | end, 125 | function(tree) 126 | tree:addObject(entity) 127 | end) 128 | end 129 | 130 | class.removeObject = function(self, entity) 131 | assert(entity.box ~= nil) 132 | local tree = entity.treeNode 133 | if not tree or not tree.nodes:hasObject(entity) then 134 | xpcall(function () 135 | debugHelper.cclog("remove node %s failed", tostring(entity.nodeId)) 136 | debugHelper.cclog(debug.traceback()) 137 | debugHelper.printDeepTable(entity, 1) 138 | debugHelper.printDeepTable(tree, 1) 139 | end, 140 | function(err) 141 | print(err) 142 | end) 143 | return 144 | end 145 | 146 | entity.treeNode = nil 147 | tree.nodes:removeObject(entity) 148 | if tree.nodes:getCount() <= 0 then 149 | tree:checkChildren() 150 | end 151 | end 152 | 153 | class.updateObject = function(self, entity) 154 | self:removeObject(entity) 155 | self:addObject(entity) 156 | end 157 | 158 | class.queryBox = function(self, box, handler) 159 | self:checkObjectBox(box, function(tree) 160 | tree.nodes:forEach(function(obj) 161 | handler(obj) 162 | end) 163 | end, 164 | function(subtree) 165 | subtree:queryBox(box, handler) 166 | end, 167 | true) 168 | end 169 | 170 | class.applyAnyTree = function(self, handler, except, box) 171 | local ret = nil 172 | self.nodes:forEach(function(obj) 173 | if handler(obj) then 174 | ret = obj 175 | return true 176 | end 177 | end) 178 | if ret then 179 | return ret 180 | end 181 | 182 | if self.children then 183 | for _, tree in ipairs(self.children) do 184 | if tree ~= except and tree.box:intersect(box) then 185 | ret = tree:applyAnyTree(handler, nil, box) 186 | if ret then 187 | return ret 188 | end 189 | end 190 | end 191 | end 192 | return ret 193 | end 194 | 195 | class.findLeafNode = function(self, point) 196 | if not self.children then 197 | return self 198 | end 199 | 200 | for _, tree in ipairs(self.children) do 201 | if tree.box:containsPoint(point.x, point.y) then 202 | return tree:findLeafNode(point) 203 | end 204 | end 205 | return nil 206 | end 207 | 208 | class.queryNearObject = function(self, box, handler) 209 | local x, y = box:getCenter() 210 | local point = {x = x, y = y} 211 | local ret = nil 212 | local leaf = self:findLeafNode(point) 213 | local unuse = nil 214 | while leaf and leaf.box:intersect(box) do 215 | ret = leaf:applyAnyTree(handler, unuse, box) 216 | if ret then 217 | break; 218 | end 219 | 220 | unuse = leaf 221 | leaf = leaf.parent 222 | end 223 | 224 | return ret 225 | end 226 | 227 | class.traverseTree = function(self, handler) 228 | handler(self) 229 | 230 | if self.children then 231 | for _, child in ipairs(self.children) do 232 | child:traverseTree(handler) 233 | end 234 | end 235 | end 236 | 237 | return class 238 | 239 | -------------------------------------------------------------------------------- /backend/Stages/StageInterface.lua: -------------------------------------------------------------------------------- 1 | local skynet = skynet or require "skynet" 2 | -- use skynet.init to determine server or client 3 | 4 | local protoTypes = require "ProtoTypes" 5 | 6 | local debugHelper = require "DebugHelper" 7 | local packetHelper = require "PacketHelper" 8 | 9 | local baseClass = require "HallInterface" 10 | 11 | local math = math 12 | 13 | local class = {mt = {}} 14 | class.mt.__index = class 15 | 16 | setmetatable(class, baseClass.mt) 17 | 18 | ---!@brief 创建StageInterface的对象 19 | ---!@param conf 游戏配置文件 20 | ---!@return slef 返回创建的StageInterface 21 | class.create = function (conf) 22 | local self = baseClass.create(conf) 23 | setmetatable(self, class.mt) 24 | 25 | xpcall(function() 26 | local GameStage = require(conf.GameStage) 27 | local server = GameStage.create(self, conf) 28 | self.stage = server 29 | 30 | server:start() 31 | end, function (err) 32 | print(err) 33 | print(debug.traceback()) 34 | end) 35 | 36 | return self 37 | end 38 | 39 | ---!@brief TODO 40 | ---! @param 41 | ---! @return 42 | class.tick = function(self, dt) 43 | self:executeEvents() 44 | 45 | local server = self.stage 46 | server:mainLoop(dt) 47 | end 48 | 49 | ---!@brief StageInterface中的默认消息 50 | ---! @param player 用户的数据 51 | ---! @param gameType 游戏类型 52 | ---!@param data TODO 53 | class.handleGameData = function (self, player, gameType, data) 54 | local user = player and self:getUserInfo(player.FUserCode) 55 | if user then 56 | player = user 57 | end 58 | 59 | if gameType == protoTypes.CGGAME_PROTO_SUBTYPE_QUITSTAGE then 60 | self:QuitStage(player) 61 | elseif gameType == protoTypes.CGGAME_PROTO_SUBTYPE_READY then 62 | self:PlayerReady(player) 63 | elseif gameType == protoTypes.CGGAME_PROTO_SUBTYPE_GIFT then 64 | self:SendUserGift(player.FUserCode, data) 65 | elseif player.playerTracker then 66 | self.stage:handleGameData(player, gameType, data) 67 | else 68 | debugHelper.cclog("unknown handleGameData %s", tostring(gameType)) 69 | end 70 | end 71 | 72 | 73 | ---! 收集用户信息 self.config.DBTableName 74 | class.CollectUserStatus = function (self, user) 75 | local info = {} 76 | info.FUserCode = user.FUserCode 77 | info.status = user.status 78 | if user.is_offline then 79 | info.status = protoTypes.CGGAME_USER_STATUS_OFFLINE 80 | end 81 | 82 | local gameStage = require(self.config.GameStage) 83 | for k, f in ipairs(gameStage.UserStatus_Fields or {}) do 84 | info[f] = user[f] 85 | end 86 | 87 | local data = packetHelper:encodeMsg(gameStage.UserStatus_ProtoName, info) 88 | local packet = packetHelper:makeProtoData(protoTypes.CGGAME_PROTO_MAINTYPE_HALL, 89 | protoTypes.CGGAME_PROTO_SUBTYPE_USERSTATUS, data) 90 | return packet 91 | end 92 | 93 | ---! @brief 发送用户包 94 | class.SendUserPacket = function (self, fromCode, packet, sendFunc) 95 | self.onlineUsers:forEach(function(user) 96 | sendFunc(self, packet, user.FUserCode) 97 | end) 98 | end 99 | 100 | ---! @brief 玩家准备 101 | ---! @param player 玩家的游戏数据 102 | ---! @rerurn true 玩家已经成功的ready 103 | ---! @rerurn nil 104 | class.PlayerReady = function(self, player) 105 | end 106 | 107 | ---! @brief 玩家离开桌子但是还在房间里 108 | ---! @param palyer 玩家的数据 109 | ---!@return true 玩家已经成功的离开了桌子 110 | ---!@retuen nil 玩家离开桌子失败 111 | class.QuitStage = function(self, player) 112 | if not player.playerTracker then 113 | return true 114 | end 115 | 116 | if not self.stage:ForceLeave(player) then 117 | return nil 118 | end 119 | 120 | self.stage:removeClient(player) 121 | 122 | if player.is_offline then 123 | self:ClearUser(player) 124 | end 125 | 126 | return true 127 | end 128 | 129 | 130 | ---! @brief 131 | ---! @param 132 | class.PlayerBreak = function(self, player) 133 | if not player then 134 | return 135 | end 136 | 137 | if self:QuitStage(player) then 138 | -- 是否可以立刻退出 139 | self:ClearUser(player) 140 | elseif not player.is_offline then 141 | -- 不能立刻退出,设置离线标志 142 | player.is_offline = true 143 | if skynet.init then 144 | self:remoteAddAppGameUser(player) 145 | end 146 | end 147 | 148 | local delay = skynet.time() - player.start_time 149 | player.FTotalTime = (player.FTotalTime or 0) + delay 150 | 151 | local keyName = "FUserCode" 152 | self:remoteUpdateDB(self.config.DBTableName, keyName, player[keyName], "FTotalTime", player.FTotalTime) 153 | 154 | local fields = { 155 | "appName", "agent", "gate", "client_fd", "address", "watchdog", 156 | } 157 | for _, key in ipairs(fields) do 158 | player[key] = nil 159 | end 160 | end 161 | 162 | ---! @brief 玩家是否已经坐下 163 | ---! @param 164 | ---! is player sit down or not? make it sit down anyway 165 | class.PlayerContinue = function(self, player) 166 | if skynet.init then 167 | self:remoteDelAppGameUser(player) 168 | end 169 | 170 | self:SendUserInfo(player.FUserCode, player.FUserCode) 171 | self:SendGameInfo(player) 172 | 173 | if not player.playerTracker then 174 | player.playerTracker = packetHelper.createObject(self.config.PlayerTracker, player, self.stage) 175 | self.stage:addClient(player) 176 | end 177 | 178 | self.stage:RefreshTracker(player.playerTracker) 179 | end 180 | 181 | class.ClearUser = function (self, player) 182 | self.onlineUsers:removeObject(player, player.FUserCode) 183 | end 184 | 185 | class.SendGameInfo = function (self, player) 186 | local config = self.config 187 | local gameInfo = { 188 | gameId = config.GameId, 189 | gameVersion = config.Version, 190 | lowVersion = config.LowestVersion, 191 | gameMode = config.GameMode, 192 | numPlayers = self.onlineUsers:getCount(), 193 | maxPlayers = config.MaxConnections, 194 | 195 | FUserCode = player.FUserCode, 196 | appName = config.HallName, 197 | } 198 | 199 | local data = packetHelper:encodeMsg("CGGame.HallInfo", gameInfo) 200 | local packet = packetHelper:makeProtoData(protoTypes.CGGAME_PROTO_MAINTYPE_GAME, 201 | protoTypes.CGGAME_PROTO_SUBTYPE_GAMEINFO, data) 202 | self:gamePacketToUser(packet, player.FUserCode) 203 | end 204 | 205 | ---!@brief 获得系统内部状态 206 | ---!@return info 内部状态的描述 207 | class.logStat = function (self) 208 | debugHelper.cclog("StageInterface log stats") 209 | debugHelper.cclog(string.format("online player: %d\n", self.onlineUsers:getCount())) 210 | end 211 | 212 | return class 213 | 214 | -------------------------------------------------------------------------------- /backend/Algos/PriorityQueue.lua: -------------------------------------------------------------------------------- 1 | --------------------------------- 2 | --! @file 3 | --! @brief PriorityQueue 4 | --! all objects in queue is sorted heap by priority, that is 5 | --! the lowest priority value is first 6 | --! 7 | --! Object in queue must have three keys, 8 | --! 1) getKey(obj) to identify itself from others, default the event itself 9 | --! 2) getPriority(obj) like os.time(), when we should fire this event, 10 | --! 3) .queueIndex index to find/update in queue, only meaningful when it is in queue 11 | --! 12 | -------------------------------- 13 | 14 | --! define class for PriorityQueue 15 | local class = {mt = {}} 16 | --! define class for PriorityQueue 17 | local PriorityQueue = class 18 | --! define class for PriorityQueue 19 | class.mt.__index = class 20 | 21 | --! @brief creator for PriorityQueue 22 | local function create (getKey, getPriority, queueIndexKey) 23 | local self = { 24 | count = 0, 25 | objects = {}, 26 | queue = {}, 27 | 28 | getKeyFunc = getKey, 29 | getPriorityFunc = getPriority, 30 | queueIndexKey = queueIndexKey or ".queueIndex" 31 | } 32 | 33 | setmetatable(self, class.mt) 34 | return self 35 | end 36 | class.create = create 37 | 38 | --! @brief add Object to PriorityQueue 39 | --! @param self the queue 40 | --! @param ele an object to add 41 | local function addObject (self, obj) 42 | local e = self:findObject(obj) 43 | if e then 44 | return 45 | end 46 | 47 | e = obj 48 | local key = self.getKeyFunc(e) 49 | self.count = self.count + 1 50 | self.objects[key] = obj 51 | local index = self.count 52 | self.queue[index] = obj 53 | obj[self.queueIndexKey] = index 54 | 55 | self:shiftUp(index) 56 | end 57 | class.addObject = addObject 58 | 59 | --! @brief update an existing object with a priority in queue 60 | --! @param self the queue 61 | --! @param obj the object 62 | --! @param setPriorityFunc set new priority for the object, setPriorityFunc(obj) 63 | local function updateObject (self, obj, setPriorityFunc) 64 | local e = self:findObject(obj) 65 | if not e then 66 | setPriorityFunc(obj) 67 | self:addObject(obj) 68 | end 69 | 70 | e = self:findObject(obj) 71 | if not e then 72 | return 73 | end 74 | 75 | local oldTimeout = self.getPriorityFunc(e) 76 | setPriorityFunc(e) 77 | if self.getPriorityFunc(e) >= oldTimeout then 78 | self:shiftDown(e[self.queueIndexKey]) 79 | else 80 | self:shiftUp(e[self.queueIndexKey]) 81 | end 82 | end 83 | class.updateObject = updateObject 84 | 85 | --! @brief remove an existing event from queue 86 | --! @param self, event 87 | local function removeObject (self, obj) 88 | local e = self:findObject(obj) 89 | if not e then 90 | return 91 | end 92 | 93 | local key= self.getKeyFunc(e) 94 | self.objects[key] = nil 95 | if e[self.queueIndexKey] == self.count then 96 | self.queue[e[self.queueIndexKey]] = nil 97 | self.count = self.count - 1 98 | 99 | else 100 | local last = self.queue[self.count] 101 | self.queue[self.count] = nil 102 | self.count = self.count - 1 103 | 104 | self.queue[e[self.queueIndexKey]] = last 105 | last[self.queueIndexKey] = e[self.queueIndexKey] 106 | 107 | if self.getPriorityFunc(last) >= self.getPriorityFunc(e) then 108 | self:shiftDown(last[self.queueIndexKey]) 109 | else 110 | self:shiftUp(last[self.queueIndexKey]) 111 | end 112 | end 113 | 114 | return e 115 | end 116 | class.removeObject = removeObject 117 | 118 | --! @brief find object in queue 119 | --! @param obj object to find 120 | --! @return the object or nil 121 | local function findObject (self, obj) 122 | local key = self.getKeyFunc(obj) 123 | local e = self.objects[key] 124 | return e 125 | end 126 | class.findObject = findObject 127 | 128 | --! @brief internal function to shift object in queue up 129 | --! @param index the queue index to shift 130 | local function shiftUp (self, index) 131 | local parent = math.floor(index / 2) 132 | if parent < 1 then 133 | return 134 | end 135 | 136 | local p = self.queue[parent] 137 | local e = self.queue[index] 138 | if self.getPriorityFunc(e) < self.getPriorityFunc(p) then 139 | p[self.queueIndexKey] = index 140 | e[self.queueIndexKey] = parent 141 | 142 | self.queue[e[self.queueIndexKey]] = e 143 | self.queue[p[self.queueIndexKey]] = p 144 | 145 | self:shiftUp(parent) 146 | end 147 | end 148 | class.shiftUp = shiftUp 149 | 150 | --! @brief internal function to shift object in queue down 151 | --! @param index the queue index to shift 152 | local function shiftDown (self, index) 153 | local sub2 = math.floor(index * 2 + 1) 154 | local sub = math.floor(index * 2) 155 | 156 | if sub > self.count then 157 | return 158 | end 159 | 160 | if sub2 > self.count or self.getPriorityFunc(self.queue[sub2]) >= self.getPriorityFunc(self.queue[sub]) then 161 | else 162 | sub = sub2 163 | end 164 | 165 | local p = self.queue[index] 166 | local e = self.queue[sub] 167 | 168 | if self.getPriorityFunc(e) < self.getPriorityFunc(p) then 169 | p[self.queueIndexKey] = sub 170 | e[self.queueIndexKey] = index 171 | 172 | self.queue[e[self.queueIndexKey]] = e 173 | self.queue[p[self.queueIndexKey]] = p 174 | 175 | self:shiftDown(sub) 176 | end 177 | end 178 | class.shiftDown = shiftDown 179 | 180 | --! @brief pop the first object in queue 181 | local function pop (self) 182 | if self.count <= 0 then 183 | return nil 184 | end 185 | 186 | local e = self.queue[1] 187 | return self:removeObject(e) 188 | end 189 | class.pop = pop 190 | 191 | --! @brief retrieve the first object in queue, but not pop 192 | --! @see top 193 | local function top (self) 194 | if self.count <= 0 then 195 | return nil 196 | end 197 | 198 | local e = self.queue[1] 199 | return e 200 | end 201 | class.top = top 202 | 203 | --! @brief forEach iterate through each items 204 | --! break if handler(obj) return true 205 | local function forEach (self, handler) 206 | for i=1,self.count do 207 | local e = self.queue[i] 208 | if handler(e) then 209 | break 210 | end 211 | end 212 | end 213 | class.forEach = forEach 214 | 215 | 216 | --! @brief debug output all event in queue 217 | local function debugOut (self) 218 | local ret = "" 219 | for i=1,self.count do 220 | local e = self.queue[i] 221 | local str = string.format("%s --> {%s, %s}", 222 | tostring(e[self.queueIndexKey]), 223 | tostring(self.getKeyFunc(e)), 224 | tostring(self.getPriorityFunc(e))) 225 | ret = ret .. str .. "\n" 226 | end 227 | return ret 228 | end 229 | class.debugOut = debugOut 230 | 231 | return PriorityQueue 232 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kuanli_server 宽立游戏服务器 2 | 3 | 基于skynet实现的宽立游戏服务器,支持斗地主等棋牌游戏和贪吃蛇大作战等在线类休闲游戏。 4 | 5 | 6 | 7 | ## 环境设置 8 | 9 | 使用vagrant搭建的集群环境,机器名称和IP地址分别为 node1(11.11.1.11), node2(11.11.1.12),node3(11.11.1.13)。 10 | 11 | 数据库用的是MySQL 主-主-主 结构,三台机器上的MySQL互为主从,**auto_increment_increment=5**, **auto_increment_offset**分别是**1**, **2**, **3**,避免**auto_increment**自动生成的主键冲突。Redis也是三台服务器上都有,分别各是一个redis-server,一个redis-sentinel;三台机器上的redis-server只有一个主服务器,其它两个是从服务器;使用redis-sentinel监控主服务器的健康,一有问题就把某一台从服务器切换成主服务器;之前的主服务器恢复后,自动变成从服务器。 12 | 13 | 14 | 15 | ## 运行说明 16 | 17 | 最主要的服务器节点分别是MainServer, HallServer, AgentServer。MainServer是监控服务器,提供其它功能服务器的查询和数据库服务;HallServer是游戏服务器;AgentServer是连接服务器,管理玩家来自TCP和Web socket的连接。 18 | 19 | 文件config/config.nodes是服务器各机器各节点的配置文件。所有机器都是同等的,给各服务器节点保留了相应的端口,可以运行上述的任何一类服务器。 20 | 21 | 启动MainServer如下所示: 22 | 23 | ```shell 24 | # sh MainServer/start.sh node3 1 25 | NodeName = node3, ServerKind = MainServer, ServerNo = 1 26 | debug port is 8501 27 | ``` 28 | 29 | 启动HallServer如下所示: 30 | 31 | ```shell 32 | # sh HallServer/start.sh node2 1 config/landlord.cfg 33 | NodeName = node2, ServerName = HallServer, ServerNo = 1, HallConfig=config/landlord.cfg 34 | debug port is 9001 35 | load 8 client bots 36 | ``` 37 | 38 | 启动AgentServer如下所示: 39 | 40 | ```shell 41 | # sh AgentServer/start.sh node1 1 42 | NodeName = node1, ServerKind = AgentServer, ServerNo = 1 43 | debug port is 8001 44 | ``` 45 | 46 | 47 | 48 | 以上服务器启动后,会显示本节点的调试端口,可以在本机上使用一下命令进入调试控制台: 49 | 50 | ```shell 51 | # telnet localhost 8501 52 | Trying ::1... 53 | telnet: connect to address ::1: Connection refused 54 | Trying 127.0.0.1... 55 | Connected to localhost. 56 | Escape character is '^]'. 57 | Welcome to skynet console 58 | 59 | list 60 | :00000004 snlua cdummy 61 | :00000006 snlua datacenterd 62 | :00000007 snlua service_mgr 63 | :00000009 snlua clusterd 64 | :0000000a snlua NodeInfo 65 | :0000000b snlua debug_console 8501 66 | :0000000c snlua gate 67 | :0000000d snlua NodeStat 68 | :0000000e snlua NodeLink 69 | :0000000f snlua MainInfo 70 | :00000010 snlua DBService 71 | :00000011 snlua RedisService 72 | :00000013 snlua MySQLService 73 | :00000014 snlua clusteragent 9 12 29 74 | :00000020 snlua clusteragent 9 12 58 75 | 76 | 77 | info :d 78 | Mon Aug 27 10:16:06 2018 79 | [Agent List] 80 | node1_AgentServer1 11.11.1.11:8001 num:0 81 | 82 | [Hall List] 83 | node2_HallServer1 11.11.1.12:9001 num:0 => [400, 800] 天天斗地主 id:2011 mode:1 version:20180901 low:20180901 84 | 85 | 大厅服务器数目:1 客户服务器数目:1 登陆人数:0 游戏人数:0 86 | 87 | 88 | ``` 89 | 90 | 其中,**list** 和 **info :d** 这两个命令是我们在调试控制台的输入。我们保证各服务启动的顺序,这样任何一个节点的调试控制台输入 **info :d** 都会定位到**NodeStat**服务,显示相应的节点状态。 91 | 92 | ## 运行结果 93 | 94 | ```shell 95 | [vagrant@node2 backend]$ sh HallServer/start.sh node2 3 config/yuncheng.cfg 96 | NodeName = node2, ServerName = HallServer, ServerNo = 3, HallConfig=config/yuncheng.cfg 97 | debug port is 9003 98 | load 5 client bots 99 | joinGame 100161 100 | joinGame 100166 101 | got selfSeatId 3 102 | joinGame 100171 103 | got selfSeatId 2 104 | joinGame 100071 105 | got selfSeatId 1 106 | check game start 107 | joinGame 100031 108 | got selfSeatId 2 109 | got selfSeatId 3 110 | check game start 111 | handCards : M m ♦️2 ♦️A ♣️A ♦️K ♣️K ♠️Q ♥️Q ♣️Q ♠️J ♥️J ♠️T ♣️T ♥️9 ♦️9 ♣️8 ♣️7 ♦️5 ♥️4 ♠️3 112 | direct : ♣️A ♣️K ♣️Q ♥️J ♣️T ♦️9 ♣️8 ♣️7 113 | 114 | handCards : ♠️2 ♥️2 ♣️2 ♠️A ♠️K ♦️J ♣️J ♥️T ♠️8 ♥️8 ♥️7 ♦️7 ♠️6 ♥️6 ♣️6 ♠️5 ♦️3 115 | othercards: ♣️A ♣️K ♣️Q ♥️J ♣️T ♦️9 ♣️8 ♣️7 116 | follow : 117 | 118 | handCards : F ♥️A ♥️K ♦️Q ♦️T ♠️9 ♣️9 ♦️8 ♠️7 ♦️6 ♥️5 ♣️5 ♠️4 ♦️4 ♣️4 ♥️3 ♣️3 119 | othercards: ♣️A ♣️K ♣️Q ♥️J ♣️T ♦️9 ♣️8 ♣️7 120 | follow : F ♠️4 ♦️4 ♣️4 121 | 122 | handCards : M m ♦️2 ♦️A ♦️K ♠️Q ♥️Q ♠️J ♠️T ♥️9 ♦️5 ♥️4 ♠️3 123 | othercards: F ♠️4 ♦️4 ♣️4 124 | follow : M m 125 | 126 | handCards : ♦️3 ♠️5 ♣️6 ♥️6 ♠️6 ♦️7 ♥️7 ♥️8 ♠️8 ♥️T ♣️J ♦️J ♠️K ♠️A ♣️2 ♥️2 ♠️2 127 | othercards: M m 128 | follow : 129 | 130 | handCards : ♥️A ♥️K ♦️Q ♦️T ♠️9 ♣️9 ♦️8 ♠️7 ♦️6 ♥️5 ♣️5 ♥️3 ♣️3 131 | othercards: M m 132 | follow : 133 | 134 | handCards : ♦️2 ♦️A ♦️K ♠️Q ♥️Q ♠️J ♠️T ♥️9 ♦️5 ♥️4 ♠️3 135 | direct : ♠️3 136 | 137 | handCards : ♦️3 ♠️5 ♣️6 ♥️6 ♠️6 ♦️7 ♥️7 ♥️8 ♠️8 ♥️T ♣️J ♦️J ♠️K ♠️A ♣️2 ♥️2 ♠️2 138 | othercards: ♠️3 139 | follow : ♠️5 140 | 141 | handCards : ♥️A ♥️K ♦️Q ♦️T ♠️9 ♣️9 ♦️8 ♠️7 ♦️6 ♥️5 ♣️5 ♥️3 ♣️3 142 | othercards: ♠️5 143 | follow : ♣️9 144 | 145 | handCards : ♦️2 ♦️A ♦️K ♠️Q ♥️Q ♠️J ♠️T ♥️9 ♦️5 ♥️4 146 | othercards: ♣️9 147 | follow : ♥️Q 148 | 149 | handCards : ♠️2 ♥️2 ♣️2 ♠️A ♠️K ♦️J ♣️J ♥️T ♠️8 ♥️8 ♥️7 ♦️7 ♠️6 ♥️6 ♣️6 ♦️3 150 | othercards: ♥️Q 151 | follow : ♠️K 152 | 153 | handCards : ♥️A ♥️K ♦️Q ♦️T ♠️9 ♦️8 ♠️7 ♦️6 ♥️5 ♣️5 ♥️3 ♣️3 154 | othercards: ♠️K 155 | follow : ♥️A 156 | 157 | handCards : ♦️2 ♦️A ♦️K ♠️Q ♠️J ♠️T ♥️9 ♦️5 ♥️4 158 | othercards: ♥️A 159 | follow : ♦️2 160 | 161 | handCards : ♠️2 ♥️2 ♣️2 ♠️A ♦️J ♣️J ♥️T ♠️8 ♥️8 ♥️7 ♦️7 ♠️6 ♥️6 ♣️6 ♦️3 162 | othercards: ♦️2 163 | follow : 164 | 165 | handCards : ♥️K ♦️Q ♦️T ♠️9 ♦️8 ♠️7 ♦️6 ♥️5 ♣️5 ♥️3 ♣️3 166 | othercards: ♦️2 167 | follow : 168 | 169 | handCards : ♦️A ♦️K ♠️Q ♠️J ♠️T ♥️9 ♦️5 ♥️4 170 | direct : ♦️A ♦️K ♠️Q ♠️J ♠️T ♥️9 171 | 172 | handCards : ♦️3 ♣️6 ♥️6 ♠️6 ♦️7 ♥️7 ♥️8 ♠️8 ♥️T ♣️J ♦️J ♠️A ♣️2 ♥️2 ♠️2 173 | othercards: ♦️A ♦️K ♠️Q ♠️J ♠️T ♥️9 174 | follow : 175 | 176 | handCards : ♣️3 ♥️3 ♣️5 ♥️5 ♦️6 ♠️7 ♦️8 ♠️9 ♦️T ♦️Q ♥️K 177 | othercards: ♦️A ♦️K ♠️Q ♠️J ♠️T ♥️9 178 | follow : 179 | 180 | handCards : ♦️5 ♥️4 181 | direct : ♥️4 182 | 183 | handCards : ♦️3 ♣️6 ♥️6 ♠️6 ♦️7 ♥️7 ♥️8 ♠️8 ♥️T ♣️J ♦️J ♠️A ♣️2 ♥️2 ♠️2 184 | othercards: ♥️4 185 | follow : ♣️6 186 | 187 | handCards : ♣️3 ♥️3 ♣️5 ♥️5 ♦️6 ♠️7 ♦️8 ♠️9 ♦️T ♦️Q ♥️K 188 | othercards: ♣️6 189 | follow : ♦️Q 190 | 191 | handCards : ♦️5 192 | othercards: ♦️Q 193 | follow : 194 | 195 | handCards : ♠️2 ♥️2 ♣️2 ♠️A ♦️J ♣️J ♥️T ♠️8 ♥️8 ♥️7 ♦️7 ♠️6 ♥️6 ♦️3 196 | othercards: ♦️Q 197 | follow : ♠️A 198 | 199 | handCards : ♥️K ♦️T ♠️9 ♦️8 ♠️7 ♦️6 ♥️5 ♣️5 ♥️3 ♣️3 200 | othercards: ♠️A 201 | follow : 202 | 203 | handCards : ♦️5 204 | othercards: ♠️A 205 | follow : 206 | 207 | handCards : ♠️2 ♥️2 ♣️2 ♦️J ♣️J ♥️T ♠️8 ♥️8 ♥️7 ♦️7 ♠️6 ♥️6 ♦️3 208 | direct : ♠️8 ♥️8 ♥️7 ♦️7 ♠️6 ♥️6 209 | 210 | handCards : ♣️3 ♥️3 ♣️5 ♥️5 ♦️6 ♠️7 ♦️8 ♠️9 ♦️T ♥️K 211 | othercards: ♠️8 ♥️8 ♥️7 ♦️7 ♠️6 ♥️6 212 | follow : 213 | 214 | handCards : ♦️5 215 | othercards: ♠️8 ♥️8 ♥️7 ♦️7 ♠️6 ♥️6 216 | follow : 217 | 218 | handCards : ♠️2 ♥️2 ♣️2 ♦️J ♣️J ♥️T ♦️3 219 | direct : ♦️3 220 | 221 | handCards : ♣️3 ♥️3 ♣️5 ♥️5 ♦️6 ♠️7 ♦️8 ♠️9 ♦️T ♥️K 222 | othercards: ♦️3 223 | follow : ♣️5 224 | 225 | handCards : ♦️5 226 | othercards: ♣️5 227 | follow : 228 | 229 | handCards : ♠️2 ♥️2 ♣️2 ♦️J ♣️J ♥️T 230 | othercards: ♣️5 231 | follow : ♥️T 232 | 233 | handCards : ♥️K ♦️T ♠️9 ♦️8 ♠️7 ♦️6 ♥️5 ♥️3 ♣️3 234 | othercards: ♥️T 235 | follow : ♥️K 236 | 237 | handCards : ♦️5 238 | othercards: ♥️K 239 | follow : 240 | 241 | handCards : ♠️2 ♥️2 ♣️2 ♦️J ♣️J 242 | othercards: ♥️K 243 | follow : ♣️2 244 | 245 | handCards : ♦️T ♠️9 ♦️8 ♠️7 ♦️6 ♥️5 ♥️3 ♣️3 246 | othercards: ♣️2 247 | follow : 248 | 249 | handCards : ♦️5 250 | othercards: ♣️2 251 | follow : 252 | 253 | handCards : ♠️2 ♥️2 ♦️J ♣️J 254 | direct : ♦️J ♣️J 255 | 256 | handCards : ♣️3 ♥️3 ♥️5 ♦️6 ♠️7 ♦️8 ♠️9 ♦️T 257 | othercards: ♦️J ♣️J 258 | follow : 259 | 260 | handCards : ♦️5 261 | othercards: ♦️J ♣️J 262 | follow : 263 | 264 | handCards : ♠️2 ♥️2 265 | direct : ♠️2 ♥️2 266 | 267 | got selfSeatId nil 268 | got selfSeatId 3 269 | ``` 270 | 271 | ## 使用惯例 272 | 273 | ### lua代码中 区分客户端和服务器端 274 | 275 | 为了方便客户端和服务器端代码的一致性,在客户端代码里,我们实现了skynet库里 skynet.time, skynet.error等函数,但没有实现skynet.init这个函数,因此在客户端和服务器端的通用代码里,我们使用 if skynet.init (是否服务器) 或者 if not skynet.init (是否客户端) 这样的语句判断是否服务器端,是否客户端,在相应语句中执行对应端相关的代码。 276 | 277 | -------------------------------------------------------------------------------- /backend/client/LoginHelper.lua: -------------------------------------------------------------------------------- 1 | local skynet = skynet or require "skynet" 2 | local crypt = skynet.crypt or require "skynet.crypt" 3 | 4 | local AuthUtils = require "AuthUtils" 5 | 6 | local strHelper = require "StringHelper" 7 | local packetHelper = require "PacketHelper" 8 | 9 | local RemoteSocket = require "RemoteSocket" 10 | 11 | local protoTypes = require "ProtoTypes" 12 | 13 | 14 | ---! create the class metatable 15 | local class = {mt = {}} 16 | class.mt.__index = class 17 | 18 | ---! create delegate object 19 | class.create = function (const) 20 | local self = {} 21 | setmetatable(self, class.mt) 22 | 23 | self.message = "" 24 | self.agentList = {} 25 | self.const = const 26 | 27 | return self 28 | end 29 | 30 | class.createFromLayer = function (delegate, handler, botName, authInfo, const) 31 | if delegate.login then 32 | delegate.login:releaseFromLayer(delegate) 33 | end 34 | 35 | local login = class.create(const) 36 | delegate.login = login 37 | 38 | login:getOldLoginList() 39 | login:tryConnect() 40 | 41 | if login.remotesocket then 42 | local BotPlayer = require(botName) 43 | local agent = BotPlayer.create(delegate, authInfo, handler) 44 | delegate.agent = agent 45 | handler.agent = agent 46 | 47 | delegate.agent:sendAuthOptions(protoTypes.CGGAME_PROTO_SUBTYPE_ASKRESUME) 48 | return true 49 | end 50 | end 51 | 52 | class.tickCheck = function (self, delegate) 53 | if self.remotesocket and self.remotesocket:isClosed() then 54 | self.remotesocket = nil 55 | end 56 | 57 | if not self.remotesocket then 58 | self:tryConnect() 59 | 60 | if not self.remotesocket then 61 | if MessageBox then 62 | MessageBox("请确定网络正常后再重试,或联系我们客服QQ群: 543221539", "网络出错") 63 | 64 | local app = cc.exports.appInstance 65 | local view = app:createView("LineScene") 66 | view:showWithScene() 67 | else 68 | print("请确定网络正常后再重试,或联系我们客服QQ群: 543221539", "网络出错") 69 | end 70 | return 71 | end 72 | 73 | delegate.agent:sendAuthOptions(protoTypes.CGGAME_PROTO_SUBTYPE_ASKRESUME) 74 | return true 75 | end 76 | end 77 | 78 | class.closeSocket = function (self) 79 | if self.remotesocket then 80 | self.remotesocket:close() 81 | self.remotesocket = nil 82 | end 83 | end 84 | 85 | class.releaseFromLayer = function (self, delegate) 86 | self:closeSocket() 87 | delegate.login = nil 88 | end 89 | 90 | ---! agent list, maybe better two host:port for each site 91 | local def_agent_list = {"192.168.0.121:8201", "192.168.0.122:8201"} 92 | 93 | ---! check for all agents, find the best one if doCheck 94 | class.checkAllLoginServers = function (self, doCheck) 95 | local best = table.concat(def_agent_list, ",") 96 | if not doCheck then 97 | return best 98 | end 99 | 100 | local probs = def_agent_list 101 | local diff = 9999 102 | for i, item in ipairs(probs) do 103 | local oldTime = skynet.time() 104 | local host, port = string.match(item, "(%d+.%d+.%d+.%d+):(%d+)") 105 | local conn = RemoteSocket.create(host, port) 106 | if conn and conn.sockfd then 107 | local tmp = skynet.time() - oldTime 108 | print("diff for item ", item, " is ", tmp) 109 | if not best or diff > tmp then 110 | diff = tmp 111 | best = item 112 | end 113 | conn:close() 114 | end 115 | end 116 | 117 | return best 118 | end 119 | 120 | class.getOldLoginList = function (self, refreshLogin, checkForeign) 121 | self.message = "msgParsingOldLoginServers" 122 | 123 | local data = AuthUtils.getItem(AuthUtils.keyLoginList, "") 124 | if refreshLogin or data == "" then 125 | data = self:checkAllLoginServers(checkForeign) 126 | end 127 | 128 | self.agentList = {} 129 | 130 | local arr = {} 131 | for w in string.gmatch(data, "[^,]+") do 132 | table.insert(arr, w) 133 | end 134 | 135 | if #arr < 1 then 136 | arr = def_agent_list 137 | end 138 | 139 | for _, v in ipairs(arr) do 140 | local host, port = string.match(v, "(%d+.%d+.%d+.%d+):(%d+)") 141 | local one = {} 142 | one.host = host 143 | one.port = port 144 | 145 | local r = tostring(math.random()) 146 | self.agentList[r] = one 147 | end 148 | end 149 | 150 | class.sendHeartBeat = function (self) 151 | local info = {} 152 | info.fromType = protoTypes.CGGAME_PROTO_HEARTBEAT_CLIENT 153 | info.timestamp = skynet.time() 154 | 155 | local data = packetHelper:encodeMsg("CGGame.HeartBeat", info) 156 | local packet = packetHelper:makeProtoData(protoTypes.CGGAME_PROTO_MAINTYPE_BASIC, 157 | protoTypes.CGGAME_PROTO_SUBTYPE_HEARTBEAT, data) 158 | if self.remotesocket then 159 | self.remotesocket:sendPacket(packet) 160 | end 161 | -- print("sendHeartBeat", self.remotesocket) 162 | end 163 | 164 | class.tryConnect = function (self) 165 | self.message = "msgTryLoginServers" 166 | 167 | for k, v in pairs(self.agentList) do 168 | local conn = RemoteSocket.create(v.host, v.port) 169 | if conn and conn.sockfd then 170 | if self.remotesocket then 171 | self.remotesocket:close() 172 | self.remotesocket = nil 173 | end 174 | 175 | self.remotesocket = conn 176 | print("agent to ", v.host, v.port, " success") 177 | return conn 178 | end 179 | end 180 | 181 | return nil 182 | end 183 | 184 | class.getAgentList = function (self) 185 | self.message = "msgRefreshLoginServerList" 186 | 187 | local data = packetHelper:encodeMsg("CGGame.AgentList", {}) 188 | local packet = packetHelper:makeProtoData(protoTypes.CGGAME_PROTO_MAINTYPE_BASIC, 189 | protoTypes.CGGAME_PROTO_SUBTYPE_AGENTLIST, data) 190 | self.remotesocket:sendPacket(packet) 191 | end 192 | 193 | class.tryHall = function (self, gameMode) 194 | self.message = "msgRefreshHallServerList" 195 | local const = self.const 196 | 197 | local info = { 198 | gameId = const.GAMEID, 199 | gameMode = gameMode or 0, 200 | gameVersion = const.GAMEVERSION, 201 | } 202 | 203 | local data = packetHelper:encodeMsg("CGGame.HallInfo", info) 204 | local packet = packetHelper:makeProtoData(protoTypes.CGGAME_PROTO_MAINTYPE_HALL, 205 | protoTypes.CGGAME_PROTO_SUBTYPE_HALLJOIN, data) 206 | self.remotesocket:sendPacket(packet) 207 | end 208 | 209 | class.sendUserInfo = function (self) 210 | local authInfo = AuthUtils.getAuthInfo() 211 | 212 | local info = { 213 | FUserCode = authInfo.userCode, 214 | FNickName = authInfo.nickname, 215 | FOSType = authInfo.osType, 216 | FPlatform = authInfo.platform, 217 | } 218 | info.fieldNames = {"FUserCode", "FNickName", "FOSType", "FPlatform"} 219 | 220 | print("send user info", info) 221 | local debugHelper = require "DebugHelper" 222 | debugHelper.cclog("send user info", info) 223 | debugHelper.printDeepTable(info) 224 | debugHelper.printDeepTable(authInfo) 225 | 226 | local data = packetHelper:encodeMsg("CGGame.UserInfo", info) 227 | local packet = packetHelper:makeProtoData(protoTypes.CGGAME_PROTO_MAINTYPE_HALL, 228 | protoTypes.CGGAME_PROTO_SUBTYPE_MYINFO, data) 229 | self.remotesocket:sendPacket(packet) 230 | end 231 | 232 | class.tryGame = function (self, gameMode) 233 | self.message = "msgRefreshGameServerList" 234 | local const = self.const 235 | 236 | local info = { 237 | gameId = const.GAMEID, 238 | gameMode = gameMode or 0, 239 | gameVersion = const.GAMEVERSION, 240 | } 241 | 242 | local data = packetHelper:encodeMsg("CGGame.HallInfo", info) 243 | local packet = packetHelper:makeProtoData(protoTypes.CGGAME_PROTO_MAINTYPE_GAME, 244 | protoTypes.CGGAME_PROTO_SUBTYPE_GAMEJOIN, data) 245 | self.remotesocket:sendPacket(packet) 246 | end 247 | 248 | 249 | return class 250 | 251 | -------------------------------------------------------------------------------- /backend/Classes/Const_YunCheng.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------- 2 | ---! @file 3 | ---! @brief 4 | ------------------------------------------------------ 5 | local const = {} 6 | setmetatable(const, { 7 | __index = function (t, k) 8 | return function() 9 | print("unknown field from const: ", k, t) 10 | end 11 | end 12 | }) 13 | 14 | const.GAMEID = 2013 15 | const.GAMEVERSION = 20180901 16 | const.LOWVERSION = 20180901 17 | 18 | 19 | ---! const values 20 | const.YUNCHENG_CARD_FLOWER = 55 21 | const.YUNCHENG_CARD_BACKGROUND = 56 22 | const.YUNCHENG_PLAYER_CARD_NUM = 17 23 | const.YUNCHENG_LORD_CARD_NUM = 20 24 | const.YUNCHENG_MAX_PLAYER_NUM = 3 25 | 26 | const.kCard_ValueLeast = 2 27 | const.kCard_Value3 = 3 28 | const.kCard_Value4 = 4 29 | const.kCard_Value5 = 5 30 | const.kCard_Value6 = 6 31 | const.kCard_Value7 = 7 32 | const.kCard_Value8 = 8 33 | const.kCard_Value9 = 9 34 | const.kCard_ValueT = 10 -- Ten 35 | const.kCard_ValueJ = 11 36 | const.kCard_ValueQ = 12 37 | const.kCard_ValueK = 13 38 | const.kCard_ValueA = 14 39 | const.kCard_Value2 = 15 40 | const.kCard_ValueJoker1 = 16 41 | const.kCard_ValueJoker2 = 17 42 | 43 | const.kCard_Joker1 = 53 44 | const.kCard_Joker2 = 54 45 | 46 | 47 | const.kCardType_Single = 1 -- 单纯类型, seriaNum == 1 48 | const.kCardType_Serial = 2 -- 单顺, 双顺, 三顺(飞机), 4顺 49 | const.kCardType_Rocket = 3 -- 火箭(大小王) 50 | 51 | 52 | ---! game trace 53 | const.YUNCHENG_GAMETRACE_PICKUP = 1 -- 发牌 54 | const.YUNCHENG_GAMETRACE_DISCLOSE = 2 -- 明牌 55 | const.YUNCHENG_GAMETRACE_REFRESH = 3 -- 更新牌面 56 | const.YUNCHENG_GAMETRACE_LANDLORD = 4 -- 叫地主 57 | const.YUNCHENG_GAMETRACE_MULTIPLE = 5 -- 加倍 58 | const.YUNCHENG_GAMETRACE_THROW = 6 -- 出牌 59 | const.YUNCHENG_GAMETRACE_SHOWBOTTOM = 7 -- 显示底牌 60 | const.YUNCHENG_GAMETRACE_BOMBMULT = 8 -- 炸弹, 更新倍数 61 | const.YUNCHENG_GAMETRACE_GAMEOVER = 10 62 | 63 | 64 | ---! table status 65 | const.YUNCHENG_TABLE_STATUS_WAIT_NEWGAME = 5 66 | const.YUNCHENG_TABLE_STATUS_WAIT_PICKUP = 6 -- 发牌 67 | const.YUNCHENG_TABLE_STATUS_WAIT_LANDLORD = 7 -- 叫地主 68 | const.YUNCHENG_TABLE_STATUS_WAIT_MULTIPLE = 8 -- 加倍 69 | const.YUNCHENG_TABLE_STATUS_WAIT_THROW = 9 70 | const.YUNCHENG_TABLE_STATUS_WAIT_GAMEOVER = 10 71 | 72 | ---! time out 73 | const.YUNCHENG_TIMEOUT_WAIT_PICKUP = 5 74 | const.YUNCHENG_TIMEOUT_WAIT_LANDLORD = 10 75 | const.YUNCHENG_TIMEOUT_WAIT_MULTIPLE = 10 76 | const.YUNCHENG_TIMEOUT_WAIT_THROW = 25 77 | const.YUNCHENG_TIMEOUT_WAIT_OFFLINE = 5 78 | const.YUNCHENG_TIMEOUT_WAIT_GAMEOVER = 2 79 | const.YUNCHENG_TIMEOUT_WAIT_NEWGAME = 3 80 | 81 | ---! card types 82 | const.kGiftItems = { 83 | egg = 0, 84 | water = 0, 85 | bomb = 0, 86 | cheer = 0, 87 | flower = 0, 88 | kiss = 0, 89 | slap = 0, 90 | car = 0, 91 | house = 0, 92 | } 93 | 94 | const.YUNCHENG_ACL_STATUS_RESTART_NO_MASTER = 101; -- 没有人叫地主, 重新开始 95 | 96 | const.YUNCHENG_ACL_STATUS_NO_SELECT_CARDS = 102; -- 你没有选择任何牌 97 | 98 | const.YUNCHENG_ACL_STATUS_NOT_VALID_TYPE = 103; -- 不能组成有效牌型 99 | const.YUNCHENG_ACL_STATUS_NOT_SAME_TYPE = 104; -- 不是同一牌型 100 | const.YUNCHENG_ACL_STATUS_NOT_BIGGER = 105; -- 打不过别人的牌 101 | const.YUNCHENG_ACL_STATUS_NO_BIG_CARDS = 106; -- 没有牌能大过上家 102 | const.YUNCHENG_ACL_STATUS_NO_YOUR_CARDS = 107; -- 发的牌不是你的牌 103 | 104 | const.deltaSeat = function (seatId, delta) 105 | seatId = seatId + (delta and delta or 1) 106 | seatId = (seatId - 1 + const.YUNCHENG_MAX_PLAYER_NUM) % const.YUNCHENG_MAX_PLAYER_NUM + 1 107 | 108 | return seatId 109 | end 110 | 111 | const.isRocket = function (node) 112 | return node.cardType == const.kCardType_Rocket 113 | end 114 | 115 | const.isBomb = function (node) 116 | return node.seralNum==1 and node.mainNum >= 4 and node.subNum == 0 117 | end 118 | 119 | const.removeSubset = function (main, sub) 120 | local all = true 121 | for _, n in ipairs(sub) do 122 | local idx = nil 123 | for i,v in ipairs(main) do 124 | if v == n then 125 | idx = i 126 | break 127 | end 128 | end 129 | 130 | if not idx then 131 | all = nil 132 | print(n , " not found in main ") 133 | else 134 | table.remove(main, idx) 135 | end 136 | end 137 | 138 | return all 139 | end 140 | 141 | const.getCardValue = function (card) 142 | if card == const.kCard_Joker1 then 143 | return const.kCard_ValueJoker1; 144 | end 145 | 146 | if card == const.kCard_Joker2 then 147 | return const.kCard_ValueJoker2; 148 | end 149 | 150 | local t = card % 13; 151 | if t < 3 then 152 | t = t + 13; 153 | end 154 | return t; 155 | end 156 | 157 | const.getCardItSelf = function (card) 158 | return card 159 | end 160 | 161 | const.getSelCards = function (array, mainFunc, subset, subFunc) 162 | local cards = {} 163 | local subArr = {} 164 | for i, v in ipairs(subset) do 165 | subArr[i] = v 166 | end 167 | 168 | for _, sp in ipairs(array) do 169 | local valueT = mainFunc(sp) 170 | 171 | for i, v in ipairs(subArr) do 172 | if valueT == subFunc(v) then 173 | table.insert(cards, sp) 174 | 175 | table.remove(subArr, i) 176 | break 177 | end 178 | end 179 | end 180 | 181 | local ok = false 182 | if #subArr == 0 then 183 | ok = true 184 | end 185 | 186 | return ok, cards 187 | end 188 | 189 | 190 | const.levelScores = { 191 | -1000, 192 | 0, 193 | 500, 194 | 1000, 195 | 2000, 196 | 5000, 197 | 10000, 198 | 20000, 199 | 50000, 200 | 100000, 201 | 200000, 202 | 500000, 203 | 1000000, 204 | 2000000, 205 | 5000000, 206 | 10000000, 207 | 20000000, 208 | 50000000, 209 | 100000000, 210 | 200000000, 211 | 500000000, 212 | 1000000000, 213 | 2000000000, 214 | } 215 | 216 | const.levelName = { 217 | "包身工", 218 | "短工", 219 | "长工", 220 | "佃户", 221 | "贫农", 222 | "渔夫", 223 | "猎人", 224 | "中农", 225 | "富农", 226 | "掌柜", 227 | "商人", 228 | "衙役", 229 | "小财主", 230 | "大财主", 231 | "小地主", 232 | "大地主", 233 | "知县", 234 | "通判", 235 | "知府", 236 | "总督", 237 | "巡抚", 238 | "丞相", 239 | "帝王", 240 | } 241 | 242 | const.findLevelRank = function(score) 243 | local ret = 1 244 | for i = 1, #const.levelScores do 245 | if score >= const.levelScores[i] then 246 | ret = i 247 | else 248 | break 249 | end 250 | end 251 | 252 | return ret 253 | end 254 | 255 | const.findLevelName = function(lvl) 256 | if not lvl or lvl < 1 then 257 | lvl = 1 258 | elseif lvl > #const.levelScores then 259 | lvl = #const.levelScores 260 | end 261 | 262 | local str = const.levelName[lvl] 263 | return str 264 | end 265 | 266 | const.getNextLevelScore = function(score) 267 | if score then 268 | local lvl = const.findLevelRank(score) + 1 269 | local nextScore 270 | if not lvl or lvl < 1 then 271 | lvl = 1 272 | elseif lvl > #const.levelScores then 273 | return nil 274 | end 275 | nextScore = const.levelScores[lvl] 276 | return nextScore 277 | end 278 | return 1 279 | end 280 | 281 | return const 282 | 283 | -------------------------------------------------------------------------------- /backend/AgentServer/WatchDog.lua: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------- 2 | ---! @file 3 | ---! @brief watchdog, 监控游戏连接 4 | -------------------------------------------------------------- 5 | 6 | ---! 系统依赖 7 | local skynet = require "skynet" 8 | local cluster = require "skynet.cluster" 9 | local socket = require "skynet.socket" 10 | local httpd = require "http.httpd" 11 | local sockethelper = require "http.sockethelper" 12 | 13 | ---! 辅助依赖 14 | local clsHelper = require "ClusterHelper" 15 | local NumSet = require "NumSet" 16 | 17 | local myInfo = nil 18 | ---! NodeInfo's address 19 | local nodeInfo = nil 20 | ---! gateserver's gate service 21 | local gate = nil 22 | local web_sock_id = nil 23 | 24 | ---! all agents 25 | local tcpAgents = NumSet.create() 26 | local webAgents = NumSet.create() 27 | 28 | ---! @brief close agent on socket fd 29 | local function close_agent(fd) 30 | local info = webAgents:getObject(fd) 31 | if info then 32 | webAgents:removeObject(fd) 33 | ---! close web socket, kick web agent 34 | pcall(skynet.send, info.agent, "lua", "disconnect") 35 | return 36 | end 37 | 38 | info = tcpAgents:getObject(fd) 39 | if info then 40 | tcpAgents:removeObject(fd) 41 | 42 | ---! close tcp socket, kick tcp agent 43 | pcall(skynet.send, info.agent, "lua", "disconnect") 44 | else 45 | skynet.error("unable to close agent, fd = ", fd) 46 | end 47 | end 48 | 49 | ---! socket handlings, SOCKET.error, SOCKET.warning, SOCKET.data 50 | ---! may not called after we transfer it to agent 51 | local SOCKET = {} 52 | 53 | ---! @brief new client from gate, start an agent and trasfer fd to agent 54 | function SOCKET.open(fd, addr) 55 | local info = tcpAgents:getObject(fd) 56 | if info then 57 | close_agent(fd) 58 | end 59 | 60 | skynet.error("watchdog tcp agent start", fd, addr) 61 | local agent = skynet.newservice("TcpAgent") 62 | 63 | info = {} 64 | info.watchdog = skynet.self() 65 | info.gate = gate 66 | info.client_fd = fd 67 | info.address = string.gsub(addr, ":%d+", "") 68 | info.agent = agent 69 | info.appName = myInfo.appName 70 | 71 | tcpAgents:addObject(info, fd) 72 | 73 | skynet.call(agent, "lua", "start", info) 74 | return 0 75 | end 76 | 77 | ---! @brief close fd, is this called after we transfer it to agent ? 78 | function SOCKET.close(fd) 79 | skynet.error("socket close",fd) 80 | 81 | skynet.timeout(10, function() 82 | close_agent(fd) 83 | end) 84 | 85 | return "" 86 | end 87 | 88 | ---! @brief error on socket, is this called after we transfer it to agent ? 89 | function SOCKET.error(fd, msg) 90 | skynet.error("socket error", fd, msg) 91 | 92 | skynet.timeout(10, function() 93 | close_agent(fd) 94 | end) 95 | end 96 | 97 | ---! @brief warnings on socket, is this called after we transfer it to agent ? 98 | function SOCKET.warning(fd, size) 99 | -- size K bytes havn't send out in fd 100 | skynet.error("socket warning", fd, size) 101 | end 102 | 103 | ---! @brief packets on socket, is this called after we transfer it to agent ? 104 | function SOCKET.data(fd, msg) 105 | end 106 | 107 | ---! skynet service handlings 108 | local CMD = {} 109 | 110 | ---! @brief this function may not be called after we transfer fd to agent 111 | function CMD.closeAgent(fd) 112 | skynet.timeout(10, function() 113 | close_agent(fd) 114 | end) 115 | 116 | return 0 117 | end 118 | 119 | ---! @brief place holder, we may use it later 120 | function CMD.noticeAll (msg) 121 | webAgents:forEach(function (info) 122 | pcall(skynet.send, info.agent, "lua", "sendProtocolPacket", msg) 123 | end) 124 | 125 | tcpAgents:forEach(function (info) 126 | pcall(skynet.send, info.agent, "lua", "sendProtocolPacket", msg) 127 | end) 128 | 129 | return 0 130 | end 131 | 132 | function CMD.getStat () 133 | local stat = {} 134 | stat.web = webAgents:getCount() 135 | stat.tcp = tcpAgents:getCount() 136 | stat.sum = stat.web + stat.tcp 137 | return stat 138 | end 139 | 140 | function CMD.gateOff () 141 | if gate then 142 | xpcall(function () 143 | skynet.call(gate, "lua", "close") 144 | end, 145 | function (err) 146 | print("gateOff -> close gate: error is ", err) 147 | end) 148 | end 149 | if web_sock_id then 150 | xpcall(function () 151 | socket.close(web_sock_id) 152 | end, 153 | function (err) 154 | print("gateOff -> close web: error is ", err) 155 | end) 156 | end 157 | end 158 | 159 | ---! 注册LoginWatchDog的处理函数,一种是skynet服务,一种是socket 160 | local function registerDispatch () 161 | skynet.dispatch("lua", function(session, source, cmd, subcmd, ...) 162 | if cmd == "socket" then 163 | local f = SOCKET[subcmd] 164 | if f then 165 | f(...) 166 | else 167 | skynet.error("unknown sub command ", subcmd, " for cmd ", cmd) 168 | end 169 | -- socket api don't need return 170 | else 171 | local f = CMD[cmd] 172 | if f then 173 | local ret = f(subcmd, ...) 174 | if ret then 175 | skynet.ret(skynet.pack(ret)) 176 | end 177 | else 178 | skynet.error("unknown command ", cmd) 179 | end 180 | end 181 | end) 182 | end 183 | 184 | ---! 处理 web socket 连接 185 | local function handle_web(id, addr) 186 | -- limit request body size to 8192 (you can pass nil to unlimit) 187 | local code, url, method, header, body = httpd.read_request(sockethelper.readfunc(id), 8192) 188 | if code and url == "/tun" then 189 | local info = webAgents:getObject(id) 190 | if info then 191 | close_agent(id) 192 | end 193 | 194 | skynet.error("watchdog web agent start", id, addr) 195 | local agent = skynet.newservice("WebAgent") 196 | 197 | local info = {} 198 | info.watchdog = skynet.self() 199 | info.gate = nil 200 | info.client_fd = id 201 | info.address = string.gsub(addr, ":%d+", "") 202 | info.agent = agent 203 | info.appName = myInfo.appName 204 | 205 | webAgents:addObject(info, id) 206 | skynet.call(agent, "lua", "start", info, header) 207 | end 208 | end 209 | 210 | ---! 开启 watchdog 功能, tcp/web 211 | local function startWatch () 212 | ---! 获得NodeInfo 服务 注册自己 213 | nodeInfo = skynet.uniqueservice(clsHelper.kNodeInfo) 214 | skynet.call(nodeInfo, "lua", "updateConfig", skynet.self(), clsHelper.kWatchDog) 215 | 216 | myInfo = skynet.call(nodeInfo, "lua", "getConfig", "nodeInfo") 217 | 218 | ---! 启动gate 219 | local publicAddr = "0.0.0.0" 220 | gate = skynet.newservice("gate") 221 | skynet.call(gate, "lua", "open", { 222 | address = publicAddr, 223 | port = myInfo.tcpPort, ---! 监听端口 8200 + serverIndex 224 | maxclient = 2048, ---! 最多允许 2048 个外部连接同时建立 注意本数值,当客户端很多时,避免阻塞 225 | nodelay = true, ---! 给外部连接设置 TCP_NODELAY 属性 226 | }) 227 | 228 | -- web tunnel, 监听 8300 + serverIndex 229 | local address = string.format("%s:%d", publicAddr, myInfo.webPort) 230 | local id = assert(socket.listen(address)) 231 | web_sock_id = id 232 | socket.start(id , function(id, addr) 233 | socket.start(id) 234 | xpcall(function () 235 | handle_web(id, addr) 236 | end, 237 | function (err) 238 | print("error is ", err) 239 | end) 240 | end) 241 | end 242 | 243 | -- 心跳, 汇报在线人数 244 | local function loopReport () 245 | local timeout = 60 -- 60 seconds 246 | while true do 247 | local stat = CMD.getStat() 248 | skynet.call(nodeInfo, "lua", "updateConfig", stat.sum, "nodeInfo", "numPlayers") 249 | local ret, nodeLink = pcall(skynet.call, nodeInfo, "lua", "getServiceAddr", clsHelper.kNodeLink) 250 | if ret and nodeLink ~= "" then 251 | pcall(skynet.send, nodeLink, "lua", "heartBeat", stat.sum) 252 | end 253 | 254 | skynet.sleep(timeout * 100) 255 | end 256 | end 257 | 258 | ---! 启动函数 259 | skynet.start(function() 260 | registerDispatch() 261 | startWatch() 262 | skynet.fork(loopReport) 263 | end) 264 | 265 | -------------------------------------------------------------------------------- /backend/Services/ProtoTypes.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------- 2 | ---! @file 3 | ---! @brief protocol type status 4 | ---------------------------------------------------------------- 5 | 6 | local class = {} 7 | setmetatable(class, { 8 | __index = function (t, k) 9 | return function() 10 | print("unknown field from protoTypes: ", k) 11 | end 12 | end 13 | }) 14 | 15 | 16 | ---! 游戏中的常量 17 | class.CGGAME_TIMEOUT_KEEPLINE = 150 18 | class.CGGAME_TIMEOUT_FORBID = 30 19 | class.CGGAME_TIMEOUT_WAITREADY = 24 20 | 21 | class.CGGAME_ROOM_TABLE_MAXID = 999999 22 | class.CGGAME_ROOM_TABLE_MINID = 100000 23 | class.CGGAME_ROOM_TABLE_EXPIRE_TIME = 8 * 60 * 60 -- 8小时停止游戏 12小时删除数据库 24 | class.CGGAME_ROOM_TABLE_EXPIRE_NO_PLAYING = 2 * 60 * 60 -- 无人玩时,最多2小时 25 | 26 | --- main proto types 27 | class.CGGAME_PROTO_MAINTYPE_BASIC = 1 -- basic proto type 28 | class.CGGAME_PROTO_SUBTYPE_MULTIPLE = 1 -- mulitiple message 29 | class.CGGAME_PROTO_SUBTYPE_ACL = 2 -- acl info 30 | class.CGGAME_PROTO_SUBTYPE_HEARTBEAT = 3 -- heart beat 31 | class.CGGAME_PROTO_HEARTBEAT_CLIENT = 1 -- heart beat from client 32 | class.CGGAME_PROTO_HEARTBEAT_SERVER = 2 -- heart beat from server 33 | class.CGGAME_PROTO_SUBTYPE_AGENTLIST = 4 -- ask for agent list 34 | class.CGGAME_PROTO_SUBTYPE_HALLTEXT = 5 -- 大厅进入通知文本 35 | class.CGGAME_PROTO_SUBTYPE_GAMETEXT = 6 -- 游戏进入通知文本 36 | class.CGGAME_PROTO_SUBTYPE_NOTICE = 7 -- system notice 37 | 38 | class.CGGAME_PROTO_MAINTYPE_AUTH = 10 -- auth proto type 39 | class.CGGAME_PROTO_SUBTYPE_ASKRESUME = 1 -- client -> server, ask resume/ask auth 40 | class.CGGAME_PROTO_SUBTYPE_CHALLENGE = 2 -- server -> client, give a challenge key 41 | class.CGGAME_PROTO_SUBTYPE_CLIENTKEY = 3 -- client -> server, give a client key 42 | class.CGGAME_PROTO_SUBTYPE_SERVERKEY = 4 -- server -> client, give a server key 43 | class.CGGAME_PROTO_SUBTYPE_RESUME_OK = 5 -- server -> client, tell resume ok 44 | 45 | class.CGGAME_PROTO_MAINTYPE_HALL = 20 -- hall proto type 46 | class.CGGAME_PROTO_SUBTYPE_QUIT = 1 -- quit hall and game 47 | class.CGGAME_PROTO_SUBTYPE_HALLJOIN = 2 -- join to hall 48 | class.CGGAME_PROTO_SUBTYPE_MYINFO = 3 -- update user info 49 | class.CGGAME_PROTO_SUBTYPE_MYSTATUS = 4 -- update user status 50 | class.CGGAME_PROTO_SUBTYPE_BONUS = 5 -- recv bonus 51 | class.CGGAME_PROTO_BONUS_DAILY = 1 -- daily bonus 52 | class.CGGAME_PROTO_BONUS_SHARE = 2 -- bonus for share 53 | class.CGGAME_PROTO_SUBTYPE_CHAT = 6 -- chat to user 54 | class.CGGAME_PROTO_SUBTYPE_USERINFO = 7 -- 别人用户信息 55 | class.CGGAME_PROTO_SUBTYPE_USERSTATUS = 8 -- 别人的在线信息 56 | 57 | 58 | class.CGGAME_PROTO_MAINTYPE_CLUB = 30 -- 俱乐部 59 | 60 | class.CGGAME_PROTO_MAINTYPE_ROOM = 40 -- 房卡 房间信息 61 | class.CGGAME_PROTO_SUBTYPE_CREATE = 1 -- 开房 62 | class.CGGAME_PROTO_SUBTYPE_JOIN = 2 -- 进房 63 | class.CGGAME_PROTO_SUBTYPE_RELEASE = 3 -- 退房 64 | class.CGGAME_PROTO_SUBTYPE_INFO = 4 -- 信息 65 | class.CGGAME_PROTO_SUBTYPE_RESULT = 5 -- 一局战绩 66 | class.CGGAME_PROTO_SUBTYPE_RESULT_ALL = 6 -- 全部战绩 67 | 68 | 69 | class.CGGAME_PROTO_MAINTYPE_GAME = 50 -- 游戏 70 | class.CGGAME_PROTO_SUBTYPE_GAMEJOIN = 1 -- 加入游戏 71 | class.CGGAME_PROTO_SUBTYPE_GAMETRACE = 2 -- 各游戏内部协议 72 | class.CGGAME_PROTO_SUBTYPE_BROADCAST = 3 -- 游戏的广播 73 | ---! events 74 | class.CGGAME_MSG_EVENT_SITDOWN = 1 -- 坐下 75 | class.CGGAME_MSG_EVENT_STANDUP = 2 -- 站起 76 | class.CGGAME_MSG_EVENT_STANDBY = 3 -- 旁观 77 | class.CGGAME_MSG_EVENT_READY = 4 -- 准备 78 | class.CGGAME_MSG_EVENT_QUITTABLE = 5 -- 退桌 79 | class.CGGAME_MSG_EVENT_BREAK = 6 -- 掉线 80 | class.CGGAME_MSG_EVENT_CONTINUE = 7 -- 继续 81 | 82 | class.CGGAME_PROTO_SUBTYPE_GIFT = 4 -- 发送礼物 83 | class.CGGAME_PROTO_SUBTYPE_TABLEMAP = 5 -- 座位图 84 | class.CGGAME_PROTO_SUBTYPE_GAMEINFO = 6 -- 游戏信息 85 | class.CGGAME_PROTO_SUBTYPE_WAITUSER = 7 -- 等候用户 86 | class.CGGAME_PROTO_SUBTYPE_GAMEOVER = 8 -- 结束信息 87 | 88 | class.CGGAME_PROTO_SUBTYPE_SITDOWN = 11 -- 坐下 89 | class.CGGAME_PROTO_SUBTYPE_READY = 12 -- 准备 90 | class.CGGAME_PROTO_SUBTYPE_CONFIRM = 13 -- 确定开始 91 | class.CGGAME_PROTO_SUBTYPE_STANDUP = 14 -- 站起 92 | class.CGGAME_PROTO_SUBTYPE_STANDBY = 15 -- 旁观 93 | class.CGGAME_PROTO_SUBTYPE_CHANGETABLE = 16 -- 换桌 94 | 95 | class.CGGAME_PROTO_SUBTYPE_QUITTABLE = 17 -- for card/board game 96 | class.CGGAME_PROTO_SUBTYPE_QUITSTAGE = 18 -- for strategy game 97 | 98 | class.CGGAME_PROTO_SUBTYPE_USER_DEFINE = 100 -- each game's subtype data start from 100 99 | 100 | 101 | ---! 房卡支付 102 | class.CGGAME_ROOM_LEAST_COINS = 1 103 | class.CGGAME_ROOM_PAYTYPE_OWNER = 0 104 | class.CGGAME_ROOM_PAYTYPE_PLAYERS = 1 105 | class.CGGAME_ROOM_PAYTYPE_WINNER = 2 106 | 107 | 108 | ---! 用户在线状态 109 | class.CGGAME_USER_STATUS_IDLE = 0 110 | class.CGGAME_USER_STATUS_OFFLINE = 1 111 | class.CGGAME_USER_STATUS_STANDUP = 2 112 | class.CGGAME_USER_STATUS_STANDBY = 3 113 | class.CGGAME_USER_STATUS_SITDOWN = 4 114 | class.CGGAME_USER_STATUS_READY = 5 115 | class.CGGAME_USER_STATUS_PLAYING = 6 116 | 117 | 118 | ---! 桌子状态 119 | class.CGGAME_TABLE_STATUS_IDLE = 0 120 | class.CGGAME_TABLE_STATUS_WAITREADY = 1 121 | class.CGGAME_TABLE_STATUS_WAITCONFIRM = 2 122 | class.CGGAME_TABLE_STATUS_PLAYING = 3 123 | 124 | 125 | ---! ACL status code 用于回应客户端消息的状态码 126 | --- 0 ~ 9 for success 127 | class.CGGAME_ACL_STATUS_SUCCESS = 0 128 | class.CGGAME_ACL_STATUS_ALREADY = 1 129 | -- class.CGGAME_ACL_STATUS_ALREADY_AGENTCODE = 1011 130 | 131 | --- 100 ~ 999 for each game 132 | 133 | ---- 1000 for common handler 134 | class.CGGAME_ACL_STATUS_SERVER_BUSY = 1000 -- 服务器繁忙 135 | class.CGGAME_ACL_STATUS_INVALID_INFO = 1001 -- 提供的信息有误 136 | class.CGGAME_ACL_STATUS_INVALID_COMMAND = 1002 -- 未知或不合法的命令 137 | class.CGGAME_ACL_STATUS_AUTH_FAILED = 1003 -- 授权失败 138 | class.CGGAME_ACL_STATUS_COUNTER_FAILED = 1004 -- 139 | class.CGGAME_ACL_STATUS_SERVER_ERROR = 1005 140 | class.CGGAME_ACL_STATUS_SHARE_EXCEED = 1006 141 | class.CGGAME_ACL_STATUS_OLDVERSION = 1007 -- 版本过旧 142 | class.CGGAME_ACL_STATUS_NODE_OFF = 1008 -- 所在服务器的节点即将关闭 143 | 144 | class.CGGAME_ACL_STATUS_INVALID_AGENTCODE = 1010 -- 代理号有误 145 | 146 | class.CGGAME_ACL_STATUS_ROOM_DB_FAILED = 1020 -- 房间创建时数据库有误 147 | class.CGGAME_ACL_STATUS_ROOM_CREATE_FAILED = 1021 -- 房间创建失败 148 | class.CGGAME_ACL_STATUS_ROOM_FIND_FAILED = 1022 -- 找不到房间号 149 | class.CGGAME_ACL_STATUS_ROOM_JOIN_FULL = 1023 -- 房间已满,无法加入 150 | class.CGGAME_ACL_STATUS_ROOM_NOT_SUPPORT = 1024 -- 尚不支持房号功能 151 | class.CGGAME_ACL_STATUS_ROOM_NO_SUCH_PAYTYPE = 1025 -- 支付方式不支持 152 | 153 | 154 | class.isACLSuccess = function (status) 155 | return status < 100 156 | end 157 | 158 | class.isACLFailed = function (status) 159 | return status >= 100 160 | end 161 | 162 | return class 163 | 164 | -------------------------------------------------------------------------------- /DB_CGGame.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.14 Distrib 5.5.56-MariaDB, for Linux (x86_64) 2 | -- 3 | -- Host: 192.168.0.121 Database: DB_CGGame 4 | -- ------------------------------------------------------ 5 | -- Server version 5.5.56-MariaDB 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `TAgent` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `TAgent`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `TAgent` ( 26 | `FAgentCode` int(11) NOT NULL AUTO_INCREMENT, 27 | `FUniqueID` char(40) NOT NULL, 28 | `FAgentLead` int(11) NOT NULL DEFAULT '0', 29 | `FAgentType` int(11) NOT NULL DEFAULT '0', 30 | `FJoinTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 31 | `FAgentStatus` int(11) NOT NULL DEFAULT '1', 32 | PRIMARY KEY (`FAgentCode`), 33 | UNIQUE KEY `FUniqueID` (`FUniqueID`), 34 | KEY `idx_agent_agentlead` (`FAgentLead`), 35 | KEY `idx_agent_agenttype` (`FAgentType`) 36 | ) ENGINE=MyISAM AUTO_INCREMENT=100000 DEFAULT CHARSET=utf8; 37 | /*!40101 SET character_set_client = @saved_cs_client */; 38 | 39 | -- 40 | -- Table structure for table `TGame` 41 | -- 42 | 43 | DROP TABLE IF EXISTS `TGame`; 44 | /*!40101 SET @saved_cs_client = @@character_set_client */; 45 | /*!40101 SET character_set_client = utf8 */; 46 | CREATE TABLE `TGame` ( 47 | `FGameID` int(11) NOT NULL, 48 | `FGameName` char(20) DEFAULT NULL, 49 | `FTableName` char(20) NOT NULL, 50 | `FReturnRate1` float DEFAULT '0', 51 | `FReturnRate2` float DEFAULT '0', 52 | `FBindBonus` int(11) DEFAULT '0', 53 | `FAppID` char(24) NOT NULL, 54 | `FAppSecret` char(36) NOT NULL, 55 | `FMerchantID` char(20) NOT NULL, 56 | `FMerchantPass` char(36) NOT NULL, 57 | `FXiaomiAppID` char(24) DEFAULT '', 58 | `FXiaomiAppKey` char(16) DEFAULT '', 59 | `FXiaomiAppSecret` char(28) DEFAULT '', 60 | `F360AppKey` char(36) DEFAULT '', 61 | `F360AppSecret` char(36) DEFAULT '', 62 | `FVivoAppId` char(36) DEFAULT '', 63 | `FVivoAppKey` char(36) DEFAULT '', 64 | `FAliAppId` char(20) DEFAULT '', 65 | `FAliAppKey` char(36) DEFAULT '', 66 | PRIMARY KEY (`FGameID`), 67 | KEY `idx_game_appid` (`FAppID`), 68 | KEY `idx_game_xiaomi_appid` (`FXiaomiAppID`), 69 | KEY `idx_game_360_appkey` (`F360AppKey`), 70 | KEY `idx_game_vivo_appid` (`FVivoAppId`), 71 | KEY `idx_game_ali_appid` (`FAliAppId`) 72 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 73 | /*!40101 SET character_set_client = @saved_cs_client */; 74 | 75 | -- 76 | -- Table structure for table `TLandlordUser` 77 | -- 78 | 79 | DROP TABLE IF EXISTS `TLandlordUser`; 80 | /*!40101 SET @saved_cs_client = @@character_set_client */; 81 | /*!40101 SET character_set_client = utf8 */; 82 | CREATE TABLE `TLandlordUser` ( 83 | `FUniqueID` char(40) NOT NULL, 84 | `FUserCode` int(11) NOT NULL AUTO_INCREMENT, 85 | `FAgentCode` int(11) NOT NULL DEFAULT '0', 86 | `FCounter` int(11) NOT NULL DEFAULT '5000', 87 | `FChargeMoney` decimal(10,2) DEFAULT '0.00', 88 | `FChargeCounter` int(11) DEFAULT '0', 89 | `FScore` int(11) NOT NULL DEFAULT '0', 90 | `FWins` int(11) NOT NULL DEFAULT '0', 91 | `FLoses` int(11) NOT NULL DEFAULT '0', 92 | `FDraws` int(11) NOT NULL DEFAULT '0', 93 | `FLastGameTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 94 | `FSaveCount` int(11) DEFAULT '0', 95 | `FSaveDate` int(11) DEFAULT '0', 96 | PRIMARY KEY (`FUserCode`), 97 | UNIQUE KEY `FUniqueID` (`FUniqueID`), 98 | KEY `idx_landlord_lasttime` (`FLastGameTime`), 99 | KEY `idx_landlord_counter` (`FCounter`), 100 | KEY `idx_landlord_agentcode` (`FAgentCode`) 101 | ) ENGINE=MyISAM AUTO_INCREMENT=100000 DEFAULT CHARSET=utf8; 102 | /*!40101 SET character_set_client = @saved_cs_client */; 103 | 104 | -- 105 | -- Table structure for table `TRoomInfo` 106 | -- 107 | 108 | DROP TABLE IF EXISTS `TRoomInfo`; 109 | /*!40101 SET @saved_cs_client = @@character_set_client */; 110 | /*!40101 SET character_set_client = utf8 */; 111 | CREATE TABLE `TRoomInfo` ( 112 | `FRoomID` int(11) NOT NULL, 113 | `FGameID` int(11) NOT NULL, 114 | `FOwnerCode` int(11) NOT NULL, 115 | `FOpenTime` datetime DEFAULT NULL, 116 | `FGameCount` int(11) DEFAULT NULL, 117 | PRIMARY KEY (`FRoomID`), 118 | KEY `idx_roominfo_opentime` (`FOpenTime`), 119 | KEY `idx_roominfo_owner` (`FOwnerCode`) 120 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 121 | /*!40101 SET character_set_client = @saved_cs_client */; 122 | 123 | -- 124 | -- Table structure for table `TShop` 125 | -- 126 | 127 | DROP TABLE IF EXISTS `TShop`; 128 | /*!40101 SET @saved_cs_client = @@character_set_client */; 129 | /*!40101 SET character_set_client = utf8 */; 130 | CREATE TABLE `TShop` ( 131 | `FShopID` varchar(60) NOT NULL, 132 | `FShopDesc` varchar(200) DEFAULT NULL, 133 | `FGameID` int(11) NOT NULL, 134 | `FFieldName` varchar(20) NOT NULL DEFAULT 'FCounter', 135 | `FMoney` decimal(6,2) NOT NULL DEFAULT '0.00', 136 | `FValue` int(11) NOT NULL DEFAULT '0', 137 | `FBonus` int(11) NOT NULL DEFAULT '0', 138 | PRIMARY KEY (`FShopID`) 139 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 140 | /*!40101 SET character_set_client = @saved_cs_client */; 141 | 142 | -- 143 | -- Table structure for table `TUseRecord` 144 | -- 145 | 146 | DROP TABLE IF EXISTS `TUseRecord`; 147 | /*!40101 SET @saved_cs_client = @@character_set_client */; 148 | /*!40101 SET character_set_client = utf8 */; 149 | CREATE TABLE `TUseRecord` ( 150 | `FRecordID` int(11) NOT NULL AUTO_INCREMENT, 151 | `FDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 152 | `FRoomID` int(11) NOT NULL, 153 | `FGameID` int(11) NOT NULL, 154 | `FUniqueID` char(40) NOT NULL, 155 | `FCounter` int(11) NOT NULL DEFAULT '0', 156 | `FOldCounter` int(11) NOT NULL DEFAULT '0', 157 | `FNewCounter` int(11) NOT NULL DEFAULT '0', 158 | PRIMARY KEY (`FRecordID`), 159 | KEY `idx_userecord_roomid` (`FRoomID`), 160 | KEY `idx_userecord_gameid` (`FGameID`), 161 | KEY `idx_userecord_uid` (`FUniqueID`) 162 | ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 163 | /*!40101 SET character_set_client = @saved_cs_client */; 164 | 165 | -- 166 | -- Table structure for table `TUser` 167 | -- 168 | 169 | DROP TABLE IF EXISTS `TUser`; 170 | /*!40101 SET @saved_cs_client = @@character_set_client */; 171 | /*!40101 SET character_set_client = utf8 */; 172 | CREATE TABLE `TUser` ( 173 | `FUniqueID` char(40) NOT NULL, 174 | `FPassword` char(32) DEFAULT 'Pa$$w0rd', 175 | `FNickName` varchar(100) NOT NULL DEFAULT '', 176 | `FOSType` varchar(32) NOT NULL DEFAULT '', 177 | `FPlatform` varchar(32) NOT NULL DEFAULT '', 178 | `FAvatarID` int(11) NOT NULL DEFAULT '0', 179 | `FAvatarData` blob, 180 | `FAvatarUrl` varchar(256) DEFAULT NULL, 181 | `FMobile` varchar(20) DEFAULT '', 182 | `FEmail` varchar(120) DEFAULT NULL, 183 | `FIDCard` varchar(40) NOT NULL DEFAULT '', 184 | `FTotalTime` double DEFAULT '0', 185 | `FRegTime` datetime DEFAULT NULL, 186 | `FLastLoginTime` datetime DEFAULT NULL, 187 | `FLastIP` char(20) NOT NULL, 188 | `FLongitude` double DEFAULT NULL, 189 | `FLatitude` double DEFAULT NULL, 190 | `FAltitude` double DEFAULT NULL, 191 | `FLocation` varchar(1024) DEFAULT NULL, 192 | `FNetSpeed` double DEFAULT NULL, 193 | UNIQUE KEY `FUniqueID` (`FUniqueID`), 194 | KEY `idx_flastlogintime` (`FLastLoginTime`), 195 | KEY `idx_flastip` (`FLastIP`) 196 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 197 | /*!40101 SET character_set_client = @saved_cs_client */; 198 | 199 | -- 200 | -- Table structure for table `TYunChengUser` 201 | -- 202 | 203 | DROP TABLE IF EXISTS `TYunChengUser`; 204 | /*!40101 SET @saved_cs_client = @@character_set_client */; 205 | /*!40101 SET character_set_client = utf8 */; 206 | CREATE TABLE `TYunChengUser` ( 207 | `FUniqueID` char(40) NOT NULL, 208 | `FUserCode` int(11) NOT NULL AUTO_INCREMENT, 209 | `FAgentCode` int(11) NOT NULL DEFAULT '0', 210 | `FCounter` int(11) NOT NULL DEFAULT '5000', 211 | `FChargeMoney` decimal(10,2) DEFAULT '0.00', 212 | `FChargeCounter` int(11) DEFAULT '0', 213 | `FScore` int(11) NOT NULL DEFAULT '0', 214 | `FWins` int(11) NOT NULL DEFAULT '0', 215 | `FLoses` int(11) NOT NULL DEFAULT '0', 216 | `FDraws` int(11) NOT NULL DEFAULT '0', 217 | `FLastGameTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 218 | `FSaveCount` int(11) DEFAULT '0', 219 | `FSaveDate` int(11) DEFAULT '0', 220 | PRIMARY KEY (`FUserCode`), 221 | UNIQUE KEY `FUniqueID` (`FUniqueID`), 222 | KEY `idx_yuncheng_lasttime` (`FLastGameTime`), 223 | KEY `idx_yuncheng_counter` (`FCounter`), 224 | KEY `idx_yuncheng_agentcode` (`FAgentCode`) 225 | ) ENGINE=MyISAM AUTO_INCREMENT=100000 DEFAULT CHARSET=utf8; 226 | /*!40101 SET character_set_client = @saved_cs_client */; 227 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 228 | 229 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 230 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 231 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 232 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 233 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 234 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 235 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 236 | 237 | -- Dump completed on 2019-01-04 15:48:26 238 | --------------------------------------------------------------------------------