├── xjgame ├── data │ ├── defineNpc.lua │ ├── defineMonster.lua │ ├── defineItem.lua │ ├── propItem.lua │ ├── propNpc.lua │ ├── propMonster.lua │ └── propScene.lua ├── etc │ ├── clustername.lua │ ├── config.c2 │ ├── config.login │ └── runconfig.lua ├── database │ ├── redis_character_handle.lua │ ├── redis_namespace_handle.lua │ ├── redis_namespace_login.lua │ ├── redis_character_login.lua │ ├── redis_character_athletic.lua │ ├── redis_namespace.lua │ ├── redis_define.lua │ ├── redis_character.lua │ └── redis_script.lua ├── proto │ ├── xjproto.lua │ ├── xjprotoloader.lua │ ├── s2c.lua │ └── c2s.lua ├── player │ ├── player_copy.lua │ ├── player_monster.lua │ ├── player_npc.lua │ ├── player_scene.lua │ ├── player_athletic.lua │ ├── player_aoi.lua │ ├── player_base.lua │ └── player_login.lua ├── service │ ├── protoloader.lua │ ├── activity │ │ ├── activity_moneytree.lua │ │ ├── activity_nationalday.lua │ │ ├── activity_worldboss.lua │ │ └── activity_doublepower.lua │ ├── agentpool.lua │ ├── eventserver_node.lua │ ├── scene │ │ ├── sceneblance.lua │ │ └── sceneserver.lua │ ├── eventserver.lua │ ├── athleticserver.lua │ ├── luaconf.lua │ ├── debugconsole.lua │ └── activityserver.lua ├── lualib │ ├── eventlistener.lua │ ├── activityd.lua │ ├── skynetex.lua │ ├── npc.lua │ ├── monster.lua │ ├── conf.lua │ ├── aoidriver.lua │ ├── loginserverd.lua │ └── msgserverd.lua ├── login │ ├── logind.lua │ └── gated.lua └── game │ ├── main.lua │ └── agent.lua ├── .gitmodules ├── .gitignore ├── README.md └── Makefile /xjgame/data/defineNpc.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | NPC_ID_1 = 1 4 | NPC_ID_2 = 2 -------------------------------------------------------------------------------- /xjgame/data/defineMonster.lua: -------------------------------------------------------------------------------- 1 | 2 | MONSTER_ID_1 = 1 3 | MONSTER_ID_2 = 2 -------------------------------------------------------------------------------- /xjgame/data/defineItem.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | ITEM_WEAPON_1 = 1 4 | ITEM_WEAPON_2 = 2 -------------------------------------------------------------------------------- /xjgame/etc/clustername.lua: -------------------------------------------------------------------------------- 1 | login = "127.0.0.1:2501" 2 | node1 = "127.0.0.1:2502" 3 | -------------------------------------------------------------------------------- /xjgame/database/redis_character_handle.lua: -------------------------------------------------------------------------------- 1 | 2 | local M = skynet.module("redis_character_handle") 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /xjgame/data/propItem.lua: -------------------------------------------------------------------------------- 1 | 2 | propItem = { 3 | [ITEM_WEAPON_1] = { ID=1, Name="武器1", Desc="描述" }, 4 | [ITEM_WEAPON_2] = { ID=2, Name="武器2", Desc="描述" }, 5 | } -------------------------------------------------------------------------------- /xjgame/data/propNpc.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | propNpc = { 4 | [NPC_ID_1] = { ID=1, Name="NPC1", Desc="npc 1 desc" }, 5 | [NPC_ID_2] = { ID=2, Name="NPC2", Desc="npc 2 desc" }, 6 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "skynet"] 2 | path = skynet 3 | url = git@github.com:cloudwu/skynet.git 4 | [submodule "lua-conf"] 5 | path = lua-conf 6 | url = git@github.com:cloudwu/lua-conf.git 7 | -------------------------------------------------------------------------------- /xjgame/data/propMonster.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | propMonster = { 5 | [MONSTER_ID_1] = { ID=1, Name="MONSTER 1", Desc="monster 1 desc" }, 6 | [MONSTER_ID_2] = { ID=2, Name="MONSTER 2", Desc="monster 2 desc" }, 7 | } -------------------------------------------------------------------------------- /xjgame/proto/xjproto.lua: -------------------------------------------------------------------------------- 1 | local sprotoparser = require "sprotoparser" 2 | 3 | local proto = {} 4 | 5 | proto.c2s = sprotoparser.parse(require "c2s") 6 | 7 | proto.s2c = sprotoparser.parse(require "s2c") 8 | 9 | return proto 10 | -------------------------------------------------------------------------------- /xjgame/database/redis_namespace_handle.lua: -------------------------------------------------------------------------------- 1 | local DB = DB 2 | local skynet = require "skynetex" 3 | 4 | local M = skynet.module("redis_namespace_handle") 5 | 6 | function M.np_get_name_by_id( id ) 7 | return DB:get("namespace:player:id:"..id) 8 | end 9 | 10 | 11 | -------------------------------------------------------------------------------- /xjgame/player/player_copy.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 副本闯关玩法逻辑 4 | -- 5 | 6 | local skynet = require "skynetex" 7 | local M = skynet.module("player") 8 | 9 | 10 | -- 前端出发通关请求 11 | function M.copy_pass( args ) 12 | -- 做是否能通关判断 13 | -- 如果是前端做战斗的话,需要在此验证战斗合法性, 并做战斗结算相关逻辑 14 | 15 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Libraries 8 | *.lib 9 | *.a 10 | 11 | # Shared objects (inc. Windows DLLs) 12 | *.dll 13 | *.so 14 | *.so.* 15 | *.dylib 16 | 17 | # Executables 18 | *.exe 19 | *.out 20 | *.app 21 | *.i*86 22 | *.x86_64 23 | *.hex 24 | -------------------------------------------------------------------------------- /xjgame/data/propScene.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | propScene = { 4 | [1] = { 5 | name = "场景1", 6 | width = 200, 7 | height = 100, 8 | tilewidth = 10, 9 | tileheight = 10, 10 | monsters = { 11 | {1, 15, 25}, {2, 5,5}, 12 | }, 13 | npcs = { 14 | {1, 10,20}, {2, 20,20}, 15 | }, 16 | 17 | map = { 18 | 19 | }, 20 | }, 21 | } -------------------------------------------------------------------------------- /xjgame/player/player_monster.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local skynet = require "skynetex" 4 | local M = skynet.module("player") 5 | 6 | -- 玩家进入怪物视野内,触发的ai逻辑由此接口完成 7 | function M.monster_player_enter( monster_objid, monster_id ) 8 | 9 | end 10 | 11 | -- 玩家离开怪物视野,触发的ai逻辑由此接口完成 12 | function M.monster_player_exit( monster_objid, monster_id ) 13 | 14 | end -------------------------------------------------------------------------------- /xjgame/service/protoloader.lua: -------------------------------------------------------------------------------- 1 | 2 | local skynet = require "skynet" 3 | local sprotoloader = require "sprotoloader" 4 | local proto = require "proto" 5 | 6 | skynet.start(function() 7 | sprotoloader.save(proto.c2s, 1) 8 | sprotoloader.save(proto.s2c, 2) 9 | -- don't call skynet.exit() , because sproto.core may unload and the global slot become invalid 10 | end) 11 | -------------------------------------------------------------------------------- /xjgame/proto/xjprotoloader.lua: -------------------------------------------------------------------------------- 1 | 2 | local skynet = require "skynet" 3 | local sprotoparser = require "sprotoparser" 4 | local sprotoloader = require "sprotoloader" 5 | local proto = require "xjproto" 6 | 7 | skynet.start(function() 8 | sprotoloader.save(proto.c2s, 1) 9 | sprotoloader.save(proto.s2c, 2) 10 | -- don't call skynet.exit() , because sproto.core may unload and the global slot become invalid 11 | end) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mmoarpgserver 2 | a simple mmoarpg game server base on skynet 3 | # build 4 | For linux, install autoconf first for jemalloc 5 | ``` 6 | cd skynet 7 | make 'PLATFORM' # PLATFORM can be linux, macosx now 8 | ``` 9 | Or you can : 10 | ``` 11 | export PLAT=linux 12 | make 13 | ``` 14 | #Test 15 | Run this in console 16 | 17 | ``` 18 | cd skynet/skynet 19 | ./skynet ../xjgame/etc/config.login 20 | -------------------------------------------------------------------------------- /xjgame/player/player_npc.lua: -------------------------------------------------------------------------------- 1 | 2 | local skynet = require "skynetex" 3 | local M = skynet.module("player") 4 | 5 | -- 玩家进入npc视野内,触发的ai逻辑由此接口完成 6 | function M.npc_player_enter( npc_objid, npc_id ) 7 | self.talk_npc_ = {npc_objid, npc_id} 8 | -- 触发与npc的对话逻辑 9 | -- ... 10 | end 11 | 12 | -- 玩家离开npc视野,触发的ai逻辑由此接口完成 13 | function M.npc_player_exit( npc_objid, npc_id ) 14 | -- 清除与npc对话的条件 15 | self.talk_npc_ = nil 16 | end 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /xjgame/database/redis_namespace_login.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynetex" 2 | local M = skynet.module("redis_namespace_handle") 3 | 4 | -- 获取 serverid 服里账号 account 所有的角色列表 5 | function M.np_get_role_list(account, serverid) 6 | assert(account) 7 | 8 | local key = "namespace:account:"..serverid..":"..account 9 | 10 | -- 返回所有的key, 这些key就是所有的角色id, 格式:{1001,1002} 11 | local list = DB:hkeys(key) 12 | local ret = {} 13 | 14 | for _, v in pairs(list) do 15 | ret[tonumber(v)] = tonumber(v) 16 | end 17 | 18 | return ret 19 | end 20 | 21 | function M.np_create_role(account, name, serverid) 22 | return DB:eval(REDIS_SCRIPT.NAMESPACE_CREATE_ROLE, 3, serverid, account, name) 23 | end 24 | -------------------------------------------------------------------------------- /xjgame/etc/config.c2: -------------------------------------------------------------------------------- 1 | thread = 8 2 | logger = nil 3 | harbor = 0 4 | bootstrap = "snlua bootstrap" -- The service for bootstrap 5 | lualoader = "lualib/loader.lua" 6 | cpath = "./cservice/?.so" 7 | cluster = "../xjgame/etc/clustername.lua" 8 | luaservice = "./service/?.lua;../xjgame/?.lua;../xjgame/etc/?.lua;../xjgame/login/?.lua;../xjgame/lualib/?.lua;../xjgame/proto/?.lua;../xjgame/service/?.lua;../xjgame/game/?.lua" 9 | lua_path = "./lualib/?.lua;../xjgame/?.lua;../xjgame/etc/?.lua;../xjgame/login/?.lua;../xjgame/lualib/?.lua;../xjgame/proto/?.lua;../xjgame/service/?.lua;../xjgame/game/?.lua" 10 | lua_cpath = "./cservice/?.so;./luaclib/?.so;../xjgame/luaclib/?.so" 11 | runconfig = "runconfig" 12 | serverid = 1 13 | 14 | start = "main" 15 | harborname = "node1" 16 | -------------------------------------------------------------------------------- /xjgame/etc/config.login: -------------------------------------------------------------------------------- 1 | thread = 8 2 | logger = nil 3 | harbor = 0 4 | bootstrap = "snlua bootstrap" -- The service for bootstrap 5 | lualoader = "lualib/loader.lua" 6 | cpath = "./cservice/?.so" 7 | cluster = "../xjgame/etc/clustername.lua" 8 | luaservice = "./service/?.lua;../xjgame/?.lua;../xjgame/etc/?.lua;../xjgame/login/?.lua;../xjgame/lualib/?.lua;../xjgame/proto/?.lua;../xjgame/service/?.lua;../xjgame/game/?.lua" 9 | lua_path = "./lualib/?.lua;../xjgame/?.lua;../xjgame/etc/?.lua;../xjgame/login/?.lua;../xjgame/lualib/?.lua;../xjgame/proto/?.lua;../xjgame/service/?.lua;../xjgame/game/?.lua" 10 | lua_cpath = "./cservice/?.so;./luaclib/?.so;../xjgame/luaclib/?.so" 11 | runconfig = "runconfig" 12 | serverid = 1 13 | 14 | start = "main" 15 | harborname = "login" 16 | -------------------------------------------------------------------------------- /xjgame/proto/s2c.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | return [[ 4 | 5 | 6 | .package { 7 | type 0 : integer 8 | session 1 : integer 9 | } 10 | 11 | heartbeat 1 {} 12 | 13 | .AOIObject { 14 | model 0 : string 15 | objid 1 : string 16 | id 2 : integer 17 | job 3 : integer 18 | x 4 : integer 19 | y 5 : integer 20 | oriend 6 : integer 21 | attack_id 7 : integer 22 | action_id 8 : integer 23 | dress_id 9 : integer 24 | dst 10 : integer 25 | isnpc 11 : boolean 26 | ismonster 12 : boolean 27 | isplayer 13 : boolean 28 | harborname 14 : string 29 | address 15 : integer 30 | } 31 | 32 | # aoi 对象列表, 用于更新列表 33 | scene_aoi_list 2 { 34 | request { 35 | obj_list 0 : *AOIObject 36 | } 37 | } 38 | 39 | scene_aoi_exit 3 { 40 | request { 41 | objid 0 : string 42 | } 43 | } 44 | 45 | ]] -------------------------------------------------------------------------------- /xjgame/service/activity/activity_moneytree.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 摇钱树活动逻辑 4 | -- 5 | 6 | local skynet = require "skynetex" 7 | require "skynet.manager" 8 | local cluster = require "cluster" 9 | local activityd = require "activityd" 10 | 11 | local activity = { 12 | activity_id = 1, 13 | activity_name = ".activity_moneytree", 14 | } 15 | 16 | local is_start = false 17 | 18 | -- 活动开启逻辑 19 | function activity.handle_start( ) 20 | is_start = true 21 | 22 | -- 在此做活动开启逻辑 23 | -- ... 24 | end 25 | 26 | -- 活动结束逻辑 27 | function activity.handle_end( ) 28 | is_start = false 29 | 30 | -- 在此做活动结束逻辑 31 | -- ... 32 | 33 | end 34 | 35 | -- 活动即将开启预告 36 | function activity.handle_pre_begin( ti ) 37 | 38 | end 39 | 40 | -- 活动即将结束预告 41 | function activity.handle_pre_stop( ti ) 42 | 43 | end 44 | 45 | 46 | activityd.start(activity) 47 | 48 | -------------------------------------------------------------------------------- /xjgame/service/activity/activity_nationalday.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 国庆节活动逻辑 4 | -- 5 | 6 | local skynet = require "skynetex" 7 | require "skynet.manager" 8 | local cluster = require "cluster" 9 | local activityd = require "activityd" 10 | 11 | local activity = { 12 | activity_id = 4, 13 | activity_name = ".activity_nationalday", 14 | } 15 | 16 | local is_start = false 17 | 18 | -- 活动开启逻辑 19 | function activity.handle_start( ) 20 | is_start = true 21 | 22 | -- 在此做活动开启逻辑 23 | -- ... 24 | end 25 | 26 | -- 活动结束逻辑 27 | function activity.handle_end( ) 28 | is_start = false 29 | 30 | -- 在此做活动结束逻辑 31 | -- ... 32 | end 33 | 34 | -- 活动即将开启预告 35 | function activity.handle_pre_begin( ti ) 36 | 37 | end 38 | 39 | -- 活动即将结束预告 40 | function activity.handle_pre_stop( ti ) 41 | 42 | end 43 | 44 | 45 | activityd.start(activity) 46 | 47 | -------------------------------------------------------------------------------- /xjgame/service/activity/activity_worldboss.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 世界boss 活动逻辑 4 | -- 5 | 6 | local skynet = require "skynetex" 7 | require "skynet.manager" 8 | local cluster = require "cluster" 9 | local activityd = require "activityd" 10 | 11 | local activity = { 12 | activity_id = 2, 13 | activity_name = ".activity_worldboss", 14 | } 15 | 16 | local is_start = false 17 | 18 | -- 活动开启逻辑 19 | function activity.handle_start( ) 20 | is_start = true 21 | 22 | -- 在此做活动开启逻辑 23 | -- ... 24 | end 25 | 26 | -- 活动结束逻辑 27 | function activity.handle_end( ) 28 | is_start = false 29 | 30 | -- 在此做活动结束逻辑 31 | -- ... 32 | end 33 | 34 | -- 活动即将开启预告 35 | function activity.handle_pre_begin( ti ) 36 | 37 | end 38 | 39 | -- 活动即将结束预告 40 | function activity.handle_pre_stop( ti ) 41 | 42 | end 43 | 44 | 45 | activityd.start(activity) 46 | 47 | -------------------------------------------------------------------------------- /xjgame/database/redis_character_login.lua: -------------------------------------------------------------------------------- 1 | 2 | local M = skynet.module("redis_character_handle") 3 | 4 | 5 | -- 创建角色 6 | function M.ct_create_role( account, player_id, name, job, create_ip, channel_id ) 7 | local ret = DB:eval(REDIS_SCRIPT.CHARACTER_CREATE_ROLE, 7, player_id, name, account, os.time(), create_ip, channel_id, job) 8 | ret = tonumber(ret) 9 | if ret > 0 then 10 | -- 创建角色成功,初始化属性 11 | 12 | end 13 | return ret 14 | end 15 | 16 | -- 获取玩家基础信息 17 | function M.ct_get_role_info( player_id ) 18 | assert(player_id) 19 | local role_info = DB:hgetall(DBKEY_CT_PLAYERINFO..player_id) 20 | 21 | local index = 1 22 | local info = {} 23 | while index < #role_info do 24 | info[role_info[index]] = role_info[index+1] 25 | index = index + 2 26 | end 27 | 28 | return info 29 | end 30 | -------------------------------------------------------------------------------- /xjgame/service/activity/activity_doublepower.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 体力翻倍活动逻辑 4 | -- 5 | 6 | local skynet = require "skynetex" 7 | require "skynet.manager" 8 | local cluster = require "cluster" 9 | local activityd = require "activityd" 10 | 11 | local activity = { 12 | activity_id = 3, 13 | activity_name = ".activity_doublepower", 14 | } 15 | 16 | local is_start = false 17 | 18 | -- 活动开启逻辑 19 | function activity.handle_start( ) 20 | is_start = true 21 | 22 | -- 在此做活动开启逻辑 23 | -- ... 24 | 25 | end 26 | 27 | -- 活动结束逻辑 28 | function activity.handle_end( ) 29 | is_start = false 30 | 31 | -- 在此做活动结束逻辑 32 | -- ... 33 | 34 | end 35 | 36 | -- 活动即将开启预告 37 | function activity.handle_pre_begin( ti ) 38 | 39 | end 40 | 41 | -- 活动即将结束预告 42 | function activity.handle_pre_stop( ti ) 43 | 44 | end 45 | 46 | 47 | activityd.start(activity) 48 | 49 | -------------------------------------------------------------------------------- /xjgame/database/redis_character_athletic.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 竞技场数据操作模块 4 | -- 5 | 6 | local M = skynet.module("redis_character_handle") 7 | 8 | local server_id = skynet.getenv("serverid") 9 | 10 | 11 | -- 竞技场挑战成功之后交换挑战双方的排行榜位置 12 | function M.athletic_exchange_rank( src, dst ) 13 | -- 交换 src 和 dst 所在排行榜的位置 14 | local score_src = DB:ZSCORE(DBKEY_CT_ATHLETIC_RANK..server_id, src) 15 | local score_dst = DB:ZSCORE(DBKEY_CT_ATHLETIC_RANK..server_id, dst) 16 | 17 | return DB:ZADD(DBKEY_CT_ATHLETIC_RANK..server_id, score_dst, src, score_src, dst) 18 | end 19 | 20 | -- 或许排行榜数据 21 | function M.athletic_get_rankdata( min, max ) 22 | local data = DB:ZRANGE(DBKEY_CT_ATHLETIC_RANK..server_id, min-1, max-1) 23 | 24 | local ret = {} 25 | local index = 1 26 | for i=min, max do 27 | if not data[index] then 28 | return 29 | end 30 | 31 | ret[i] = data[index] 32 | index = index + 1 33 | end 34 | 35 | return ret 36 | end -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PLAT ?= none 2 | PLATS = linux freebsd macosx 3 | 4 | CC ?= gcc 5 | 6 | .PHONY : none $(PLATS) clean all cleanall 7 | 8 | #ifneq ($(PLAT), none) 9 | 10 | .PHONY : default 11 | 12 | default : 13 | $(MAKE) $(PLAT) 14 | 15 | #endif 16 | 17 | none : 18 | @echo "Please do 'make PLATFORM' where PLATFORM is one of these:" 19 | @echo " $(PLATS)" 20 | 21 | linux : PLAT = linux 22 | macosx : PLAT = macosx 23 | freebsd : PLAT = freebsd 24 | 25 | LUA_INC ?= skynet/3rd/lua 26 | LUA_CLIB_PATH = ./xjgame/luaclib 27 | 28 | CFLAGS = -g -O2 -Wall -I$(LUA_INC) $(MYCFLAGS) 29 | SHARED := -fPIC --shared 30 | macosx : SHARED := -fPIC -dynamiclib -Wl,-undefined,dynamic_lookup 31 | 32 | macosx : conf 33 | cd skynet/ && $(MAKE) CC=$(CC) $(PLAT) 34 | 35 | linux : conf 36 | cd skynet/ && $(MAKE) CC=$(CC) $(PLAT) 37 | 38 | 39 | conf : lua-conf/luaconf.c 40 | mkdir -p $(LUA_CLIB_PATH) 41 | $(CC) $(CFLAGS) $(SHARED) $^ -o $(LUA_CLIB_PATH)/conf.so 42 | 43 | 44 | clean : 45 | cd skynet/ && $(MAKE) clean 46 | rm -rf $(LUA_CLIB_PATH)/* 47 | 48 | -------------------------------------------------------------------------------- /xjgame/database/redis_namespace.lua: -------------------------------------------------------------------------------- 1 | 2 | require("database/redis_define") 3 | 4 | local skynet = require "skynetex" 5 | 6 | local redis = require "redis" 7 | local namespace_conf = require(skynet.getenv("runconfig")).database["namespace"] 8 | 9 | DB = nil 10 | 11 | require ("database/redis_script") 12 | require ("database/redis_namespace_handle") 13 | require ("database/redis_namespace_login") 14 | 15 | function db_message_handle(session, address, method, ...) 16 | assert(method) 17 | 18 | local func = assert(redis_namespace_handle[method], "reidis_character_handle param err!") 19 | skynet.ret(skynet.pack(func(...))) 20 | end 21 | 22 | skynet.start(function() 23 | DB = redis.connect(namespace_conf) 24 | 25 | skynet.dispatch("lua", db_message_handle) 26 | skynet.register ".namespace_db" 27 | 28 | local function ping_callback() 29 | skynet.send(skynet.self(),"debug","GC") 30 | DB:ping() 31 | skynet.timeout(30*100, ping_callback) 32 | end 33 | skynet.timeout(100*100, ping_callback) 34 | 35 | print("[NAMESPACE_DB] start success!") 36 | end) 37 | -------------------------------------------------------------------------------- /xjgame/player/player_scene.lua: -------------------------------------------------------------------------------- 1 | 2 | local cluster = require "cluster" 3 | local skynet = require "skynetex" 4 | local M = skynet.module("player") 5 | 6 | local harborname = skynet.getenv("harborname") 7 | 8 | 9 | -- 协议进入场景 10 | function M.scene_enter( args ) 11 | local scene_id = args.scene_id 12 | local x = tonumber(args.x) 13 | local y = tonumber(args.y) 14 | 15 | M.scene_enter_base(scene_id, x, y ) 16 | end 17 | 18 | -- 进入场景基础借口,不能暴露给前端用户直接调用 19 | function M.scene_enter_base( scene_id, x, y ) 20 | if self.scene_line_ then 21 | M.scene_exit() 22 | end 23 | -- 获取场景分线的服务地址名称 24 | self.scene_line_ = cluster.call("login", ".scene_blance_"..scene_id, "get_scene_line") 25 | -- 进入场景,返回场景内同步怪物、玩家 26 | local maker_list = cluster.call("login", self.scene_line_, "enter_scene", self.player_id_, self.job_, harborname, skynet.self(), x, y ) 27 | -- 同步到客户端 28 | send_to_client("scene_aoi_list", {obj_list=maker_list}) 29 | end 30 | 31 | -- 退出场景 32 | function M.scene_exit( ) 33 | if self.scene_line_ then 34 | cluster.call("login", self.scene_line_, "exit_scene", self.player_id_ ) 35 | self.scene_line_ = nil 36 | end 37 | end -------------------------------------------------------------------------------- /xjgame/database/redis_define.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 玩家基础信息 3 | DBKEY_CT_PLAYERINFO = "player:info:" -- player:info:[player_id] 4 | INFO_ID = "id" 5 | INFO_ACCOUNT = "account" 6 | INFO_NAME = "name" 7 | INFO_JOB = "job" 8 | INFO_VIP = "vip" 9 | INFO_GMLEVEL = "gmlevel" 10 | INFO_DIAMOND = "diamond" 11 | INFO_PAYDIAMOND = "paydiamond" 12 | INFO_BINDDIAMOND = "binddiamond" 13 | INFO_GOLD = "gold" 14 | INFO_SILVER = "silver" 15 | INFO_LEVEL = "level" 16 | INFO_POWER = "power" 17 | INFO_SCENE_ID = "scene_id" 18 | INFO_POSX = "pos_x" 19 | INFO_POSY = "pos_y" 20 | INFO_CREATETIME = "createtime" 21 | INFO_LOGINTIME = "logintime" 22 | INFO_LOGOUTTIME = "logouttime" 23 | INFO_CREATEIP = "createip" 24 | INFO_LOGINIP = "loginip" 25 | INFO_CHANNEL = "channel_id" -- 渠道来源 26 | 27 | 28 | -- 竞技场数据 29 | -- 排行榜存在 character db 里 30 | DBKEY_CT_ATHLETIC_RANK = "character:athletic:rank:" -- namespace:athletic:rank:[server_id] 31 | 32 | -- 具体玩家的玩法数据存在 character db 里 33 | DBKEY_CT_ATHLETIC = "player:athletic:" -- player:athletic:[player_id], hash set 34 | 35 | -------------------------------------------------------------------------------- /xjgame/lualib/eventlistener.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynetex" 2 | require "skynet.manager" 3 | 4 | local command = {} 5 | local event_listener = {} 6 | -- 接受全局事件的中转派发 7 | function command.publish_event( event, ... ) 8 | assert(event_listener[event]) 9 | for _, func in pairs(event_listener[event]) do 10 | func(...) 11 | end 12 | end 13 | 14 | -- 订阅事件 15 | function command.subscribe_event( event, func, is_local ) 16 | if event_listener[event] == nil then 17 | event_listener[event] = {} 18 | 19 | -- 如果是本地事件的话,不需要注册到全局事件服务里 20 | if not is_local then 21 | skynet.call( ".eventserver_node", "lua", "subscribe", event, skynet.self(), "publish_event") 22 | end 23 | end 24 | 25 | event_listener[event][func] = func 26 | return func, is_local 27 | end 28 | 29 | -- 取消订阅 30 | function command.unsubscribe_event( event, func, is_local) 31 | assert(event_listener[event] and event_listener[event][func]) 32 | event_listener[event][func] = nil 33 | 34 | local flag = true 35 | for _,_ in pairs(event_listener[event]) do 36 | flag = false 37 | break 38 | end 39 | 40 | if flag and not is_local then 41 | skynet.call( ".eventserver_node", "lua", "unsubscribe", event, skynet.self() ) 42 | end 43 | end 44 | 45 | return command -------------------------------------------------------------------------------- /xjgame/service/agentpool.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynetex" 2 | 3 | local CMD = {} 4 | 5 | local pool = {} -- the least agent 6 | local agentlist = {} -- all of agent, include dispatched 7 | 8 | local agentname, maxnum, recyremove, brokecachelen = ... 9 | 10 | local function __pool_init__( ) 11 | for i=1, maxnum do 12 | local agent = skynet.newservice(agentname, brokecachelen) 13 | table.insert(pool, agent) 14 | agentlist[agent] = agent 15 | end 16 | end 17 | 18 | function CMD.get( ) 19 | local agent = table.remove(pool) 20 | if not agent then 21 | agent = assert(skynet.newservice(agentname, brokecachelen)) 22 | agentlist[agent] = agent 23 | end 24 | 25 | return agent 26 | end 27 | 28 | function CMD.recycle( agent ) 29 | assert(agent) 30 | 31 | if recyremove == 1 and #pool > maxnum then 32 | agentlist[agent] = nil 33 | skynet.kill(agent) 34 | else 35 | table.insert(pool, 1, agent) 36 | end 37 | end 38 | 39 | skynet.start(function() 40 | 41 | skynet.dispatch("lua", function (_, address, cmd, ...) 42 | local f = assert(CMD[cmd]) 43 | skynet.ret(skynet.pack(f(...))) 44 | end) 45 | 46 | skynet.register(".agentpool") 47 | 48 | __pool_init__() 49 | end) 50 | -------------------------------------------------------------------------------- /xjgame/player/player_athletic.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 竞技场操作逻辑 4 | -- 5 | 6 | local cluster = require "cluster" 7 | local skynet = require "skynetex" 8 | local M = skynet.module("player") 9 | 10 | 11 | -- 客户端发起挑战 12 | function M.athletic_change( args ) 13 | local src = self.player_id_ 14 | local dst = args.dst_id 15 | 16 | local win = cluster.call("login", ".athleticserver", "challenge", src, dst) 17 | 18 | local is_win = false 19 | if is_win == src then 20 | is_win = true 21 | end 22 | 23 | -- 派发挑战信息 24 | cluster.call("login", ".eventserver", "publish", "athletic_changed_"..dst, src, is_win ) 25 | 26 | return { win_id = win } 27 | end 28 | 29 | -- 获取竞技场排行榜数据 30 | function M.athletic_get_rank( args ) 31 | local min = args.rankmin 32 | local max = args.rankmax 33 | assert(min>0 and max>0 and max-min==5) 34 | 35 | local data = skynet.call(self.dbserver_, "lua","athletic_get_rankdata", min, max) 36 | 37 | local ret = {} 38 | ret.rankdata = {} 39 | for i=min, max do 40 | if not data[i] then 41 | break 42 | end 43 | local tmp = {} 44 | tmp.id = data[i] 45 | tmp.name = skynet.call(self.dbserver_, "lua", "np_get_name_by_id", tmp.id) 46 | tmp.rank = i 47 | table.insert(ret, tmp) 48 | end 49 | 50 | return ret 51 | end 52 | 53 | -------------------------------------------------------------------------------- /xjgame/database/redis_character.lua: -------------------------------------------------------------------------------- 1 | 2 | require("database/redis_define") 3 | 4 | skynet = require "skynetex" 5 | DB = nil 6 | 7 | local redis = require "redis" 8 | 9 | local character_index = ... 10 | local character_conf = require(skynet.getenv("runconfig")).database["character"] 11 | 12 | require ("database/redis_script") 13 | require ("database/redis_character_handle") 14 | require ("database/redis_character_login") 15 | require ("database/redis_character_athletic") 16 | 17 | function db_message_handle(session, address, method, ...) 18 | assert(method) 19 | 20 | local func = assert(redis_character_handle[method], "reidis_character_handle param err!") 21 | skynet.ret(skynet.pack(func(...))) 22 | end 23 | 24 | skynet.start(function() 25 | DB = redis.connect(character_conf[tonumber(character_index)]) 26 | 27 | skynet.dispatch("lua", db_message_handle) 28 | skynet.register(".character_db"..character_index) 29 | 30 | local function ping_callback() 31 | skynet.send(skynet.self(),"debug","GC") 32 | DB:ping() 33 | skynet.timeout(30*100, ping_callback) 34 | end 35 | skynet.timeout(character_index*100, ping_callback) 36 | 37 | print("[CHARACTER_DB] start success! DB id: ", character_index) 38 | end) 39 | 40 | -------------------------------------------------------------------------------- /xjgame/service/eventserver_node.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local skynet = require "skynetex" 4 | local cluster = require "cluster" 5 | 6 | local harborname = skynet.getenv("harborname") 7 | 8 | local command = {} 9 | local event_list = {} 10 | 11 | function command.publish( event, ... ) 12 | if not event_list[event] then 13 | return 14 | end 15 | 16 | for address, cmd in pairs(event_list[event]) do 17 | skynet.send(address, "lua", cmd, event, ... ) 18 | end 19 | end 20 | 21 | function command.subscribe( event, address, cmd ) 22 | if not event_list[event] then 23 | event_list[event] = {} 24 | cluster.call( "login", ".eventserver", "subscribe", event, harborname, skynet.self(), "publish") 25 | end 26 | 27 | event_list[event][address] = cmd 28 | end 29 | 30 | function command.unsubscribe( event, address ) 31 | if not event_list[event] then 32 | error(string.format("[.agentevent] find not this event %s, %s, %s", event, address)) 33 | end 34 | 35 | event_list[event][address] = nil 36 | end 37 | 38 | skynet.start(function() 39 | skynet.dispatch("lua", function(session, address, cmd, ...) 40 | local f = command[cmd] 41 | if f then 42 | skynet.ret(skynet.pack(f(...))) 43 | else 44 | error(string.format("[.agentevent] can not find command %s", cmd)) 45 | end 46 | end) 47 | skynet.register ".eventserver_node" 48 | 49 | end) 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /xjgame/service/scene/sceneblance.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 场景分线逻辑, 控制分线负载分配 4 | -- 5 | 6 | local skynet = require "skynetex" 7 | local cluster = require "cluster" 8 | local conf = require "conf" 9 | 10 | -- 场景id、有多少个分线、每个分线最大容纳多少个玩家 11 | local scene_id, line_num, line_max = ... 12 | local service_name = ".scene_blance_"..scene_id 13 | line_max = tonumber(line_max) 14 | 15 | local command = {} 16 | local blance = {} 17 | 18 | function command.get_scene_line( ) 19 | local line = 1 20 | local warn = true 21 | for i=1, line_num do 22 | if blance[i] < line_max then 23 | line = i 24 | blance[line] = blance[line] + 1 25 | warn = false 26 | break 27 | end 28 | end 29 | 30 | -- 如果每条分线的在线人数都超过设定的界限了,那么继续提高界限,以防人数过多的时候无法登陆场景 31 | if warn then 32 | line_max = line_max + 1 33 | end 34 | 35 | return ".sence_"..scene_id.."_"..line 36 | end 37 | 38 | skynet.start(function() 39 | skynet.dispatch("lua", function(session, address, cmd, ...) 40 | local f = command[cmd] 41 | if f then 42 | skynet.ret(skynet.pack(f(...))) 43 | else 44 | error(string.format("Unknown command %s", tostring(cmd))) 45 | end 46 | end) 47 | skynet.register(service_name) 48 | 49 | -- 同步配置表数据 50 | local t = skynet.call(".luaconfig", "lua", "GET") 51 | resmng = conf.box(t) 52 | 53 | for i=1, line_num do 54 | blance[i] = 0 55 | skynet.newservice("scene/sceneserver", scene_id, i) 56 | end 57 | end) 58 | -------------------------------------------------------------------------------- /xjgame/player/player_aoi.lua: -------------------------------------------------------------------------------- 1 | 2 | local skynet = require "skynetex" 3 | local M = skynet.module("player") 4 | 5 | -- 6 | -- obj 格式 7 | -- 8 | -- return { 9 | -- model = self.model, 10 | -- objid = self.objid, 11 | -- id = self.id, 12 | -- x = self.x, 13 | -- y = self.y, 14 | -- oriend = self.oriend, 15 | -- attack_id = self.attack_id, 16 | -- action_id = self.action_id, 17 | -- dress_id = self.dress_id, 18 | -- dst = self.dst, 19 | -- isnpc = self.isnpc, 20 | -- ismonster = self.ismonster, 21 | -- isplayer = self.isplayer, 22 | -- } 23 | 24 | -- aoi视野范围内新增玩家, call by sceneline 25 | function M.aoi_enter( obj ) 26 | self.aoi_obj_list_[obj.objid] = obj 27 | 28 | -- 通过协议同步给前端 29 | send_to_client("scene_aoi_list", { obj_list={obj} } ) 30 | end 31 | 32 | -- aoi范围内玩家更新状态, call by sceneline 33 | function M.aoi_update( obj ) 34 | self.aoi_obj_list_[obj.objid] = obj 35 | 36 | -- 通过协议同步给前端 37 | send_to_client("scene_aoi_list", { obj_list={obj} } ) 38 | end 39 | 40 | -- aoi范围内有玩家离开视野, call by sceneline 41 | function M.aoi_exit( obj ) 42 | self.aoi_obj_list_[obj.objid] = nil 43 | 44 | -- 通过协议同步给前端 45 | send_to_client("scene_aoi_exit", { objid=obj.objid } ) 46 | end 47 | 48 | -- 玩家行走移动时调用, call by self 49 | function M.aoi_move( x,y ) 50 | if self.scene_line_ then 51 | -- 移动的时候得到一个周围新增object列表,同步到前端 52 | local update_list = cluster.call("login", self.scene_line_, "move", self.player_id_, x, y ) 53 | send_to_client("scene_aoi_list", { obj_list = update_list } ) 54 | end 55 | end 56 | 57 | -------------------------------------------------------------------------------- /xjgame/service/eventserver.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- a simple global event server, you can listen and dispathch a event 3 | -- 4 | -- provide global broadcast 5 | -- 6 | 7 | local skynet = require "skynetex" 8 | local cluster = require "cluster" 9 | 10 | local command = {} 11 | 12 | local event_list = {} 13 | 14 | function command.publish( event, ... ) 15 | if not event_list[event] then 16 | return 17 | end 18 | 19 | for node, addr_list in pairs(event_list[event]) do 20 | for address, cmd in pairs(addr_list) do 21 | --addr_list[addr_list] = nil 22 | cluster.call(node, address, cmd, event, ... ) 23 | end 24 | end 25 | 26 | end 27 | 28 | function command.subscribe( event, node, address, cmd ) 29 | if not event_list[event] then 30 | event_list[event] = {} 31 | end 32 | 33 | if not event_list[event][node] then 34 | event_list[event][node] = {} 35 | end 36 | 37 | event_list[event][node][address] = cmd 38 | end 39 | 40 | function command.unsubscribe( event, node, address ) 41 | if not event_list[event] or not event_list[event][node] then 42 | error(string.format("[.agentevent] find not this event %s, %s, %s", event, node, address)) 43 | end 44 | 45 | event_list[event][node][address] = nil 46 | end 47 | 48 | skynet.start(function() 49 | skynet.dispatch("lua", function(session, address, cmd, ...) 50 | local f = command[cmd] 51 | if f then 52 | skynet.ret(skynet.pack(f(...))) 53 | else 54 | error(string.format("[.agentevent] can not find command %s", cmd)) 55 | end 56 | end) 57 | skynet.register ".eventserver" 58 | end) 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /xjgame/lualib/activityd.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local skynet = require "skynetex" 4 | require "skynet.manager" 5 | local cluster = require "cluster" 6 | local eventlistener = require "eventlistener" 7 | 8 | local activity = {} 9 | 10 | 11 | function activity.start( handle ) 12 | local function activity_start( activity_id, ... ) 13 | if handle.activity_id == activity_id then 14 | handle.handle_start( ... ) 15 | end 16 | end 17 | 18 | local function activity_end( activity_id, ... ) 19 | if handle.activity_id == activity_id then 20 | handle.handle_end( ... ) 21 | end 22 | end 23 | 24 | local function pre_begin( activity_id, ... ) 25 | if handle.activity_id == activity_id then 26 | handle.handle_pre_begin( ... ) 27 | end 28 | end 29 | 30 | local function pre_stop( ... ) 31 | if handle.activity_id == activity_id then 32 | handle.handle_pre_stop( ... ) 33 | end 34 | end 35 | 36 | skynet.start(function() 37 | skynet.dispatch("lua", function(session, address, cmd, ...) 38 | local f = handle[cmd] 39 | if f then 40 | skynet.ret(skynet.pack(f(...))) 41 | elseif eventlistener[cmd] then 42 | eventlistener[cmd](...) 43 | else 44 | error(string.format("Unknown command %s", tostring(cmd))) 45 | end 46 | end) 47 | skynet.register(handle.activity_name) 48 | 49 | eventlistener.subscribe_event("activity_start", activity_start) 50 | eventlistener.subscribe_event("activity_end", activity_end) 51 | eventlistener.subscribe_event("activity_pre_begin", pre_begin) 52 | eventlistener.subscribe_event("activity_pre_stop", pre_stop) 53 | end) 54 | 55 | end 56 | 57 | 58 | return activity 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /xjgame/player/player_base.lua: -------------------------------------------------------------------------------- 1 | 2 | local cluster = require "cluster" 3 | local skynet = require "skynetex" 4 | local M = skynet.module("player") 5 | 6 | 7 | -- 初始化 8 | function M.__init__( ) 9 | self.server_id_ = skynet.getenv("serverid") 10 | self.role_idlist_ = {} 11 | self.player_id_ = 0 12 | self.player_name_ = "" 13 | self.job_ = 0 14 | self.vip_ = 0 15 | self.diamond_ = 0 16 | self.paydiamond_ = 0 17 | self.binddiamond_ = 0 18 | self.gold_ = 0 19 | self.silver_ = 0 20 | self.power_ = 0 21 | self.scene_id_ = 0 22 | self.pos_y_ = 0 23 | self.pos_x_ = 0 24 | self.createtime_ = 0 25 | self.createip_ = "" 26 | self.channel_id_ = 0 27 | self.last_logintime_ = 0 28 | self.last_logouttime_ = 0 29 | self.last_loginip_ = "" 30 | 31 | self.load_flag_ = false -- 是否已经加载角色数据 32 | 33 | self.aoi_obj_list_ = {} 34 | end 35 | 36 | -- 离线的时候移除侦听 37 | function M.base_unsubscribe_events( ) 38 | if self.event_athlect_changed_ then 39 | self.eventlistener_.unsubscribe_event("athletic_changed_"..self.player_id_, self.event_athlect_changed_) 40 | self.event_athlect_changed_ = nil 41 | end 42 | 43 | -- 其他事件移除在此继续添加 44 | -- ... 45 | end 46 | 47 | -- 玩家登陆之后执行 48 | function M.base_subscribe_events( ) 49 | -- 贞听竞技场上谁挑战我 50 | -- 事件源是 player_athletic 的 athletic_change 协议接口 51 | local function changed_cb( changer, is_win ) 52 | print(string.format("[event dispatch ], %s change %s is_win: %s", changer, self.player_id_, is_win) ) 53 | end 54 | self.event_athlect_changed_ = self.eventlistener_.subscribe_event("athletic_changed_"..self.player_id_, changed_cb ) 55 | 56 | -- 其他侦听事件在此继续添加 57 | -- ... 58 | end -------------------------------------------------------------------------------- /xjgame/proto/c2s.lua: -------------------------------------------------------------------------------- 1 | 2 | return [[ 3 | 4 | .package { 5 | type 0 : integer 6 | session 1 : integer 7 | } 8 | 9 | .LoginRoleInfo{ 10 | player_id 0 : integer 11 | player_name 1 : string 12 | level 2 : integer 13 | job 3 : integer 14 | } 15 | 16 | login_get_rolelist 1 { 17 | response { 18 | rolelist 0 : *LoginRoleInfo 19 | } 20 | } 21 | 22 | login_load_playerinfo 2 { 23 | request { 24 | player_id 0 : integer 25 | } 26 | response { 27 | result 0 : integer 28 | } 29 | } 30 | 31 | login_create_role 3 { 32 | request { 33 | player_name 0 : string 34 | job 1 : integer 35 | } 36 | response { 37 | result 0 : integer 38 | } 39 | } 40 | 41 | login_get_player_info 4 { 42 | response { 43 | player_name 0 : string 44 | vip 1 : integer 45 | diamond 2 : integer 46 | paydiamond 3 : integer 47 | binddiamond 4 : integer 48 | gold 5 : integer 49 | silver 6 : integer 50 | power 7 : integer 51 | scene_id 8 : integer 52 | pos_y 9 : integer 53 | pos_x 10 : integer 54 | createtime 11 : integer 55 | 56 | } 57 | } 58 | 59 | athletic_change 5 { 60 | request { 61 | dst_id 0 : integer 62 | dst_rank 1 : integer 63 | } 64 | response { 65 | win_id 0 : integer 66 | } 67 | } 68 | 69 | .AthleticRankData{ 70 | id 0 : integer 71 | name 1 : string 72 | rank 2 : integer 73 | } 74 | 75 | athletic_get_rank 6 { 76 | request { 77 | rankmin 0 : integer 78 | rankmax 1 : integer 79 | } 80 | response { 81 | rankdata 0 : *AthleticRankData 82 | } 83 | } 84 | 85 | scene_enter 7 { 86 | request { 87 | scene_id 0 : integer 88 | x 1 : integer 89 | y 2 : integer 90 | } 91 | } 92 | 93 | heartbeat 8 {} 94 | 95 | 96 | ]] -------------------------------------------------------------------------------- /xjgame/lualib/skynetex.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 扩展skynet基础方法 4 | -- 5 | 6 | local skynet = require "skynet" 7 | require "skynet.manager" 8 | -- a new timer interface for time 9 | function skynet.add_timer(ti, func) 10 | local flag = true 11 | local function cb() 12 | if not flag then 13 | return 14 | end 15 | func() 16 | end 17 | skynet.timeout(ti, cb) 18 | 19 | return function() flag = false end 20 | end 21 | 22 | function skynet.del_timer( timer ) 23 | assert(type(timer) == "function") 24 | timer() 25 | end 26 | 27 | 28 | local function __reload_script( script_str, ... ) 29 | print(string.format("[skynet __reload_script] service:%s", skynet.self())) 30 | local param = {...} 31 | local ok, data = xpcall(function() 32 | local func = loadstring(script_str) 33 | if func then 34 | func(unpack(param)) 35 | else 36 | assert(false) 37 | end 38 | end, debug.traceback) 39 | skynet.ret(skynet.pack(ok)) 40 | end 41 | 42 | -- 这个接口的服务才能使用热更接口 43 | function skynet.dispatchex(typename, func) 44 | local function funcex(session, source, cmd, ...) 45 | if typename == "lua" and cmd == "__reload_script" then 46 | __reload_script( ... ) 47 | else 48 | func(session, source, cmd, ...) 49 | end 50 | end 51 | 52 | skynet.dispatch(typename, funcex) 53 | end 54 | 55 | function skynet.require_ex(modname) 56 | if package.loaded[modname] then 57 | package.loaded[modname] = nil 58 | print(string.format("require_ex %s", modname)) 59 | end 60 | local ret, errstr = xpcall(function() require(modname) end, debug.traceback ) 61 | assert(ret, errstr) 62 | return ret 63 | end 64 | 65 | function skynet.module( name ) 66 | local M = _G[name] or {} 67 | _G[name]=M 68 | package.loaded[name]=M 69 | return M 70 | end 71 | 72 | return skynet 73 | 74 | -------------------------------------------------------------------------------- /xjgame/service/athleticserver.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- 竞技场服务模块 3 | -- 4 | -- 竞技场的排行榜放在 namespace db 上 5 | -- 6 | -- 竞技场的计算热点在于发生挑战时的战斗计算,所以需要启动多个slave去分担这些计算量 7 | -- 8 | 9 | local skynet = require "skynetex" 10 | local cluster = require "cluster" 11 | 12 | local slave = {} 13 | local balance = 1 14 | local lock = {} 15 | 16 | local command = {} 17 | 18 | -- src vs dst 19 | function command.challenge( src, dst ) 20 | if lock[src] or lock[dst] then 21 | return 22 | end 23 | lock[src] = true 24 | lock[dst] = true 25 | 26 | local s = slave[balance] 27 | balance = balance + 1 28 | if balance > #slave then 29 | balance = 1 30 | end 31 | 32 | local win = skynet.call(s, "lua", src, dst) 33 | 34 | lock[src] = nil 35 | lock[dst] = nil 36 | 37 | return win 38 | end 39 | 40 | 41 | local function launch_slave() 42 | local function challenge(src, dst) 43 | -- 在做真正的战斗计算逻辑,判断胜负,返回获胜一方 44 | -- ... 45 | 46 | -- for test 47 | local win = src 48 | 49 | if win == src then 50 | -- 决定胜负之后,如果挑战成功,则交换双方排行榜位置 51 | skynet.call(".namespace_db", "lua", "athletic_exchange_rank", src, dst) 52 | end 53 | 54 | return win 55 | end 56 | 57 | local function ret_pack(ok, err, ...) 58 | if ok then 59 | skynet.ret(skynet.pack(err, ...)) 60 | else 61 | error(err) 62 | end 63 | end 64 | 65 | skynet.dispatch("lua", function(_,_,...) 66 | ret_pack(pcall(challenge, ...)) 67 | end) 68 | end 69 | 70 | local function launch_master( ) 71 | skynet.dispatch("lua", function(session, address, cmd, ...) 72 | local f = command[cmd] 73 | if f then 74 | skynet.ret(skynet.pack(f(...))) 75 | else 76 | error(string.format("Unknown command %s", tostring(cmd))) 77 | end 78 | end) 79 | skynet.register ".athleticserver" 80 | 81 | for i=1,10 do 82 | table.insert(slave, skynet.newservice(SERVICE_NAME)) 83 | end 84 | end 85 | 86 | skynet.start(function() 87 | local athleticmaster = skynet.localname(".athleticserver") 88 | if athleticmaster then 89 | launch_slave() 90 | else 91 | launch_master() 92 | end 93 | end) 94 | -------------------------------------------------------------------------------- /xjgame/lualib/npc.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | -- 4 | -- NPC模块 5 | -- 6 | 7 | local skynet = require "skynetex" 8 | local cluster = require "cluster" 9 | 10 | local npc = {} 11 | local player_list = {} -- 视野范围内的玩家列表 12 | 13 | function npc.new(aoi, command, id, x, y) 14 | local data = {} 15 | data.aoi = aoi 16 | data.command = command 17 | data.model = "wm" 18 | data.objid = "N_"..id 19 | data.id = math.tointeger(id) 20 | data.job = 1 21 | data.x = math.tointeger(x) 22 | data.y = math.tointeger(y) 23 | data.oriend = 1 24 | data.attack_id = 0 25 | data.action_id = 0 26 | data.dress_id = 0 27 | data.dst = 10 28 | data.isnpc = true 29 | data.ismonster = false 30 | data.isplayer = false 31 | harborname = "" 32 | address = 0 33 | local mon = setmetatable( data, { __index = npc } ) 34 | mon:init() 35 | 36 | return mon 37 | end 38 | 39 | function npc:init( ) 40 | 41 | end 42 | 43 | function npc:get_obj( ) 44 | return { 45 | model = self.model, 46 | objid = self.objid, 47 | id = self.id, 48 | job = self.job, 49 | x = self.x, 50 | y = self.y, 51 | oriend = self.oriend, 52 | attack_id = self.attack_id, 53 | action_id = self.action_id, 54 | dress_id = self.dress_id, 55 | dst = self.dst, 56 | isnpc = self.isnpc, 57 | ismonster = self.ismonster, 58 | isplayer = self.isplayer, 59 | harborname = self.harborname, 60 | address = self.address, 61 | } 62 | end 63 | 64 | -- 有玩家进入视野 65 | function npc:enter_field( player ) 66 | assert(not player_list[player.objid]) 67 | player_list[player.objid] = player 68 | cluster.call(player.harborname, player.address, "call_player", "npc_player_enter", self.objid, self.id ) 69 | 70 | print("printdsssssss ********** enter_field") 71 | end 72 | 73 | -- 有玩家走出视野 74 | function npc:exit_field( player ) 75 | assert(player_list[player.objid]) 76 | player_list[player.objid] = nil 77 | cluster.call(player.harborname, player.address, "call_player", "npc_player_exit", self.objid, self.id ) 78 | print("printdsssssss ********** exit_field") 79 | end 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | return npc 89 | -------------------------------------------------------------------------------- /xjgame/service/luaconf.lua: -------------------------------------------------------------------------------- 1 | 2 | local skynet = require "skynetex" 3 | local conf = require "conf" 4 | 5 | function require_data() 6 | local require = skynet.require_ex 7 | require("data/defineItem") 8 | require("data/defineNpc") 9 | require("data/defineMonster") 10 | 11 | require("data/propItem") 12 | require("data/propMonster") 13 | require("data/propNpc") 14 | require("data/propScene") 15 | -- 继续添加其他配置表的加载 16 | -- ... 17 | 18 | -- 从_G表里过滤配置表信息,只有prop和大写字母开头才是配置表相关配置数据 19 | local tmpconf = {} 20 | for k,v in pairs(_G) do 21 | local ktype = type(k) 22 | local vtype = type(v) 23 | 24 | local fchar = string.sub(k,1,1) 25 | 26 | if fchar ~= "_" and ktype == "string" and vtype ~= "function" and ( string.sub(k,1,4) == "prop" or 27 | (fchar >="A" and fchar <="Z")) then 28 | tmpconf[k] = v 29 | end 30 | end 31 | 32 | return conf.host.new(tmpconf) 33 | end 34 | 35 | local tconf_prop = require_data() 36 | 37 | local address_list = {} 38 | local command = {} 39 | 40 | function command.GET(address) 41 | if address then 42 | table.insert(address_list, address) 43 | end 44 | return tconf_prop 45 | end 46 | 47 | -- 热更配置表接口 48 | function reload_resmng() 49 | skynet.cache.clear() 50 | tconf_prop = require_data() 51 | assert(tconf_prop) 52 | for _,address in ipairs(address_list) do 53 | if not skynet.call(address, "lua", "__reload_script", [[ 54 | local t = ... 55 | assert(t) 56 | local conf = require "conf" 57 | resmng = conf.box(t) 58 | ]], tconf_prop) then 59 | assert(false) 60 | end 61 | end 62 | end 63 | 64 | skynet.start(function() 65 | skynet.dispatch("lua", function(session, address, cmd, ...) 66 | local f = command[cmd] 67 | if f then 68 | skynet.ret(skynet.pack(f(address, ...))) 69 | else 70 | error(string.format("[luaconfig] has not find the command: %s", cmd)) 71 | end 72 | end) 73 | skynet.register ".luaconfig" 74 | end) 75 | 76 | -------------------------------------------------------------------------------- /xjgame/lualib/monster.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 怪物模块 4 | -- 5 | 6 | local skynet = require "skynetex" 7 | local cluster = require "cluster" 8 | 9 | local monster = {} 10 | local player_list = {} -- 视野范围内的玩家列表 11 | 12 | function monster.new(aoi, command, id, x, y) 13 | local data = {} 14 | data.aoi = aoi 15 | data.command = command 16 | data.model = "wm" 17 | data.objid = "M_"..id 18 | data.id = tonumber(id) 19 | data.job = 1 20 | data.x = tonumber(x) 21 | data.y = tonumber(y) 22 | data.oriend = 1 23 | data.attack_id = 0 24 | data.action_id = 0 25 | data.dress_id = 0 26 | data.dst = 10 27 | data.ismonster = true 28 | data.isnpc = false 29 | data.isplayer = false 30 | harborname = "" 31 | address = 0 32 | 33 | local mon = setmetatable( data, { __index = monster } ) 34 | mon:init() 35 | 36 | return mon 37 | end 38 | 39 | function monster:init( ) 40 | 41 | end 42 | 43 | function monster:get_obj( ) 44 | return { 45 | model = self.model, 46 | objid = self.objid, 47 | id = math.tointeger(self.id), 48 | job = math.tointeger(self.job), 49 | x = math.tointeger(self.x), 50 | y = math.tointeger(self.y), 51 | oriend = math.tointeger(self.oriend), 52 | attack_id = math.tointeger(self.attack_id), 53 | action_id = math.tointeger(self.action_id), 54 | dress_id = math.tointeger(self.dress_id), 55 | dst = math.tointeger(self.dst), 56 | isnpc = self.isnpc, 57 | ismonster = self.ismonster, 58 | isplayer = self.isplayer, 59 | harborname = self.harborname, 60 | address = self.address, 61 | } 62 | end 63 | 64 | -- 有玩家进入视野 65 | function monster:enter_field( player ) 66 | assert(not player_list[player.objid]) 67 | player_list[player.objid] = player 68 | cluster.call(player.harborname, player.address, "call_player", "monster_player_enter", self.objid, self.id ) 69 | end 70 | 71 | -- 推出视野 72 | function monster:exit_field( player ) 73 | assert(player_list[player.objid]) 74 | player_list[player.objid] = nil 75 | cluster.call(player.harborname, player.address, "call_player", "monster_player_exit", self.objid, self.id ) 76 | end 77 | 78 | 79 | 80 | 81 | 82 | 83 | return monster 84 | -------------------------------------------------------------------------------- /xjgame/login/logind.lua: -------------------------------------------------------------------------------- 1 | local login = require "loginserverd" 2 | local crypt = require "crypt" 3 | local skynet = require "skynetex" 4 | require "skynet.manager" 5 | local cluster = require "cluster" 6 | 7 | local name, host, port, instance = ... 8 | 9 | local server = { 10 | host = host, 11 | port = port, 12 | multilogin = false, -- disallow multilogin 13 | name = name, 14 | instance = tonumber(instance) or 8, 15 | } 16 | 17 | local server_list = {} 18 | local user_online = {} 19 | local user_login = {} 20 | 21 | function server.auth_handler(token) 22 | -- the token is base64(user)@base64(server):base64(password) 23 | local user, server, password = token:match("([^@]+)@([^:]+):(.+)") 24 | user = crypt.base64decode(user) 25 | server = crypt.base64decode(server) 26 | password = crypt.base64decode(password) 27 | assert(password == "password") 28 | return server, user 29 | end 30 | 31 | function server.login_handler(server, uid, secret, addr) 32 | print(string.format("%s@%s is login, secret is %s", uid, server, crypt.hexencode(secret))) 33 | local gameserver = assert(server_list[server], "Unknown server") 34 | -- only one can login, because disallow multilogin 35 | local last = user_online[uid] 36 | if last then 37 | cluster.call(last.address.harborname, last.address.addr, "kick", uid, last.subid) 38 | end 39 | if user_online[uid] then 40 | error(string.format("user %s is already online", uid)) 41 | end 42 | 43 | local subid = tostring(cluster.call(gameserver.harborname, gameserver.addr, "login", uid, secret, addr)) 44 | user_online[uid] = { address = gameserver, subid = subid , server = server} 45 | return subid 46 | end 47 | 48 | local CMD = {} 49 | 50 | function CMD.register_gate(server, address, harborname) 51 | server_list[server] = { addr = address, harborname = harborname } 52 | end 53 | 54 | function CMD.logout(uid, subid) 55 | local u = user_online[uid] 56 | if u then 57 | print(string.format("%s@%s is logout", uid, u.server)) 58 | user_online[uid] = nil 59 | end 60 | end 61 | 62 | function server.command_handler(command, source, ...) 63 | local f = assert(CMD[command]) 64 | return f(source, ...) 65 | end 66 | 67 | login(server) 68 | -------------------------------------------------------------------------------- /xjgame/game/main.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynetex" 2 | local cluster = require "cluster" 3 | local conf = require "conf" 4 | 5 | local harborname = skynet.getenv("harborname") 6 | local runconfig = require(skynet.getenv("runconfig")) 7 | local nodeconf = runconfig[harborname] 8 | 9 | skynet.start(function() 10 | -- 打开集群机制 11 | cluster.open(harborname) 12 | -- 启动控制台 13 | skynet.uniqueservice("debugconsole", nodeconf.consoleport) 14 | skynet.uniqueservice("luaconf") 15 | 16 | -- 17 | skynet.uniqueservice("xjprotoloader") 18 | 19 | if harborname == "login" then 20 | -- 启动全局消息派发 21 | skynet.uniqueservice("eventserver") 22 | skynet.uniqueservice("eventserver_node") 23 | 24 | -- 启动活动控制模块,启动于eventserver之后 25 | skynet.uniqueservice("activityserver") 26 | -- 启动各个子活动, 子活动全局唯一 27 | skynet.uniqueservice(true, "activity/activity_doublepower") 28 | skynet.uniqueservice(true, "activity/activity_moneytree") 29 | skynet.uniqueservice(true, "activity/activity_nationalday") 30 | skynet.uniqueservice(true, "activity/activity_worldboss") 31 | -- 启动竞技场服务 32 | skynet.newservice("athleticserver") 33 | 34 | -- 启动场景分线 35 | require "data/propScene" 36 | for k,v in pairs(propScene) do 37 | skynet.newservice("scene/sceneblance", k, 20, 20) 38 | end 39 | 40 | -- 启动登陆服务器 41 | skynet.newservice("logind", nodeconf.conf.name, nodeconf.conf.host, nodeconf.conf.port, nodeconf.conf.instance) 42 | else 43 | skynet.uniqueservice("eventserver_node") 44 | end 45 | 46 | -- 启动DB服务, 一个全局命名DB, 若干个角色数据服务 47 | skynet.newservice("database/redis_namespace") 48 | for index, _ in pairs(runconfig.database.character) do 49 | skynet.newservice("database/redis_character", index) 50 | end 51 | skynet.setenv("characterdb_num", #runconfig.database.character) 52 | 53 | -- 启动agent池和若干个网关gate 54 | skynet.uniqueservice("agentpool", nodeconf.agentpool.name, nodeconf.agentpool.maxnum, nodeconf.agentpool.recyremove, runconfig.brokecachelen) 55 | for _, conf in pairs(nodeconf.gate_list) do 56 | local gate = skynet.newservice("gated") 57 | skynet.call(gate, "lua", "open" , conf ) 58 | end 59 | 60 | -- test eventserver 61 | cluster.call("login", ".eventserver", "publish", "test", "hehe", true, false) 62 | 63 | end) 64 | 65 | 66 | -------------------------------------------------------------------------------- /xjgame/etc/runconfig.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | return { 4 | 5 | brokecachelen = 5, -- 玩家链接断开之后保持agent多长时间,超过则清楚agent缓存数据, 单位为秒 6 | 7 | -- 数据库服务配置 8 | database = { 9 | -- 全局命名数据库服务, 这些配置最终是作为参赛传入redis驱动里进行初始化数据库服务 10 | namespace = { 11 | host = "127.0.0.1" , 12 | port = 6379, 13 | db = 0 , 14 | --auth = "passwd", 15 | }, 16 | 17 | -- 角色数据库, 有多个 18 | character = { 19 | [1] = { 20 | host = "127.0.0.1" , 21 | port = 6379, 22 | db = 1 , 23 | --auth = "passwd", 24 | }, 25 | [2] = { 26 | host = "127.0.0.1" , 27 | port = 6379, 28 | db = 2 , 29 | --auth = "passwd", 30 | }, 31 | }, 32 | 33 | }, 34 | 35 | -- 登陆节点启动配置,此节点也作为逻辑上的主节点 36 | login = { 37 | -- 登陆服务器网关配置 38 | conf = { 39 | name = "login_master", -- 登陆服务名称, 全局通过cluster能访问到 40 | host = "0.0.0.0", -- 侦听地址 41 | port = 8001, -- 侦听端口 42 | instance = 8, -- 登陆slave验证服务个数 43 | }, 44 | 45 | -- agent池配置 46 | agentpool = { 47 | name = "agent", -- 要启动缓存的 agent 文件名 48 | maxnum = 2, -- 池的最大容量 49 | recyremove = 0, -- 如果池的最大容易都已经用完之后,后续扩展的容量在回收时是否删除,0: 不删除; 1: 删除 50 | }, 51 | 52 | -- 游戏服务器网关配置, 根据网络使用情况选配多个 53 | gate_list = { 54 | { 55 | servername = "gate_name1", -- 服务器名称,玩家在登陆验证的时候会使用这个名称做握手验证 56 | address = "0.0.0.0", -- 侦听地址 57 | port = 9001, -- 侦听端口 58 | maxclient = 2048, -- 接受最大客户端连接数 59 | }, 60 | { 61 | servername = "gate_name2", 62 | address = "0.0.0.0", 63 | port = 9002, 64 | maxclient = 2048, 65 | }, 66 | }, 67 | 68 | consoleport = 8801 , -- 当前节点控制台侦听端口 69 | }, 70 | 71 | -- node1 节点配置 72 | node1 = { 73 | 74 | -- agent池配置, 同上 75 | agentpool = { 76 | name = "agent", 77 | maxnum = 10, 78 | recyremove = 0, 79 | }, 80 | 81 | -- 服务器网关配置,同上 82 | gate_list = { 83 | { 84 | servername = "gate_name3", 85 | address = "0.0.0.0", 86 | port = 9003, 87 | maxclient = 2048, 88 | }, 89 | { 90 | servername = "gate_name4", 91 | address = "0.0.0.0", 92 | port = 9004, 93 | maxclient = 2048, 94 | }, 95 | }, 96 | 97 | consoleport = 8802 , 98 | }, 99 | 100 | } -------------------------------------------------------------------------------- /xjgame/lualib/conf.lua: -------------------------------------------------------------------------------- 1 | local core = require "conf.core" 2 | local type = type 3 | local next = next 4 | local rawget = rawget 5 | 6 | local conf = {} 7 | 8 | conf.host = { 9 | new = core.new, 10 | delete = core.delete, 11 | getref = core.getref, 12 | markdirty = core.markdirty, 13 | } 14 | 15 | local meta = {} 16 | 17 | local isdirty = core.isdirty 18 | local index = core.index 19 | local needupdate = core.needupdate 20 | local len = core.len 21 | 22 | local function findroot(self) 23 | while self.__parent do 24 | self = self.__parent 25 | end 26 | return self 27 | end 28 | 29 | local function update(root, cobj, gcobj) 30 | root.__obj = cobj 31 | root.__gcobj = gcobj 32 | -- don't use pairs 33 | for k,v in next, root do 34 | if type(v)=="table" and k~="__parent" then 35 | local pointer = index(cobj, k) 36 | if type(pointer) == "userdata" then 37 | update(v, pointer, gcobj) 38 | else 39 | root[k] = nil 40 | end 41 | end 42 | end 43 | end 44 | 45 | local function genkey(self) 46 | local key = tostring(self.__key) 47 | while self.__parent do 48 | self = self.__parent 49 | key = self.__key .. "." .. key 50 | end 51 | return key 52 | end 53 | 54 | function meta:__index(key) 55 | local obj = self.__obj 56 | if isdirty(obj) then 57 | local newobj, newtbl = needupdate(self.__gcobj) 58 | if newobj then 59 | local newgcobj = newtbl.__gcobj 60 | local root = findroot(self) 61 | update(root, newobj, newgcobj) 62 | if obj == self.__obj then 63 | error ("The key [" .. genkey(self) .. "] doesn't exist after update") 64 | end 65 | obj = self.__obj 66 | end 67 | end 68 | local v = index(obj, key) 69 | if type(v) == "userdata" then 70 | local r = setmetatable({ 71 | __obj = v, 72 | __gcobj = self.__gcobj, 73 | __parent = self, 74 | __key = key, 75 | }, meta) 76 | self[key] = r 77 | return r 78 | else 79 | return v 80 | end 81 | end 82 | 83 | function meta:__len() 84 | return len(self.__obj) 85 | end 86 | 87 | local function conf_ipairs(self, index) 88 | local obj = self.__obj 89 | index = index + 1 90 | local value = rawget(self, index) 91 | if value then 92 | return index, value 93 | end 94 | local sz = len(obj) 95 | if sz < index then 96 | return 97 | end 98 | return index, self[index] 99 | end 100 | 101 | function meta:__ipairs() 102 | return conf_ipairs, self, 0 103 | end 104 | 105 | function meta:__pairs() 106 | return conf.next, self, nil 107 | end 108 | 109 | function conf.next(obj, key) 110 | local nextkey = core.nextkey(obj.__obj, key) 111 | if nextkey then 112 | return nextkey, obj[nextkey] 113 | end 114 | end 115 | 116 | function conf.box(obj) 117 | local gcobj = core.box(obj) 118 | return setmetatable({ 119 | __parent = false, 120 | __obj = obj, 121 | __gcobj = gcobj, 122 | __key = "", 123 | } , meta) 124 | end 125 | 126 | function conf.update(self, pointer) 127 | local cobj = self.__obj 128 | assert(isdirty(cobj), "Only dirty object can be update") 129 | core.update(self.__gcobj, pointer, { __gcobj = core.box(pointer) }) 130 | end 131 | 132 | return conf 133 | 134 | -------------------------------------------------------------------------------- /xjgame/login/gated.lua: -------------------------------------------------------------------------------- 1 | local msgserver = require "msgserverd" 2 | local crypt = require "crypt" 3 | local skynet = require "skynetex" 4 | require "skynet.manager" 5 | local cluster = require "cluster" 6 | 7 | local harborname = skynet.getenv("harborname") 8 | local loginservice 9 | 10 | local server = {} 11 | local users = {} 12 | local username_map = {} 13 | local internal_id = 0 14 | 15 | -- login server disallow multi login, so login_handler never be reentry 16 | -- call by login server 17 | function server.login_handler(uid, secret, addr) 18 | if users[uid] then 19 | error(string.format("%s is already login", uid)) 20 | end 21 | 22 | internal_id = internal_id + 1 23 | local username = msgserver.username(uid, internal_id, servername) 24 | 25 | -- you can use a pool to alloc new agent 26 | local agent = skynet.call(".agentpool", "lua", "get") --skynet.newservice "msgagent" 27 | local u = { 28 | username = username, 29 | agent = agent, 30 | uid = uid, 31 | subid = internal_id, 32 | } 33 | 34 | -- trash subid (no used) 35 | skynet.call(agent, "lua", "login", uid, internal_id, secret, addr) 36 | 37 | users[uid] = u 38 | username_map[username] = u 39 | 40 | msgserver.login(username, secret) 41 | 42 | -- you should return unique subid 43 | return internal_id 44 | end 45 | 46 | -- call by agent 47 | function server.logout_handler(uid, subid) 48 | local u = users[uid] 49 | if u then 50 | local username = msgserver.username(uid, subid, servername) 51 | assert(u.username == username) 52 | msgserver.logout(u.username) 53 | users[uid] = nil 54 | username_map[u.username] = nil 55 | skynet.call(loginservice, "lua", "logout",uid, subid) 56 | 57 | -- recycle agent 58 | skynet.call(".agentpool", "lua", "recycle", u.agent) 59 | end 60 | end 61 | 62 | -- call by login server 63 | function server.kick_handler(uid, subid) 64 | local u = users[uid] 65 | if u then 66 | local username = msgserver.username(uid, subid, servername) 67 | assert(u.username == username) 68 | -- NOTICE: logout may call skynet.exit, so you should use pcall. 69 | pcall(skynet.call, u.agent, "lua", "logout") 70 | end 71 | end 72 | 73 | -- call by self (when socket disconnect) 74 | function server.disconnect_handler(username) 75 | local u = username_map[username] 76 | if u then 77 | -- NOTICE: afk may call skynet.exit or logout, so you should use pcall. 78 | pcall(skynet.call, u.agent, "lua", "afk") 79 | end 80 | end 81 | 82 | -- call by self (when recv a request from client) 83 | function server.request_handler(username, msg, sz) 84 | local u = username_map[username] 85 | return skynet.tostring(skynet.rawcall(u.agent, "client", msg, sz)) 86 | end 87 | 88 | -- call by self (when gate open) 89 | function server.register_handler(name) 90 | servername = name 91 | loginservice = cluster.proxy("login", ".login_master") 92 | skynet.call(loginservice, "lua", "register_gate", servername, skynet.self(), harborname) 93 | end 94 | 95 | -- call by self (when after auth) 96 | function server.auth_handler(username, fd, addr) 97 | local u = username_map[username] 98 | pcall(skynet.call, u.agent, "lua", "cbk", fd, addr) 99 | end 100 | 101 | msgserver.start(server) 102 | 103 | -------------------------------------------------------------------------------- /xjgame/player/player_login.lua: -------------------------------------------------------------------------------- 1 | 2 | local cluster = require "cluster" 3 | local skynet = require "skynetex" 4 | local M = skynet.module("player") 5 | 6 | -- 离线时保存相关数据, 大部分数据都是实时保持的,所以在玩家连接坏掉之后缓存的数据都是早已经保存到数据库,这个接口 7 | -- 只需要保持离线时间相关数据,以及清理定时器等工作 8 | function M.logout_save_data( ) 9 | -- 移除事件的侦听 10 | M.base_unsubscribe_events( ) 11 | 12 | -- 退出场景 13 | M.scene_exit() 14 | 15 | -- 清除数据 16 | M.__init__() 17 | end 18 | 19 | -- 登陆时加载角色列表 20 | function M.login_load_rolelist( ) 21 | -- 获取当前账号的角色列表 22 | self.role_idlist_ = skynet.call(".namespace_db", "lua", "np_get_role_list", self.account_, self.server_id_) 23 | end 24 | 25 | -- 前端请求获取角色列表 26 | function M.login_get_rolelist( args ) 27 | local ret = { rolelist = {} } 28 | for _, player_id in pairs(self.role_idlist_) do 29 | local data = {} 30 | local dbserver = get_character_dbname(player_id) 31 | -- 加载玩家基本信息 32 | local info = skynet.call(dbserver, "lua", "ct_get_role_info", player_id) 33 | 34 | data.player_id = tonumber(player_id) 35 | data.player_name = info.name 36 | data.level = tonumber(info.level) 37 | data.job = tonumber(info.job) 38 | 39 | table.insert(ret.rolelist, data) 40 | end 41 | 42 | return ret 43 | end 44 | 45 | -- 创建角色 46 | function M.login_create_role( args ) 47 | local player_name = args.player_name 48 | local job = args.job 49 | local player_id = skynet.call(".namespace_db", "lua", "np_create_role", self.account_, player_name, self.server_id_) 50 | local dbserver = get_character_dbname(player_id) 51 | local ret = skynet.call(dbserver, "lua", "ct_create_role", self.account_, player_id, player_name, job, self.loginip_, 1) 52 | 53 | if ret > 0 then 54 | M.login_load_rolelist() 55 | end 56 | 57 | return { result = ret } 58 | end 59 | 60 | -- 加载角色数据 61 | function M.login_load_playerinfo( args ) 62 | assert(self.load_flag_==false) 63 | 64 | local player_id = assert(args.player_id) 65 | if not self.role_idlist_[player_id] then 66 | return { result = -1 } 67 | end 68 | 69 | -- 默认取第一个初始化,这个在正式游戏上可以用户选择之后再初始化 70 | self.player_id_ = player_id 71 | assert(self.player_id_ > 0) 72 | self.load_flag_ = true 73 | 74 | self.dbserver_ = get_character_dbname(self.player_id_) 75 | 76 | -- 加载玩家基本信息 77 | local info = skynet.call(self.dbserver_, "lua", "ct_get_role_info", self.player_id_) 78 | self.player_name_ = info.name 79 | self.job_ = info.job 80 | self.vip_ = info.vip 81 | self.diamond_ = info.diamond or 0 82 | self.paydiamond_ = info.paydiamond or 0 83 | self.binddiamond_ = info.binddiamond or 0 84 | self.gold_ = info.gold or 0 85 | self.silver_ = info.silver or 0 86 | self.power_ = info.power or 0 87 | self.scene_id_ = info.scene_id or 1 88 | self.pos_y_ = info.pos_y or 1 89 | self.pos_x_ = info.pos_x or 1 90 | self.createtime_ = info.createtime or 0 91 | self.createip_ = info.createip or "" 92 | self.channel_id_ = info.channel_id or 1 93 | self.last_logintime_ = info.logintime or 0 94 | self.last_logouttime_ = info.logouttime or 0 95 | self.last_loginip_ = info.loginip or "" 96 | 97 | -- 加载属性 98 | -- ... 99 | 100 | -- 加载物品道具 101 | -- ... 102 | 103 | -- ... 104 | 105 | -- 注册要侦听的事件 106 | M.base_subscribe_events( ) 107 | 108 | return { result = 0 } 109 | end 110 | 111 | -- 客户端获取玩家数据接口 112 | function M.login_get_player_info( ) 113 | return { 114 | player_name = self.player_name_, 115 | vip = tonumber(self.vip_), 116 | diamond = tonumber(self.diamond_), 117 | paydiamond = tonumber(self.paydiamond_), 118 | binddiamond = tonumber(self.binddiamond_), 119 | gold = tonumber(self.gold_), 120 | silver = tonumber(self.silver_), 121 | power = tonumber(self.power_), 122 | scene_id = tonumber(self.scene_id_), 123 | pos_y = tonumber(self.pos_y_), 124 | pos_x = tonumber(self.pos_x_), 125 | createtime = tonumber(self.createtime_), 126 | } 127 | end 128 | 129 | 130 | -------------------------------------------------------------------------------- /xjgame/service/scene/sceneserver.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- 场景基础服务单元 4 | -- 5 | 6 | local skynet = require "skynetex" 7 | local cluster = require "cluster" 8 | local conf = require "conf" 9 | local aoi = require "aoidriver" 10 | local monster = require "monster" 11 | local npc = require "npc" 12 | 13 | local scene_id, line_id = ... 14 | local service_name = ".sence_"..scene_id.."_"..line_id 15 | 16 | local command = {} 17 | 18 | local propdata 19 | local obj_list = {} 20 | 21 | local online_num = 0 22 | 23 | 24 | local function enterfunc( sobj, tobj ) 25 | if tobj.isplayer then 26 | -- 让agent同步前端数据 27 | local tmpobj = sobj 28 | if not sobj.isplayer then 29 | tmpobj = sobj:get_obj() 30 | end 31 | cluster.call(tobj.harborname, tobj.address, "call_player", "aoi_enter", tmpobj ) 32 | elseif tobj.isnpc and sobj.isplayer then 33 | -- 触发NPC的AI 34 | tobj:enter_field( sobj ) 35 | elseif tobj.ismonster and sobj.isplayer then 36 | -- 触发怪物的AI 37 | tobj:enter_field( sobj ) 38 | end 39 | end 40 | 41 | local function exitfunc( sobj, tobj ) 42 | if tobj.isplayer then 43 | -- 让agent同步前端数据 44 | if not sobj.isplayer then 45 | tmpobj = sobj:get_obj() 46 | end 47 | cluster.call(tobj.harborname, tobj.address, "call_player", "aoi_exit", tmpobj ) 48 | elseif tobj.isnpc and sobj.isplayer then 49 | -- 触发NPC的AI 50 | tobj:exit_field( sobj ) 51 | elseif tobj.ismonster and sobj.isplayer then 52 | -- 触发怪物的AI 53 | tobj:exit_field( sobj ) 54 | end 55 | end 56 | 57 | local function movefunc( sobj, tobj ) 58 | if tobj.isplayer then 59 | -- 让agent同步前端数据 60 | if not sobj.isplayer then 61 | tmpobj = sobj:get_obj() 62 | end 63 | cluster.call(tobj.harborname, tobj.address, "call_player", "aoi_update", tmpobj ) 64 | elseif tobj.isnpc and sobj.isplayer then 65 | -- 触发NPC的AI 66 | 67 | elseif tobj.ismonster and sobj.isplayer then 68 | -- 触发怪物的AI 69 | 70 | end 71 | end 72 | 73 | local function attackfunc( sobj, tobj ) 74 | if tobj.isplayer and sobj.ismonster then 75 | -- 玩家被怪物攻击 76 | cluster.call(tobj.harborname, tobj.address, "call_player", "aoi_update", sobj:get_obj() ) 77 | elseif tobj.isnpc and sobj.isplayer then 78 | -- 触发NPC的AI 79 | 80 | elseif tobj.ismonster and sobj.isplayer then 81 | -- 怪物被玩家攻击 82 | 83 | end 84 | end 85 | 86 | -- 进入场景 87 | function command.enter_scene( player_id, job, harborname, addr, x, y, oriend ) 88 | local pobj = { 89 | objid = tostring(player_id), 90 | id = math.tointeger(player_id), 91 | job = math.tointeger(job), 92 | model = "wm", 93 | x = math.tointeger(x), 94 | y = math.tointeger(y), 95 | oriend = math.tointeger(oriend), 96 | dst = 30, -- 观察半径 97 | attack_id = 0, 98 | dress_id = 0, 99 | action_id = 0, 100 | isplayer = true, 101 | ismonster = false, 102 | isnpc = false, 103 | harborname = harborname, 104 | address = addr, 105 | } 106 | obj_list[pobj.objid] = pobj 107 | online_num = online_num + 1 108 | 109 | return aoi.enter(pobj, enterfunc) 110 | end 111 | 112 | -- 退出场景 113 | function command.exit_scene( player_id ) 114 | local pobj = assert(obj_list[tostring(player_id)]) 115 | obj_list[player_id] = nil 116 | aoi.remove(pobj, exitfunc) 117 | 118 | online_num = online_num - 1 119 | assert(online_num >= 0, string.format("online_num:%d", online_num)) 120 | end 121 | 122 | -- 行走 123 | function command.move( player_id, x, y ) 124 | local pobj = assert(obj_list[player_id]) 125 | pobj.x = x 126 | pobj.y = y 127 | 128 | -- 更新的时候有可能产生新增玩家进入视野, 同步到前端 129 | return aoi.update(pobj, movefunc, enterfunc, exitfunc) 130 | 131 | end 132 | 133 | -- 攻击 134 | -- attack_id: 攻击的对象, action_id: 所做的攻击动作 135 | function command.attack( obj_id, attack_id, action_id ) 136 | local pobj = assert(obj_list[obj_id]) 137 | pobj.attack_id = attack_id 138 | pobj.action_id = action_id 139 | -- 更新的时候有可能产生新增玩家进入视野, 需要同步到前端 140 | return aoi.update(pobj, attackfunc, enterfunc, exitfunc) 141 | end 142 | 143 | -- 初始化场景地图数据、怪物、npc等 144 | local function __init__( ) 145 | -- 获取场景配置表 146 | propdata = resmng.propScene[tonumber(scene_id)] 147 | aoi.init(propdata.width, propdata.height) 148 | 149 | -- 加载npc 150 | for _, v in pairs(propdata.npcs) do 151 | local n = npc.new(aoi, command, v[1],v[2],v[3]) 152 | obj_list[n.objid] = n 153 | 154 | aoi.enter(n, enterfunc) 155 | end 156 | 157 | -- 加载monster 158 | for _, v in pairs(propdata.monsters) do 159 | local m = monster.new(aoi, command, v[1], v[2], v[3]) 160 | obj_list[m.objid] = m 161 | 162 | aoi.enter(m, enterfunc) 163 | end 164 | 165 | end 166 | 167 | skynet.start(function() 168 | skynet.dispatch("lua", function(session, address, cmd, ...) 169 | local f = command[cmd] 170 | if f then 171 | skynet.ret(skynet.pack(f(...))) 172 | else 173 | error(string.format("Unknown command %s", tostring(cmd))) 174 | end 175 | end) 176 | skynet.register(service_name) 177 | 178 | -- 同步配置表数据 179 | local t = skynet.call(".luaconfig", "lua", "GET") 180 | resmng = conf.box(t) 181 | 182 | __init__() 183 | end) 184 | 185 | -------------------------------------------------------------------------------- /xjgame/game/agent.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynetex" 2 | local cluster = require "cluster" 3 | local netpack = require "netpack" 4 | local socket = require "socket" 5 | local sproto = require "sproto" 6 | local conf = require "conf" 7 | local netpack = require "netpack" 8 | local proto = require "xjproto" 9 | local sprotoloader = require "sprotoloader" 10 | local eventlistener = require "eventlistener" 11 | 12 | require "player/player_base" 13 | require "player.player_login" 14 | require "player.player_athletic" 15 | require "player.player_copy" 16 | require "player.player_scene" 17 | require "player.player_npc" 18 | require "player.player_aoi" 19 | require "player.player_monster" 20 | 21 | local brokecachelen = ... -- 连接端口之后agent保持时长,在runconfig里配置 22 | 23 | local host = nil 24 | send_request = nil 25 | 26 | 27 | skynet.register_protocol { 28 | name = "client", 29 | id = skynet.PTYPE_CLIENT, 30 | unpack = function (msg, sz) 31 | return host:dispatch(msg, sz) 32 | end, 33 | } 34 | 35 | local CMD = {} 36 | 37 | self = {} -- 玩家的大部分数据将存放在这个self table里 38 | self.client_fd_ = 0 39 | self.eventlistener_ = eventlistener 40 | 41 | -- 确定玩家数据存在哪个 character db 里 42 | local characterdb_num = skynet.getenv("characterdb_num") 43 | function get_character_dbname( player_id ) 44 | local index = player_id%characterdb_num 45 | if index == 0 then 46 | index = characterdb_num 47 | end 48 | return ".character_db"..index 49 | end 50 | 51 | -- 此接口用于发送服务端发送数据包给客户端 52 | function send_package(pack) 53 | if self.client_fd_ <= 0 then 54 | return 55 | end 56 | 57 | local session = 0 58 | pack = pack..'\1'..string.pack(">I4", session) 59 | 60 | local size = #pack 61 | local package = string.pack(">I2", size)..pack 62 | 63 | socket.write(self.client_fd_, package) 64 | end 65 | 66 | function send_to_client( name, args ) 67 | send_package( send_request(name, args) ) 68 | end 69 | 70 | local function logout() 71 | -- 离线时保持相关数据 72 | player.logout_save_data() 73 | if self.gate_ then 74 | skynet.call(self.gate_, "lua", "logout", self.userid_, self.subid_) 75 | end 76 | 77 | if self.logout_timer_ then 78 | skynet.del_timer(self.logout_timer_) 79 | self.logout_timer_ = nil 80 | end 81 | 82 | -- skynet.exit() 83 | end 84 | 85 | function CMD.login(source, uid, sid, secret, addr) 86 | skynet.error(string.format("%s is login", uid)) 87 | player.__init__() 88 | 89 | self.gate_ = source 90 | self.userid_ = uid 91 | self.account_ = uid 92 | self.subid_ = sid 93 | 94 | self.loginip_ = addr 95 | self.logintime_ = os.time() 96 | 97 | -- 在此加载数据库,加载玩家角色列表 98 | player.login_load_rolelist() 99 | 100 | -- 但设定了一定的时限,超过时限还没回来的话即刻当作离线退出处理 101 | if self.logout_timer_ then 102 | skynet.del_timer(self.logout_timer_) 103 | self.logout_timer_ = nil 104 | end 105 | self.logout_timer_ = skynet.add_timer(brokecachelen*100, logout) 106 | end 107 | 108 | function CMD.logout(source) 109 | -- NOTICE: The logout MAY be reentry 110 | skynet.error(string.format("%s is logout", self.userid_)) 111 | logout() 112 | end 113 | 114 | function CMD.afk(source) 115 | -- 连接中断了,不意味着玩家马上离线,有可能稍后会马上回来 116 | skynet.error(string.format("AFK")) 117 | self.client_fd_ = 0 118 | 119 | -- 但设定了一定的时限,超过时限还没回来的话即刻当作离线退出处理 120 | if self.logout_timer_ then 121 | skynet.del_timer(self.logout_timer_) 122 | self.logout_timer_ = nil 123 | end 124 | self.logout_timer_ = skynet.add_timer(brokecachelen*100, logout) 125 | end 126 | 127 | -- 连接修复之后的回调 128 | function CMD.cbk( source, fd, addr ) 129 | -- the connect is come back 130 | skynet.error(string.format("CBK")) 131 | self.client_fd_ = fd 132 | 133 | if self.logout_timer_ then 134 | skynet.del_timer(self.logout_timer_) 135 | self.logout_timer_ = nil 136 | end 137 | end 138 | 139 | function CMD.call_player( source, cmd, ...) 140 | local f = assert(player[cmd]) 141 | return f(...) 142 | end 143 | 144 | function request(type, name, args, response) 145 | local f = assert(player[name]) 146 | local r = f(args) 147 | 148 | if response then 149 | return response(r) 150 | end 151 | end 152 | 153 | skynet.start(function() 154 | -- 创建协议数据 155 | -- host = sproto.new(proto.c2s):host "package" 156 | -- send_request = host:attach(sproto.new(proto.s2c)) 157 | host = sprotoloader.load(1):host "package" 158 | send_request = host:attach(sprotoloader.load(2)) 159 | 160 | -- 初始化玩家代理数据 161 | player.__init__() 162 | 163 | skynet.dispatch("lua", function(session, source, command, ...) 164 | local f = CMD[command] 165 | if f then 166 | skynet.ret(skynet.pack(f(source, ...))) 167 | elseif eventlistener[command] then 168 | eventlistener[command](...) 169 | else 170 | error(string.format("[agent] has not find the command: %s", command)) 171 | end 172 | end) 173 | 174 | -- c2s rpc 175 | skynet.dispatch("client", function(_,_, ...) 176 | local result = request(...) --pcall(request, ...) 177 | skynet.ret(result) 178 | end) 179 | 180 | -- 同步配置表数据 181 | local t = skynet.call(".luaconfig", "lua", "GET") 182 | resmng = conf.box(t) 183 | print("[agent] test conf:", resmng.ITEM_WEAPON_1, resmng.propItem[resmng.ITEM_WEAPON_2].Name) 184 | 185 | -- test 心跳包 186 | skynet.fork(function() 187 | while true do 188 | send_package(send_request "heartbeat") 189 | skynet.sleep(300) 190 | end 191 | end) 192 | 193 | -- 测试事件订阅 194 | local function eventtest(...) 195 | print("************** test subscribe event success, ", skynet.self(), ...) 196 | end 197 | eventlistener.subscribe_event("test", eventtest) 198 | end) 199 | -------------------------------------------------------------------------------- /xjgame/service/debugconsole.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | require "skynet.manager" 3 | local codecache = require "skynet.codecache" 4 | local core = require "skynet.core" 5 | local socket = require "socket" 6 | local snax = require "snax" 7 | 8 | local port = tonumber(...) 9 | local COMMAND = {} 10 | 11 | local function format_table(t) 12 | local index = {} 13 | for k in pairs(t) do 14 | table.insert(index, k) 15 | end 16 | table.sort(index) 17 | local result = {} 18 | for _,v in ipairs(index) do 19 | table.insert(result, string.format("%s:%s",v,tostring(t[v]))) 20 | end 21 | return table.concat(result,"\t") 22 | end 23 | 24 | local function dump_line(print, key, value) 25 | if type(value) == "table" then 26 | print(key, format_table(value)) 27 | else 28 | print(key,tostring(value)) 29 | end 30 | end 31 | 32 | local function dump_list(print, list) 33 | local index = {} 34 | for k in pairs(list) do 35 | table.insert(index, k) 36 | end 37 | table.sort(index) 38 | for _,v in ipairs(index) do 39 | dump_line(print, v, list[v]) 40 | end 41 | print("OK") 42 | end 43 | 44 | local function split_cmdline(cmdline) 45 | local split = {} 46 | for i in string.gmatch(cmdline, "%S+") do 47 | table.insert(split,i) 48 | end 49 | return split 50 | end 51 | 52 | local function docmd(cmdline, print) 53 | local split = split_cmdline(cmdline) 54 | local cmd = COMMAND[split[1]] 55 | local ok, list 56 | if cmd then 57 | ok, list = pcall(cmd, select(2,table.unpack(split))) 58 | else 59 | ok, list = pcall(skynet.call,".launcher","lua", table.unpack(split)) 60 | end 61 | 62 | if ok then 63 | if list then 64 | if type(list) == "string" then 65 | print(list) 66 | else 67 | dump_list(print, list) 68 | end 69 | else 70 | print("OK") 71 | end 72 | else 73 | print("Error:", list) 74 | end 75 | end 76 | 77 | local function console_main_loop(stdin, print) 78 | socket.lock(stdin) 79 | print("Welcome to skynet console") 80 | while true do 81 | local cmdline = socket.readline(stdin, "\n") 82 | if not cmdline then 83 | break 84 | end 85 | if cmdline ~= "" then 86 | docmd(cmdline, print) 87 | end 88 | end 89 | socket.unlock(stdin) 90 | end 91 | 92 | skynet.start(function() 93 | local listen_socket = socket.listen ("127.0.0.1", port) 94 | skynet.error("Start debug console at 127.0.0.1 " .. port) 95 | socket.start(listen_socket , function(id, addr) 96 | local function print(...) 97 | local t = { ... } 98 | for k,v in ipairs(t) do 99 | t[k] = tostring(v) 100 | end 101 | socket.write(id, table.concat(t,"\t")) 102 | socket.write(id, "\n") 103 | end 104 | socket.start(id) 105 | skynet.fork(console_main_loop, id , print) 106 | end) 107 | end) 108 | 109 | function COMMAND.help() 110 | return { 111 | help = "This help message", 112 | list = "List all the service", 113 | stat = "Dump all stats", 114 | info = "Info address : get service infomation", 115 | exit = "exit address : kill a lua service", 116 | kill = "kill address : kill service", 117 | mem = "mem : show memory status", 118 | gc = "gc : force every lua service do garbage collect", 119 | start = "lanuch a new lua service", 120 | snax = "lanuch a new snax service", 121 | clearcache = "clear lua code cache", 122 | service = "List unique service", 123 | task = "task address : show service task detail", 124 | inject = "inject address luascript.lua", 125 | logon = "logon address", 126 | logoff = "logoff address", 127 | log = "launch a new lua service with log", 128 | } 129 | end 130 | 131 | function COMMAND.clearcache() 132 | codecache.clear() 133 | end 134 | 135 | function COMMAND.start(...) 136 | local ok, addr = pcall(skynet.newservice, ...) 137 | if ok then 138 | return { [skynet.address(addr)] = ... } 139 | else 140 | return "Failed" 141 | end 142 | end 143 | 144 | function COMMAND.log(...) 145 | local ok, addr = pcall(skynet.call, ".launcher", "lua", "LOGLAUNCH", "snlua", ...) 146 | if ok then 147 | return { [skynet.address(addr)] = ... } 148 | else 149 | return "Failed" 150 | end 151 | end 152 | 153 | function COMMAND.snax(...) 154 | local ok, s = pcall(snax.newservice, ...) 155 | if ok then 156 | local addr = s.handle 157 | return { [skynet.address(addr)] = ... } 158 | else 159 | return "Failed" 160 | end 161 | end 162 | 163 | function COMMAND.service() 164 | return skynet.call("SERVICE", "lua", "LIST") 165 | end 166 | 167 | local function adjust_address(address) 168 | if address:sub(1,1) ~= ":" then 169 | address = bit32.replace( tonumber("0x" .. address), skynet.harbor(skynet.self()), 24, 8) 170 | end 171 | return address 172 | end 173 | 174 | function COMMAND.exit(address) 175 | skynet.send(adjust_address(address), "debug", "EXIT") 176 | end 177 | 178 | function COMMAND.inject(address, filename) 179 | address = adjust_address(address) 180 | local f = io.open(filename, "rb") 181 | if not f then 182 | return "Can't open " .. filename 183 | end 184 | local source = f:read "*a" 185 | f:close() 186 | return skynet.call(address, "debug", "RUN", source, filename) 187 | end 188 | 189 | function COMMAND.task(address) 190 | address = adjust_address(address) 191 | return skynet.call(address,"debug","TASK") 192 | end 193 | 194 | function COMMAND.info(address) 195 | address = adjust_address(address) 196 | return skynet.call(address,"debug","INFO") 197 | end 198 | 199 | function COMMAND.logon(address) 200 | address = adjust_address(address) 201 | core.command("LOGON", skynet.address(address)) 202 | end 203 | 204 | function COMMAND.logoff(address) 205 | address = adjust_address(address) 206 | core.command("LOGOFF", skynet.address(address)) 207 | end 208 | -------------------------------------------------------------------------------- /xjgame/lualib/aoidriver.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- aoi模块,采用十字链算法 4 | -- 5 | 6 | local assert = assert 7 | 8 | 9 | local aoi = {} 10 | 11 | local w = 200 12 | local h = 100 13 | 14 | local obj_list = {} 15 | local obj_list_x = {} 16 | local obj_list_y = {} 17 | 18 | local dst_max = 50 -- 最大观察半径 19 | 20 | --[[ object 格式 21 | 22 | obj = { 23 | objid = object, 24 | id = playerid, 25 | x = x, 26 | y = y, 27 | oriend = oriend, 28 | attack_id = 0, 29 | action_id = 0, 30 | dress_id = 0, 31 | model = "w", -- "w"、"m"、“wm"、"mw" 32 | dst = 10, -- 观察半径 33 | isplayer = false, 34 | ismonster = false, 35 | isnpc = false, 36 | } 37 | 38 | --]] 39 | 40 | local function is_watcher(obj) 41 | if obj.model == "w" or obj.model == "wm" or obj.model == "mw" then 42 | return true 43 | else 44 | return false 45 | end 46 | end 47 | 48 | local function is_maker(obj) 49 | if obj.model == "m" or obj.model == "wm" or obj.model == "mw" then 50 | return true 51 | else 52 | return false 53 | end 54 | end 55 | 56 | local function dispatch_message( obj, func ) 57 | assert(func) 58 | if is_maker(obj) then 59 | -- 按照设置的最大视野范围遍历x轴 60 | local tmps = obj.x - dst_max 61 | if tmps <= 0 then 62 | tmps = 1 63 | end 64 | 65 | local tmpe = obj.x + dst_max 66 | if tmpe > w then 67 | tmpe = w 68 | end 69 | 70 | for i=tmps, tmpe do 71 | if i <= 0 then 72 | break 73 | end 74 | 75 | if obj_list_x[i] then 76 | for k,v in pairs(obj_list_x[i]) do 77 | -- 是观察者,并且在他的视野范围内 78 | if is_watcher(v) and math.abs(v.x - obj.x) <= v.dst and math.abs(v.y - obj.y) <= v.dst and v ~= obj then 79 | -- 执行更新回调 80 | func(obj, v) 81 | end 82 | end 83 | end 84 | end 85 | 86 | end 87 | end 88 | 89 | -- 获取obj附近的watcher 90 | local function get_watcher( obj ) 91 | local list = {} 92 | -- 按照设置的最大视野范围遍历x轴 93 | local tmps = obj.x - dst_max 94 | if tmps <= 0 then 95 | tmps = 1 96 | end 97 | 98 | local tmpe = obj.x + dst_max 99 | if tmpe > w then 100 | tmpe = w 101 | end 102 | 103 | for i=tmps, tmpe do 104 | if i <= 0 then 105 | break 106 | end 107 | 108 | if obj_list_x[i] then 109 | for k,v in pairs(obj_list_x[i]) do 110 | -- 是观察者,并且在他的视野范围内 111 | if is_watcher(v) and math.abs(v.y - obj.y) <= v.dst and math.abs(v.x - obj.x) <= v.dst and v ~= obj then 112 | -- 执行更新回调 113 | list[v.id] = v 114 | end 115 | end 116 | end 117 | end 118 | 119 | return list 120 | end 121 | 122 | -- 获取obj附近的maker 123 | local function get_maker( obj, isproto ) 124 | local list = {} 125 | -- 按照设置的最大视野范围遍历x轴 126 | local tmps = obj.x - dst_max 127 | if tmps <= 0 then 128 | tmps = 1 129 | end 130 | 131 | local tmpe = obj.x + dst_max 132 | if tmpe > w then 133 | tmpe = w 134 | end 135 | 136 | for i=tmps, tmpe do 137 | if i <= 0 then 138 | break 139 | end 140 | 141 | if obj_list_x[i] then 142 | for k,v in pairs(obj_list_x[i]) do 143 | -- 是观察者,并且在他的视野范围内 144 | if is_maker(v) and math.abs(v.y - obj.y) <= obj.dst and math.abs(v.x - obj.x) <= obj.dst and v ~= obj then 145 | -- 执行更新回调 146 | local ret 147 | if v.isplayer then 148 | ret = v 149 | else 150 | ret = v:get_obj() 151 | end 152 | 153 | if isproto then 154 | table.insert(list,ret) 155 | else 156 | list[k] = ret 157 | end 158 | end 159 | end 160 | end 161 | end 162 | 163 | return list 164 | end 165 | 166 | function aoi.enter( obj, func ) 167 | obj_list[obj.objid] = obj 168 | 169 | if not obj_list_x[obj.x] then 170 | obj_list_x[obj.x] = {} 171 | end 172 | 173 | if not obj_list_y[obj.y] then 174 | obj_list_y[obj.y] = {} 175 | end 176 | 177 | obj_list_x[obj.x][obj.objid] = obj 178 | obj_list_y[obj.y][obj.objid] = obj 179 | 180 | -- 如果是maker模式,广播给所有视野范围的带w模式的obj 181 | dispatch_message(obj, func) 182 | 183 | -- 如果是watcher模式,在进入之后返回视野上所有带m模式的obj 184 | if is_watcher(obj) then 185 | return get_maker(obj, true) 186 | end 187 | end 188 | 189 | function aoi.remove( obj, func ) 190 | obj_list[obj.objid] = nil 191 | obj_list_x[obj.x][obj.objid] = nil 192 | obj_list_y[obj.y][obj.objid] = nil 193 | 194 | -- 如果是m活着mw模式,需要广播给其他所有视野范围的w 195 | dispatch_message(obj, func) 196 | end 197 | 198 | -- 需要计算更新之前和更新之后的交集作为广播集合 199 | function aoi.update( obj, updatefunc, enterfunc, exitfunc ) 200 | local bef_w_list = get_watcher(obj_list[obj.objid]) 201 | local bef_m_list = get_maker(obj_list[obj.objid]) 202 | 203 | obj_list[obj.objid] = obj 204 | obj_list_x[obj.x][obj.objid] = obj 205 | obj_list_y[obj.y][obj.objid] = obj 206 | 207 | local aft_w_list = get_watcher(obj) 208 | local aft_m_list = get_maker(obj) 209 | 210 | if is_maker(obj) then 211 | for k,v in pairs(aft_w_list) do 212 | if bef_w_list[k] then 213 | updatefunc(obj, v) -- 更新交集 214 | else 215 | enterfunc(obj, v) -- 新增进入视野 216 | end 217 | end 218 | 219 | -- 走出对方的视野了也通知对方做退出视野的相关操作 220 | for k,v in pairs(bef_w_list) do 221 | if not aft_w_list[k] then 222 | exitfunc(obj, v) 223 | end 224 | end 225 | end 226 | 227 | local list = {} 228 | -- 如果是watcher 229 | if is_watcher(obj) then 230 | -- 找出新增的部分,需要更新到前端 231 | for k,v in pairs(aft_m_list) do 232 | if not bef_m_list[k] then 233 | if v.isplayer then 234 | -- list[k] = v 235 | table.insert(list, v) 236 | else 237 | -- list[k] = v:get_obj() 238 | table.insert(list, v:get_obj()) 239 | end 240 | end 241 | end 242 | end 243 | 244 | return list 245 | end 246 | 247 | function aoi.init(width, height, maxdst) 248 | w = width or w 249 | h = height or h 250 | dst_max = maxdst or dst_max 251 | end 252 | 253 | return aoi 254 | 255 | -------------------------------------------------------------------------------- /xjgame/lualib/loginserverd.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | require "skynet.manager" 3 | local socket = require "socket" 4 | local crypt = require "crypt" 5 | local table = table 6 | local string = string 7 | local assert = assert 8 | 9 | --[[ 10 | 11 | Protocol: 12 | 13 | line (\n) based text protocol 14 | 15 | 1. Server->Client : base64(8bytes random challenge) 16 | 2. Client->Server : base64(8bytes handshake client key) 17 | 3. Server: Gen a 8bytes handshake server key 18 | 4. Server->Client : base64(DH-Exchange(server key)) 19 | 5. Server/Client secret := DH-Secret(client key/server key) 20 | 6. Client->Server : base64(HMAC(challenge, secret)) 21 | 7. Client->Server : DES(secret, base64(token)) 22 | 8. Server : call auth_handler(token) -> server, uid (A user defined method) 23 | 9. Server : call login_handler(server, uid, secret) ->subid (A user defined method) 24 | 10. Server->Client : 200 base64(subid) 25 | 26 | Error Code: 27 | 400 Bad Request . challenge failed 28 | 401 Unauthorized . unauthorized by auth_handler 29 | 403 Forbidden . login_handler failed 30 | 406 Not Acceptable . already in login (disallow multi login) 31 | 32 | Success: 33 | 200 base64(subid) 34 | ]] 35 | 36 | local socket_error = {} 37 | local function assert_socket(v, fd) 38 | if v then 39 | return v 40 | else 41 | skynet.error(string.format("auth failed: socket (fd = %d) closed", fd)) 42 | error(socket_error) 43 | end 44 | end 45 | 46 | local function write(fd, text) 47 | assert_socket(socket.write(fd, text), fd) 48 | end 49 | 50 | local function launch_slave(auth_handler) 51 | local function auth(fd, addr) 52 | fd = assert(tonumber(fd)) 53 | skynet.error(string.format("connect from %s (fd = %d)", addr, fd)) 54 | socket.start(fd) 55 | 56 | -- set socket buffer limit (8K) 57 | -- If the attacker send large package, close the socket 58 | socket.limit(fd, 8192) 59 | 60 | local challenge = crypt.randomkey() 61 | write(fd, crypt.base64encode(challenge).."\n") 62 | 63 | local handshake = assert_socket(socket.readline(fd), fd) 64 | local clientkey = crypt.base64decode(handshake) 65 | if #clientkey ~= 8 then 66 | error "Invalid client key" 67 | end 68 | local serverkey = crypt.randomkey() 69 | write(fd, crypt.base64encode(crypt.dhexchange(serverkey)).."\n") 70 | 71 | local secret = crypt.dhsecret(clientkey, serverkey) 72 | 73 | local response = assert_socket(socket.readline(fd), fd) 74 | local hmac = crypt.hmac64(challenge, secret) 75 | 76 | if hmac ~= crypt.base64decode(response) then 77 | write(fd, "400 Bad Request\n") 78 | error "challenge failed" 79 | end 80 | 81 | local etoken = assert_socket(socket.readline(fd),fd) 82 | 83 | local token = crypt.desdecode(secret, crypt.base64decode(etoken)) 84 | 85 | local ok, server, uid = pcall(auth_handler,token) 86 | 87 | socket.abandon(fd) 88 | return ok, server, uid, secret 89 | end 90 | 91 | local function ret_pack(ok, err, ...) 92 | if ok then 93 | skynet.ret(skynet.pack(err, ...)) 94 | else 95 | error(err) 96 | end 97 | end 98 | 99 | skynet.dispatch("lua", function(_,_,...) 100 | ret_pack(pcall(auth, ...)) 101 | end) 102 | end 103 | 104 | local user_login = {} 105 | 106 | local function accept(conf, s, fd, addr) 107 | -- call slave auth 108 | local ok, server, uid, secret = skynet.call(s, "lua", fd, addr) 109 | socket.start(fd) 110 | 111 | if not ok then 112 | write(fd, "401 Unauthorized\n") 113 | error(server) 114 | end 115 | 116 | if not conf.multilogin then 117 | if user_login[uid] then 118 | write(fd, "406 Not Acceptable\n") 119 | error(string.format("User %s is already login", uid)) 120 | end 121 | 122 | user_login[uid] = true 123 | end 124 | 125 | local ok, err = pcall(conf.login_handler, server, uid, secret, addr) 126 | -- unlock login 127 | user_login[uid] = nil 128 | 129 | if ok then 130 | err = err or "" 131 | write(fd, "200 "..crypt.base64encode(err).."\n") 132 | else 133 | write(fd, "403 Forbidden\n") 134 | error(err) 135 | end 136 | end 137 | 138 | local function launch_master(conf) 139 | local instance = conf.instance or 8 140 | assert(instance > 0) 141 | local host = conf.host or "0.0.0.0" 142 | local port = assert(tonumber(conf.port)) 143 | local slave = {} 144 | local balance = 1 145 | 146 | skynet.dispatch("lua", function(_,source,command, ...) 147 | skynet.ret(skynet.pack(conf.command_handler(command, ...))) 148 | end) 149 | 150 | for i=1,instance do 151 | table.insert(slave, skynet.newservice(SERVICE_NAME, conf.name, conf.host, conf.port, conf.instance)) 152 | end 153 | 154 | skynet.error(string.format("login server listen at : %s %d", host, port)) 155 | local id = socket.listen(host, port) 156 | socket.start(id , function(fd, addr) 157 | local s = slave[balance] 158 | balance = balance + 1 159 | if balance > #slave then 160 | balance = 1 161 | end 162 | local ok, err = pcall(accept, conf, s, fd, addr) 163 | if not ok then 164 | if err ~= socket_error then 165 | skynet.error(string.format("invalid client (fd = %d) error = %s", fd, err)) 166 | end 167 | socket.start(fd) 168 | end 169 | socket.close(fd) 170 | end) 171 | end 172 | 173 | local function login(conf) 174 | local name = "." .. (conf.name or "login") 175 | skynet.start(function() 176 | local loginmaster = skynet.localname(name) 177 | if loginmaster then 178 | local auth_handler = assert(conf.auth_handler) 179 | launch_master = nil 180 | conf = nil 181 | launch_slave(auth_handler) 182 | else 183 | launch_slave = nil 184 | conf.auth_handler = nil 185 | assert(conf.login_handler) 186 | assert(conf.command_handler) 187 | skynet.register(name) 188 | launch_master(conf) 189 | end 190 | end) 191 | end 192 | 193 | return login 194 | -------------------------------------------------------------------------------- /xjgame/database/redis_script.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | REDIS_SCRIPT = {} 4 | 5 | -- 6 | -- create new role for namespace db 7 | -- 8 | -- KEYS[1]: server_id 9 | -- KEYS[2]: account 10 | -- KEYS[3]: name 11 | -- 12 | -- namespace:max_id 13 | -- namespace:account:[serverid]:[account] id id -- 当前账号包含的所有角色 14 | -- 15 | -- namespace:ids:[serverid] id id -- 当前服包含的所有账号id 16 | -- 17 | -- namespace:player:id:[id] name 18 | -- namespace:player:name:[name] id 19 | -- 20 | -- 返回 1 :创建成功 21 | -- 返回 -1 :创建失败,名字有重名 22 | -- 23 | REDIS_SCRIPT.NAMESPACE_CREATE_ROLE = 24 | [[ 25 | 26 | if redis.call('exists', 'namespace:max_id') <= 0 then 27 | redis.call('set','namespace:max_id',0) 28 | end 29 | 30 | local max_id = redis.call('get', 'namespace:max_id') + 1 31 | if redis.call('exists','namespace:player:id:'..max_id) <= 0 and 32 | redis.call('exists','namespace:player:name:'..KEYS[3]) <=0 33 | then 34 | redis.call('set', 'namespace:player:id:'..max_id,KEYS[3]) 35 | redis.call('set', 'namespace:player:name:'..KEYS[3],max_id) 36 | 37 | redis.call('hset', 'namespace:account:'..KEYS[1]..':'..KEYS[2], max_id, max_id ) 38 | redis.call('hset', 'namespace:ids:'..KEYS[1], max_id, max_id ) 39 | redis.call('incr', 'namespace:max_id') 40 | 41 | return max_id 42 | else 43 | return -1 44 | end 45 | 46 | ]] 47 | 48 | -- 49 | -- create new role for character db 50 | -- 51 | -- KEYS[1]: id 52 | -- KEYS[2]: name 53 | -- KEYS[3]: account 54 | -- KEYS[4]: createtime 55 | -- KEYS[5]: createip 56 | -- KEYS[6]: channelid -- 渠道来源 57 | -- KEYS[7]: job 58 | -- 59 | -- HASHS player:info:[id] 60 | -- 61 | -- player:info:[id] id id 62 | -- player:info:[id] account account 63 | -- player:info:[id] name name 64 | -- player:info:[id] vip vip 65 | -- player:info:[id] level level 66 | -- player:info:[id] scene_id scene_id 67 | -- player:info:[id] createtime createtime 68 | -- player:info:[id] logintime logintime 69 | -- player:info:[id] logouttime logouttime 70 | -- player:info:[id] createip createip 71 | -- player:info:[id] channelid channelid 72 | -- player:info:[id] loginip loginip 73 | -- player:info:[id] job job 74 | -- 75 | -- 返回 player_id( > 0 ):创建成功 76 | -- 返回 -1:创建失败,角色已经存在 77 | -- 78 | REDIS_SCRIPT.CHARACTER_CREATE_ROLE = 79 | [[ 80 | 81 | if redis.call('exists', 'player:info:'..KEYS[1]) == 1 then 82 | return -1 83 | else 84 | redis.call('hset', 'player:info:'..KEYS[1], 'id', KEYS[1]) 85 | redis.call('hset', 'player:info:'..KEYS[1], 'name', KEYS[2]) 86 | redis.call('hset', 'player:info:'..KEYS[1], 'account', KEYS[3]) 87 | redis.call('hset', 'player:info:'..KEYS[1], 'level', 1) 88 | redis.call('hset', 'player:info:'..KEYS[1], 'vip', 0) 89 | redis.call('hset', 'player:info:'..KEYS[1], 'gmlevel', 0) 90 | redis.call('hset', 'player:info:'..KEYS[1], 'scene_id', 1) 91 | redis.call('hset', 'player:info:'..KEYS[1], 'createtime', KEYS[4]) 92 | redis.call('hset', 'player:info:'..KEYS[1], 'logintime', KEYS[4]) 93 | redis.call('hset', 'player:info:'..KEYS[1], 'logouttime', 0) 94 | redis.call('hset', 'player:info:'..KEYS[1], 'createip', KEYS[5]) 95 | redis.call('hset', 'player:info:'..KEYS[1], 'loginip', KEYS[5]) 96 | redis.call('hset', 'player:info:'..KEYS[1], 'channelid', KEYS[6]) 97 | redis.call('hset', 'player:info:'..KEYS[1], 'job', KEYS[7]) 98 | 99 | return KEYS[1] 100 | end 101 | 102 | ]] 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /xjgame/lualib/msgserverd.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local gateserver = require "snax.gateserver" 3 | local netpack = require "netpack" 4 | local crypt = require "crypt" 5 | local socketdriver = require "socketdriver" 6 | local assert = assert 7 | local b64encode = crypt.base64encode 8 | local b64decode = crypt.base64decode 9 | 10 | --[[ 11 | 12 | Protocol: 13 | 14 | All the number type is big-endian 15 | 16 | Shakehands (The first package) 17 | 18 | Client -> Server : 19 | 20 | base64(uid)@base64(server)#base64(subid):index:base64(hmac) 21 | 22 | Server -> Client 23 | 24 | XXX ErrorCode 25 | 404 User Not Found 26 | 403 Index Expired 27 | 401 Unauthorized 28 | 400 Bad Request 29 | 200 OK 30 | 31 | Req-Resp 32 | 33 | Client -> Server : Request 34 | word size (Not include self) 35 | string content (size-4) 36 | dword session 37 | 38 | Server -> Client : Response 39 | word size (Not include self) 40 | string content (size-5) 41 | byte ok (1 is ok, 0 is error) 42 | dword session 43 | 44 | API: 45 | server.userid(username) 46 | return uid, subid, server 47 | 48 | server.username(uid, subid, server) 49 | return username 50 | 51 | server.login(username, secret) 52 | update user secret 53 | 54 | server.logout(username) 55 | user logout 56 | 57 | server.ip(username) 58 | return ip when connection establish, or nil 59 | 60 | server.start(conf) 61 | start server 62 | 63 | Supported skynet command: 64 | kick username (may used by loginserver) 65 | login username secret (used by loginserver) 66 | logout username (used by agent) 67 | 68 | Config for server.start: 69 | conf.expired_number : the number of the response message cached after sending out (default is 128) 70 | conf.login_handler(uid, secret) -> subid : the function when a new user login, alloc a subid for it. (may call by login server) 71 | conf.logout_handler(uid, subid) : the functon when a user logout. (may call by agent) 72 | conf.kick_handler(uid, subid) : the functon when a user logout. (may call by login server) 73 | conf.request_handler(username, session, msg) : the function when recv a new request. 74 | conf.register_handler(servername) : call when gate open 75 | conf.disconnect_handler(username) : call when a connection disconnect (afk) 76 | ]] 77 | 78 | local server = {} 79 | 80 | skynet.register_protocol { 81 | name = "client", 82 | id = skynet.PTYPE_CLIENT, 83 | } 84 | 85 | local user_online = {} 86 | local handshake = {} 87 | local connection = {} 88 | 89 | function server.userid(username) 90 | -- base64(uid)@base64(server)#base64(subid) 91 | local uid, servername, subid = username:match "([^@]*)@([^#]*)#(.*)" 92 | return b64decode(uid), b64decode(subid), b64decode(servername) 93 | end 94 | 95 | function server.username(uid, subid, servername) 96 | return string.format("%s@%s#%s", b64encode(uid), b64encode(servername), b64encode(tostring(subid))) 97 | end 98 | 99 | function server.logout(username) 100 | local u = user_online[username] 101 | user_online[username] = nil 102 | if u.fd then 103 | gateserver.closeclient(u.fd) 104 | connection[u.fd] = nil 105 | end 106 | end 107 | 108 | function server.login(username, secret) 109 | assert(user_online[username] == nil) 110 | user_online[username] = { 111 | secret = secret, 112 | version = 0, 113 | index = 0, 114 | username = username, 115 | response = {}, -- response cache 116 | } 117 | end 118 | 119 | function server.ip(username) 120 | local u = user_online[username] 121 | if u and u.fd then 122 | return u.ip 123 | end 124 | end 125 | 126 | function server.start(conf) 127 | local expired_number = conf.expired_number or 128 128 | 129 | local handler = {} 130 | 131 | local CMD = { 132 | login = assert(conf.login_handler), 133 | logout = assert(conf.logout_handler), 134 | kick = assert(conf.kick_handler), 135 | } 136 | 137 | function handler.command(cmd, source, ...) 138 | local f = assert(CMD[cmd]) 139 | return f(...) 140 | end 141 | 142 | function handler.open(source, gateconf) 143 | local servername = assert(gateconf.servername) 144 | return conf.register_handler(servername) 145 | end 146 | 147 | function handler.connect(fd, addr) 148 | handshake[fd] = addr 149 | gateserver.openclient(fd) 150 | end 151 | 152 | function handler.disconnect(fd) 153 | handshake[fd] = nil 154 | local c = connection[fd] 155 | if c then 156 | c.fd = nil 157 | connection[fd] = nil 158 | if conf.disconnect_handler then 159 | conf.disconnect_handler(c.username) 160 | end 161 | end 162 | end 163 | 164 | handler.error = handler.disconnect 165 | 166 | -- atomic , no yield 167 | local function do_auth(fd, message, addr) 168 | local username, index, hmac = string.match(message, "([^:]*):([^:]*):([^:]*)") 169 | local u = user_online[username] 170 | if u == nil then 171 | return "404 User Not Found" 172 | end 173 | local idx = assert(tonumber(index)) 174 | hmac = b64decode(hmac) 175 | 176 | if idx <= u.version then 177 | return "403 Index Expired" 178 | end 179 | 180 | local text = string.format("%s:%s", username, index) 181 | local v = crypt.hmac_hash(u.secret, text) -- equivalent to crypt.hmac64(crypt.hashkey(text), u.secret) 182 | if v ~= hmac then 183 | return "401 Unauthorized" 184 | end 185 | 186 | u.version = idx 187 | u.fd = fd 188 | u.ip = addr 189 | connection[fd] = u 190 | end 191 | 192 | local function auth(fd, addr, msg, sz) 193 | local message = netpack.tostring(msg, sz) 194 | local ok, result = pcall(do_auth, fd, message, addr) 195 | if not ok then 196 | skynet.error(result) 197 | result = "400 Bad Request" 198 | end 199 | 200 | local close = result ~= nil 201 | 202 | if result == nil then 203 | result = "200 OK" 204 | end 205 | 206 | socketdriver.send(fd, netpack.pack(result)) 207 | 208 | if close then 209 | gateserver.closeclient(fd) 210 | else 211 | if conf.auth_handler then 212 | local u = connection[fd] 213 | conf.auth_handler(u.username, fd, addr) 214 | end 215 | end 216 | end 217 | 218 | local request_handler = assert(conf.request_handler) 219 | 220 | -- u.response is a struct { return_fd , response, version, index } 221 | local function retire_response(u) 222 | if u.index >= expired_number * 2 then 223 | local max = 0 224 | local response = u.response 225 | for k,p in pairs(response) do 226 | if p[1] == nil then 227 | -- request complete, check expired 228 | if p[4] < expired_number then 229 | response[k] = nil 230 | else 231 | p[4] = p[4] - expired_number 232 | if p[4] > max then 233 | max = p[4] 234 | end 235 | end 236 | end 237 | end 238 | u.index = max + 1 239 | end 240 | end 241 | 242 | local function do_request(fd, message) 243 | local u = assert(connection[fd], "invalid fd") 244 | local session = string.unpack(">I4", message, -4) 245 | message = message:sub(1,-5) 246 | local p = u.response[session] 247 | if p then 248 | -- session can be reuse in the same connection 249 | if p[3] == u.version then 250 | local last = u.response[session] 251 | u.response[session] = nil 252 | p = nil 253 | if last[2] == nil then 254 | local error_msg = string.format("Conflict session %s", crypt.hexencode(session)) 255 | skynet.error(error_msg) 256 | error(error_msg) 257 | end 258 | end 259 | end 260 | 261 | if p == nil then 262 | p = { fd } 263 | u.response[session] = p 264 | local ok, result = pcall(conf.request_handler, u.username, message) 265 | -- NOTICE: YIELD here, socket may close. 266 | result = result or "" 267 | if not ok then 268 | skynet.error(result) 269 | result = string.pack(">BI4", 0, session) 270 | else 271 | result = result .. string.pack(">BI4", 1, session) 272 | end 273 | 274 | p[2] = string.pack(">s2",result) 275 | p[3] = u.version 276 | p[4] = u.index 277 | else 278 | -- update version/index, change return fd. 279 | -- resend response. 280 | p[1] = fd 281 | p[3] = u.version 282 | p[4] = u.index 283 | if p[2] == nil then 284 | -- already request, but response is not ready 285 | return 286 | end 287 | end 288 | u.index = u.index + 1 289 | -- the return fd is p[1] (fd may change by multi request) check connect 290 | fd = p[1] 291 | if connection[fd] then 292 | socketdriver.send(fd, p[2]) 293 | end 294 | p[1] = nil 295 | retire_response(u) 296 | end 297 | 298 | local function request(fd, msg, sz) 299 | local message = netpack.tostring(msg, sz) 300 | local ok, err = pcall(do_request, fd, message) 301 | -- not atomic, may yield 302 | if not ok then 303 | skynet.error(string.format("Invalid package %s : %s", err, message)) 304 | if connection[fd] then 305 | gateserver.closeclient(fd) 306 | end 307 | end 308 | end 309 | 310 | function handler.message(fd, msg, sz) 311 | local addr = handshake[fd] 312 | if addr then 313 | auth(fd,addr,msg,sz) 314 | handshake[fd] = nil 315 | else 316 | request(fd, msg, sz) 317 | end 318 | end 319 | 320 | return gateserver.start(handler) 321 | end 322 | 323 | return server 324 | -------------------------------------------------------------------------------- /xjgame/service/activityserver.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- 这是一个简单活动定时开启、关闭、预告的活动管理器 3 | -- 每当定时器到了的时候会向全局 eventserver 派发活动状态驱动消息,驱动侦听这些事件的各个活动服务开展工作 4 | -- 按照时间划分活动(按天、周、月、年周期进行的活动) 5 | -- 具体的活动逻辑由各个活动服务实现,此服务只负责活动开启驱动 6 | -- 7 | 8 | local skynet = require "skynetex" 9 | local cluster = require "cluster" 10 | 11 | local command = {} 12 | 13 | local running_activity = {} 14 | local start_timer = {} 15 | local end_timer = {} 16 | local pre_timer = {} 17 | local pre_stoptimer = {} 18 | 19 | local propactivity = { 20 | [1] = { ID = 1, Name = "摇钱树", type = "day", start = "12:30:0", timelen = 20*60, pretime = {10,5,1}, prestoptime = {5,1}, }, 21 | [2] = { ID = 2, Name = "世界boss", type = "week", start = "4|18:0:0", timelen = 30*60, pretime = {10,5,1}, prestoptime = {5,1}, }, 22 | [3] = { ID = 3, Name = "体力恢复翻倍", type = "month", start = "15|0:0:0", timelen = 24*60*60, pretime = {10,5,1}, prestoptime = {5,1}, }, 23 | [4] = { ID = 4, Name = "国庆活动", type = "year", start = "10.1|0:0:0", timelen = 3*24*60*60, pretime = {10,5,1}, prestoptime = {5,1}, }, 24 | } 25 | 26 | -- dispath the message that activity start now 27 | local function dispath_start_messge( activity_id ) 28 | cluster.call("login", ".eventserver", "publish", "activity_start", activity_id) 29 | end 30 | 31 | -- dispath the message that activity stop now 32 | local function dispath_end_message( activity_id ) 33 | cluster.call("login", ".eventserver", "publish", "activity_end", activity_id) 34 | end 35 | 36 | -- dispath the message that activity start soon 37 | local function dispath_pre_begin_message( activity_id, ti ) 38 | cluster.call("login", ".eventserver", "publish", "activity_pre_begin", activity_id, ti) 39 | end 40 | 41 | -- dispath the message that activity stop soon 42 | local function dispath_pre_stop_message( activity_id, ti ) 43 | cluster.call("login", ".eventserver", "publish", "activity_pre_stop", activity_id, ti) 44 | end 45 | 46 | local function set_activity_timer( len, prop, lestlen ) 47 | start_timer[prop.ID] = skynet.add_timer(len*100, function() 48 | start_timer[prop.ID] = nil 49 | pre_timer[prop.ID] = nil 50 | running_activity[prop.ID] = true 51 | 52 | -- Todo dispath activity start message 53 | dispath_start_messge(prop.ID) 54 | 55 | -- begin timer activity end logic 56 | end_timer[prop.ID] = skynet.add_timer(lestlen*100, function( ) 57 | end_timer[prop.ID] = nil 58 | 59 | -- dispath activity end message 60 | dispath_end_message(prop.ID) 61 | 62 | -- begin next time logic 63 | init_activity(prop.ID) 64 | end) 65 | 66 | -- 设置活动结束预告定时器 67 | if prop.prestoptime then 68 | if not pre_stoptimer[prop.ID] then 69 | pre_stoptimer[prop.ID] = {} 70 | end 71 | 72 | for key, ti in ipairs(prop.prestoptime) do 73 | local tmplen = lestlen - ti*60 74 | if tmplen > 0 then 75 | local timer = skynet.add_timer(tmplen*100, function( ) 76 | dispath_pre_stop_message(prop.ID, ti) 77 | end) 78 | table.insert(pre_stoptimer[prop.ID], timer) 79 | end 80 | end 81 | end 82 | 83 | end) 84 | 85 | -- 设置预告定时器 86 | if prop.pretime then 87 | if not pre_timer[prop.ID] then 88 | pre_timer[prop.ID] = {} 89 | end 90 | 91 | for key, ti in ipairs(prop.pretime) do 92 | local tmplen = len - ti*60 93 | if tmplen > 0 then 94 | local timer = skynet.add_timer(tmplen*100, function( ) 95 | dispath_pre_begin_message(prop.ID, ti) 96 | end) 97 | table.insert(pre_timer[prop.ID], timer) 98 | end 99 | end 100 | end 101 | end 102 | 103 | -- init the day type activity 104 | local function init_day_activity(prop, is_next) 105 | assert(prop.type=="day") 106 | running_activity[prop.ID] = nil 107 | 108 | local timearr = {} 109 | for ti in string.gmatch(prop.start, "%d+") do 110 | table.insert(timearr, ti) 111 | end 112 | 113 | local now = os.time() 114 | local nowdate = os.date("*t", now) 115 | nowdate.hour = timearr[1] 116 | nowdate.min = timearr[2] 117 | nowdate.sec = timearr[3] 118 | 119 | local tstime = os.time(nowdate) -- the start time 120 | local tetime = tstime + prop.timelen -- the stop time 121 | 122 | local next_tstime = tstime + 24*3600 123 | local last_tetime = tetime - 24*3600 124 | 125 | local lestlen = prop.timelen 126 | local len = 0 127 | if now < tstime and now > last_tetime then 128 | len = tstime - now 129 | elseif (now + 20) > tetime and now < next_tstime then -- +20 是为了避免在活动刚刚结束时如果时间误差而造成重复启动活动的问题 130 | len = next_tstime - now 131 | else 132 | -- activity is running 133 | if now < last_tetime then 134 | lestlen = last_tetime - now 135 | end 136 | end 137 | 138 | set_activity_timer(len, prop, lestlen) 139 | end 140 | 141 | local function init_week_activity( prop ) 142 | assert(prop.type=="week") 143 | running_activity[prop.ID] = nil 144 | 145 | local timearr = {} 146 | for ti in string.gmatch(prop.start, "%d+") do 147 | table.insert(timearr, ti) 148 | end 149 | 150 | local now = os.time() 151 | local nowdate = os.date("*t", now) 152 | local dstday = timearr[1] - nowdate.wday 153 | 154 | nowdate.day = nowdate.day + dstday 155 | nowdate.hour = timearr[2] 156 | nowdate.min = timearr[3] 157 | nowdate.sec = timearr[4] 158 | 159 | local tstime = os.time(nowdate) -- the start time this week 160 | local tetime = tstime + prop.timelen -- the stop time this week 161 | 162 | local next_tstime = tstime + 7*24*3600 163 | local last_tetime = tetime - 7*24*3600 164 | 165 | local lestlen = prop.timelen 166 | local len = 0 167 | if now < tstime and now > last_tetime then 168 | len = tstime - now 169 | elseif (now + 20) > tetime and now < next_tstime then -- +20 是为了避免在活动刚刚结束时如果时间误差而造成重复启动活动的问题 170 | len = next_tstime - now 171 | else 172 | -- activity is running 173 | if now < last_tetime then 174 | lestlen = last_tetime - now 175 | end 176 | end 177 | 178 | set_activity_timer(len, prop, lestlen) 179 | end 180 | 181 | local function init_month_activity( prop ) 182 | assert(prop.type=="month") 183 | running_activity[prop.ID] = nil 184 | 185 | local timearr = {} 186 | for ti in string.gmatch(prop.start, "%d+") do 187 | table.insert(timearr, ti) 188 | end 189 | 190 | local now = os.time() 191 | local nowdate = os.date("*t", now) 192 | 193 | -- 25|10:10:10 194 | nowdate.day = timearr[1] 195 | nowdate.hour = timearr[2] 196 | nowdate.min = timearr[3] 197 | nowdate.sec = timearr[4] 198 | 199 | local tstime = os.time(nowdate) 200 | local tetime = tstime + prop.timelen 201 | 202 | local last_tedate = os.date( "*t", tetime ) 203 | if last_tedate.month == 1 then 204 | last_tedate.year = last_tedate.year - 1 205 | last_tedate.month = 12 206 | else 207 | last_tedate.month = last_tedate.month - 1 208 | end 209 | local last_tetime = os.time(last_tedate) 210 | 211 | local lestlen = prop.timelen 212 | local len = 0 213 | if now < tstime and now > last_tetime then 214 | len = tstime - now 215 | elseif (now + 20) > tetime then -- +20 是为了避免在活动刚刚结束时如果时间误差而造成重复启动活动的问题 216 | if nowdate.month < 12 then 217 | nowdate.month = nowdate.month + 1 218 | else 219 | nowdate.month = 1 220 | nowdate.year = nowdate.year + 1 221 | end 222 | local tstime = os.time(nowdate) 223 | len = tstime - now 224 | else 225 | -- activity is running 226 | if now < last_tetime then 227 | lestlen = last_tetime - now 228 | end 229 | end 230 | 231 | set_activity_timer(len, prop, lestlen) 232 | end 233 | 234 | local function init_year_activity( prop ) 235 | assert(prop.type=="year") 236 | running_activity[prop.ID] = nil 237 | 238 | local timearr = {} 239 | for ti in string.gmatch(prop.start, "%d+") do 240 | table.insert(timearr, ti) 241 | end 242 | 243 | local now = os.time() 244 | local nowdate = os.date("*t", now) 245 | 246 | -- 2.14|10:10:10 247 | nowdate.month = timearr[1] 248 | nowdate.day = timearr[2] 249 | nowdate.hour = timearr[3] 250 | nowdate.min = timearr[4] 251 | nowdate.sec = timearr[5] 252 | 253 | local tstime = os.time(nowdate) 254 | local tetime = tstime + prop.timelen 255 | 256 | local last_tedate = os.date( "*t", tetime ) 257 | last_tedate.year = last_tedate.year - 1 258 | local last_tetime = os.time(last_tedate) 259 | 260 | local lestlen = prop.timelen 261 | local len = 0 262 | if now < tstime and now > last_tetime then 263 | len = tstime - now 264 | elseif (now + 20) > tetime then -- +20 是为了避免在活动刚刚结束时如果时间误差而造成重复启动活动的问题 265 | nowdate.year = nowdate.year + 1 266 | local next_tstime = os.time(nowdate) 267 | len = next_tstime - now 268 | else 269 | -- activity is running 270 | if now < last_tetime then 271 | lestlen = last_tetime - now 272 | end 273 | end 274 | 275 | set_activity_timer(len, prop, lestlen) 276 | end 277 | 278 | function init_activity( id ) 279 | local prop = assert(propactivity[id]) 280 | if prop.type == "day" then 281 | init_day_activity(prop) 282 | elseif prop.type == "week" then 283 | init_week_activity(prop) 284 | elseif prop.type == "month" then 285 | init_month_activity(prop) 286 | elseif prop.type == "year" then 287 | init_year_activity(prop) 288 | else 289 | error(string.format("the acitivity type is wrong, id:%s, type:%s", prop.ID, prop.type ) ) 290 | end 291 | end 292 | 293 | local function __activity_init__() 294 | for id, prop in pairs(propactivity) do 295 | init_activity(id) 296 | end 297 | end 298 | 299 | function command.start_activity( id ) 300 | assert(propactivity[id]) 301 | command.stop_activity(id) 302 | init_activity(id) 303 | end 304 | 305 | function command.stop_activity( id ) 306 | if running_activity[id] then 307 | dispath_end_message( id ) 308 | running_activity[id] = nil 309 | end 310 | 311 | if start_timer[id] then 312 | skynet.del_timer(start_timer[id]) 313 | start_timer[id] = nil 314 | end 315 | 316 | if end_timer[id] then 317 | skynet.del_timer(end_timer[id]) 318 | end_timer[id] = nil 319 | end 320 | 321 | if pre_timer[prop.ID] then 322 | for _, timer in pairs(pre_timer[prop.ID]) do 323 | skynet.del_timer(timer) 324 | end 325 | pre_timer[prop.ID] = nil 326 | end 327 | end 328 | 329 | function command.all_running_activity( ) 330 | local ret = {} 331 | for id, _ in pairs(running_activity) do 332 | table.insert(ret, id) 333 | end 334 | return ret 335 | end 336 | 337 | skynet.start(function() 338 | skynet.dispatch("lua", function(session, address, cmd, ...) 339 | local f = command[cmd] 340 | if f then 341 | skynet.ret(skynet.pack(f(...))) 342 | else 343 | error(string.format("Unknown command %s", tostring(cmd))) 344 | end 345 | end) 346 | skynet.register ".activityserver" 347 | 348 | __activity_init__() 349 | end) 350 | --------------------------------------------------------------------------------