Browse Source

first commit

make 1 month ago
commit
327d0b7ae3
100 changed files with 23063 additions and 0 deletions
  1. 28 0
      config
  2. 18 0
      game.conf
  3. 57 0
      host.lua
  4. 238 0
      preload.lua
  5. 37 0
      project/lib/logger.lua
  6. 94 0
      project/lib/logger_debug.lua
  7. 87 0
      project/lib/pbSproto.lua
  8. 34 0
      project/lib/pipeline.lua
  9. 2955 0
      project/lib/sha2.lua
  10. 77 0
      project/lib/stringify.lua
  11. 56 0
      project/lib/strtable.lua
  12. 13 0
      project/lib/timer.lua
  13. 289 0
      project/lib/util.lua
  14. 234 0
      project/model/activity/act_rank.lua
  15. 339 0
      project/model/activitytimemod.lua
  16. 882 0
      project/model/adventure.lua
  17. 9 0
      project/model/asset.lua
  18. 45 0
      project/model/character.lua
  19. 358 0
      project/model/common_fun.lua
  20. 252 0
      project/model/currency.lua
  21. 291 0
      project/model/embattle.lua
  22. 745 0
      project/model/equip.lua
  23. 62 0
      project/model/example.lua
  24. 155 0
      project/model/gm.lua
  25. 471 0
      project/model/hero.lua
  26. 12 0
      project/model/keygen.lua
  27. 23 0
      project/model/loader.lua
  28. 221 0
      project/model/log.lua
  29. 404 0
      project/model/mailbox.lua
  30. 502 0
      project/model/manual/init.lua
  31. 427 0
      project/model/manual/relic_manual.lua
  32. 93 0
      project/model/module_fun.lua
  33. 39 0
      project/model/mq.lua
  34. 60 0
      project/model/payment.lua
  35. 607 0
      project/model/player.lua
  36. 349 0
      project/model/powerfunc.lua
  37. 64 0
      project/model/presence.lua
  38. 487 0
      project/model/quest/init.lua
  39. 529 0
      project/model/quest/manager.lua
  40. 392 0
      project/model/rechargemod.lua
  41. 80 0
      project/model/red_point.lua
  42. 150 0
      project/model/reward.lua
  43. 358 0
      project/model/role.lua
  44. 31 0
      project/model/schema.lua
  45. 237 0
      project/model/spawn.lua
  46. 527 0
      project/model/stats.lua
  47. 181 0
      project/model/talent.lua
  48. 228 0
      project/model/time_box.lua
  49. 116 0
      project/proto/errno.lua
  50. 1303 0
      project/proto/game.pb
  51. 5 0
      project/proto/mkpb.sh
  52. 1 0
      project/proto/proto
  53. 0 0
      project/ser_list.sql
  54. 393 0
      project/service/act_rankinglist.lua
  55. 375 0
      project/service/activitytime.lua
  56. 249 0
      project/service/agent.lua
  57. 110 0
      project/service/assetcenter.lua
  58. 57 0
      project/service/gift.lua
  59. 151 0
      project/service/itemavg.lua
  60. 64 0
      project/service/log/init.lua
  61. 100 0
      project/service/loginserver/apple.lua
  62. 90 0
      project/service/loginserver/google.lua
  63. 63 0
      project/service/loginserver/init.lua
  64. 196 0
      project/service/loginserver/shengtian.lua
  65. 37 0
      project/service/loginserver/tourist.lua
  66. 50 0
      project/service/logserver.lua
  67. 159 0
      project/service/logslave.lua
  68. 625 0
      project/service/mailbox.lua
  69. 81 0
      project/service/main.lua
  70. 103 0
      project/service/mq.lua
  71. 177 0
      project/service/namecenter.lua
  72. 106 0
      project/service/pbuf.lua
  73. 10 0
      project/service/protoloader.lua
  74. 296 0
      project/service/quest.lua
  75. 188 0
      project/service/rankinglist.lua
  76. 162 0
      project/service/recharge.lua
  77. 131 0
      project/service/relic_manual.lua
  78. 38 0
      project/service/snowflake.lua
  79. 271 0
      project/service/statistics.lua
  80. 279 0
      project/service/usercenter.lua
  81. 109 0
      project/service/webagent.lua
  82. 45 0
      project/service/webserver.lua
  83. 163 0
      project/service/ws_gate.lua
  84. 589 0
      project/service/ws_watchdog.lua
  85. 82 0
      project/webpage/check_role.lua
  86. 49 0
      project/webpage/check_server.lua
  87. 540 0
      project/webpage/delitem.lua
  88. 146 0
      project/webpage/exchange_account.lua
  89. 128 0
      project/webpage/field_inquire.lua
  90. 154 0
      project/webpage/forbit.lua
  91. 108 0
      project/webpage/get_server_uid.lua
  92. 269 0
      project/webpage/gm_test.lua
  93. 102 0
      project/webpage/guild.lua
  94. 123 0
      project/webpage/hotfix.lua
  95. 286 0
      project/webpage/inquire_deltime.lua
  96. 133 0
      project/webpage/mail.lua
  97. 84 0
      project/webpage/modify_time.lua
  98. 77 0
      project/webpage/notice.lua
  99. 63 0
      project/webpage/online.lua
  100. 0 0
      project/webpage/player.lua

+ 28 - 0
config

@@ -0,0 +1,28 @@
+root   = "../"
+skynet = root .. "skynet/"
+enablessl = true
+
+-- 基础配置项
+thread = 8
+logger = nil
+logpath = "."
+daemon = nil
+profile = true
+harbor = 0
+bootstrap = "snlua bootstrap"
+lualoader = skynet .. "lualib/loader.lua"
+
+-- skynet 服务路径
+luaservice = skynet .. "service/?.lua;"
+
+-- c 编写的服务路径
+cpath = skynet .. "cservice/?.so"
+
+-- 将添加到 package.cpath 中的路径,供 require 调用
+lua_cpath = skynet .. "luaclib/?.so"
+
+-- 将添加到 package.path 中的路径,供 require 调用
+lua_path = root .. "?.lua;" ..
+			     skynet .. "?.lua;" ..
+			     skynet .. "lualib/?.lua;" ..
+					 skynet .. "lualib/?/init.lua;"

+ 18 - 0
game.conf

@@ -0,0 +1,18 @@
+include "config"
+asset = root .. "config/testserver/"
+src = root .. "src/"
+project	= src .. "project/"
+preload = src .. "preload.lua"
+
+start = "main"
+luaservice 	= luaservice ..
+              project .. "service/?.lua;" ..
+              project .. "service/?/init.lua;"
+lua_path    = lua_path ..
+              asset .. "?.lua;" ..
+              src .. "?.lua;" ..
+              project .. "?.lua;" ..
+              project .. "?/init.lua;" ..
+              project .. "lib/?.lua;" ..
+              project .. "proto/?.lua;"..
+              project .. "service/list/?.lua;"

+ 57 - 0
host.lua

@@ -0,0 +1,57 @@
+local SERVER_ID = 1       -- 服务器标识
+local MAX_CLIENT = 8000   -- 最大连接数限制
+
+local GAME_PORT = 8010    -- 向客户开放的游戏端口
+local WEB_PORT = 9010     -- 向WEB开放的控制端口
+local CONSOLE_PORT = 7010 -- 控制台端口
+
+local REDIS_HOST = "127.0.0.1"    -- 数据库主机ip地址
+local REDIS_PORT = 6010               -- 数据库端口
+local REDIS_AUTH = 'xh123'          -- 数据库密码
+
+local WEB_HOST = "0.0.0.0"
+
+option = {
+  sid = SERVER_ID,
+  game_port = GAME_PORT,
+  web_port = WEB_PORT,
+  console_port = CONSOLE_PORT,
+  max_client = MAX_CLIENT,
+  web_host = WEB_HOST,
+  redis = {
+    auth = REDIS_AUTH,
+    host = REDIS_HOST,
+    port = REDIS_PORT,
+  },
+  cluster_redis = {
+    auth = REDIS_AUTH,
+    host = REDIS_HOST,
+    port = 7000,
+  },
+  cluster = {
+    -- log = "127.0.0.1:2345",
+  }
+}
+
+-- 集群地址配置
+-- CLUSTER = {
+--   watchdog = "127.0.0.1:10001", -- 接受集群推送
+--   master = "127.0.0.1:12000",
+-- }
+
+-- 集群启动使用参数
+-- MASTER_CONF = {
+--   debug_port = 5558,
+--   web_port = 5559,
+-- }
+
+-- 服务器启动相关参数
+LAUNCH = {
+  test_recharge = true;       -- 测试充值
+  maintain = false,           -- true:维护 false:开放
+  battle_log = true,         -- true:开启战斗日志 false:关闭战斗日志
+  white_list = {
+  },
+}
+
+RECHARGE_SERVICE = "127.0.0.1:8081"

+ 238 - 0
preload.lua

@@ -0,0 +1,238 @@
+require "host"    -- import host config
+
+STD_ERR = require "errno"
+
+MAXRAND = 10000
+
+DAY_SEC = 86400
+HOUR_SEC = 3600
+MIN_SEC = 60
+
+MONEY_PARAM = 100 
+
+-- 物品类型
+GOODS_NONE      = 0 -- 未知类型
+GOODS_HERO      = 1 -- 英雄(角色)
+GOODS_CARD      = 2 -- 技能卡
+GOODS_EQUIP     = 3 -- 装备
+GOODS_MONEY     = 4 -- 货币
+GOODS_DEBRIS    = 5 -- 碎片
+GOODS_BOX       = 6 -- 宝箱
+
+-- 定义全局货币id, 根据GoodsConfig修改
+CURRENCY_ID_COINS       = 100001        -- 金币
+CURRENCY_ID_STM         = 110001        -- 体力
+CURRENCY_ID_DACTIVITY   = 110003        -- 日常活跃
+CURRENCY_ID_WACTIVITY   = 110004        -- 周常活跃
+CURRENCY_ID_RELIC_SCORE = 110005        -- 遗迹积分
+CURRENCY_ID_RELIC_COIN  = 110006        -- 遗迹币
+CURRENCY_ID_EQUIP_COIN  = 110009        -- 锻造锤
+CURRENCY_ID_DIA         = 130001        -- 钻石
+CURRENCY_ID_IRON        = 140001        -- 铁矿
+CURRENCY_ID_WOOD        = 140002        -- 铁矿
+CURRENCY_ID_ROLE_EXP    = 140003        -- 队伍经验
+CURRENCY_ID_EXP_STONE   = 140004        -- 经验石
+CURRENCY_ID_EXP_BOOK    = 140005        -- 经验书
+CURRENCY_ID_TALENT_STONE    = 140006        -- 天赋石
+CURRENCY_ID_WEAPON_SCROLL   = 140007        -- 武器卷轴
+CURRENCY_ID_JEWELRY_SCROLL  = 140008        -- 首饰卷轴
+CURRENCY_ID_WRISTER_SCROLL  = 140009        -- 护腕卷轴
+CURRENCY_ID_CUIRASS_SCROLL  = 140010        -- 胸甲卷轴
+CURRENCY_ID_BELT_SCROLL = 140011        -- 腰带卷轴
+CURRENCY_ID_SHOE_SCROLL = 140012        -- 鞋子卷轴G
+CURRENCY_ID_DRAW_COIN   = 140050        -- 召唤卷
+CURRENCY_ID_DRAW_COIN2 = 140051        -- 高级召唤卷
+
+
+-- 货币类型
+CURRENCY_TYPE_RANDOM_BOX = 8    -- 随机宝箱
+CURRENCY_TYPE_SELECT_BOX = 9    -- 选择宝箱
+
+-- 英雄类型
+HERO_TYPE_CARD= 5
+HERO_TYPE_CITY = 6
+
+-- 品阶
+STAR_COLOR_PURPLE = 4   -- 紫色
+STAR_COLOR_ORANGE = 7   -- 橙色
+STAR_COLOR_RED = 11     -- 红色
+
+-- 任务类型
+DAILY_QUEST     = 1     -- 日常任务
+WEEKLY_QUEST    = 2     -- 周常任务
+ACHI_QUEST      = 3     -- 成就任务
+MAIN_QUEST    = 4 -- 主线任务
+
+-- 任务状态
+QUEST_PROGRESS  = 0     -- 进行中的任务
+QUEST_REACHED   = 1     -- 已达成的任务
+QUEST_END       = 2     -- 已结束的任务
+
+-- 排行榜类型
+RANKING_ADV = 1         -- 冒险
+RANKING_POWER = 2       -- 战力
+
+-- 支付方式
+PAY_TYPE_FREE = 0    -- 免费
+PAY_TYPE_GIFT = 1    -- 计费点礼包
+PAY_TYPE_ITEM = 2    -- 资源支付
+
+-- 限购方式
+LIMIT_TYPE_NONE = 0     -- 不限购
+LIMIT_TYPE_DAILY = 1    -- 日限购
+LIMIT_TYPE_WEEKLY = 2   -- 周限购
+LIMIT_TYPE_MONTHLY = 3   -- 月限购
+LIMIT_TYPE_LONG = 4     -- 终身限购
+
+
+-- 不知到放哪就放这把
+OTHER_REDIS_KEY = "other"
+
+
+STATE_OPEN = 0 -- 活动开启
+STATE_CLOSE = 1 -- 活动关闭
+STATE_OVER = 2 -- 活动结束(奖励领取结束,或不再显示)
+
+-- 模块id
+MODULE_ID_RELIC_MANUA = 50
+
+-- 活动相关模块
+MODULE_ID_QUEST = 15        -- 任务
+MODULE_ID_SIGN_IN = 18      -- 日常签到
+MODULE_ID_DIA_DRAW = 20     -- 抽卡
+MODULE_ID_SHOP = 45         -- 商店
+MODULE_ID_DAILY_DUNGEONS = 53 -- 日常副本
+MODULE_ID_FUND = 61         -- 基金
+MODULE_ID_LOTTERY = 76      -- 千抽     
+
+MODULE_ID_RELIC = 2003      -- 遗迹
+MODULE_ID_DRAW_EQUIP = 2004  -- 装备锻造
+MODULE_ID_DIA_SHOP = 2009
+MODULE_ID_ADV_GIFT = 2010   -- 冒险礼包
+MODULE_ID_PRIVILEGE_CARD = 2011 -- 特权卡
+MODULE_ID_AD_CARD = 2012
+MODULE_ID_MONTH_CARD = 2013     -- 月卡
+MODULE_ID_SUPREME_CARD = 2014
+MODULE_ID_KEEP_RECHARGE = 2015 -- 连续充值
+MODULE_ID_LEVEL_GIFT = 2017 -- 等级礼包
+
+MODULE_ID_FIRST_RECHARGE1 = 54
+MODULE_ID_FIRST_RECHARGE2 = 3002
+MODULE_ID_FIRST_RECHARGE3 = 3003
+
+MODULE_ID_FUND1 = 3006 -- 成长基金
+MODULE_ID_FUND2 = 3007 -- 冒险基金
+MODULE_ID_FUND3 = 3008 -- 精英基金
+MODULE_ID_FUND4 = 3009 -- 召唤基金
+MODULE_ID_FUND5 = 3010 -- 锻造基金
+
+MODULE_ID_POP_GIFT = 3011 -- 弹窗礼包
+
+MODULE_ID_HERO_RANK = 4001          -- 开服英雄排行
+MODULE_ID_SIMPLE_BATTLE_RANK = 4002 -- 开服闯关排行
+MODULE_ID_POWER_RANK = 4003         -- 开服战力排行
+MODULE_ID_ELITE_BATTLE_RANK = 4004  -- 开服精英排行
+MODULE_ID_TREASURE = 4005   -- 开服宝藏
+
+
+--怪物类型
+MONESTER_TYPE_ORDINARGY = 21 -- 普通
+MONESTER_TYPE_ELITE = 22    -- 精英
+MONESTER_TYPE_BOSS = 23     -- boss
+
+--
+ATTR_ADV_COIN = 100     -- 冒险金币加成
+ATTR_ADV_EXP_STONE = 101      -- 冒险经验石加成
+ATTR_FARM_ADD = 102     -- 农场加成
+ATTR_COIN_ADD = 103     -- 金矿加成
+ATTR_WOOD_ADD = 104     -- 伐木场加成
+ATTR_IRON_ADD = 105     -- 铁矿加成
+
+-- 品质(装备/道具)
+-- eQUALITY = {
+--   WHITE   = 0,    -- 白
+--   GREEN   = 1,    -- 绿
+--   BLUE    = 2,    -- 蓝
+--   PURPLE  = 3,    -- 紫
+--   ORANGE  = 4,    -- 橙
+--   RED	  = 5,    -- 红
+--   DRED    = 6,    -- 深红
+--   GOLDEN  = 7,    -- 金
+--   DGOLDEN = 8,    -- 升金
+-- }
+
+
+
+
+-- 聊天类型 主类型
+-- CHAT_TYPE = {
+--     WORD_CHAT       = 1,          -- 世界聊天
+--     NOTICE_CHAT     = 2,          -- 公告
+--     PRIVATE_CHAT    = 3,          -- 私聊
+--     GUILD_CHAT      = 4,          -- 公会聊天
+--     SPAN_CHAT       = 5,          -- 跨服聊天
+-- }
+
+
+
+-- -- 公告子类型
+-- CHAT_SON_TYPE = {
+--     VIP_UP = 101,                 -- 跑马灯配置sid-玩家vip提升
+--     RED_ELF = 201,                -- 跑马灯配置sid-玩家获得红色精灵
+--     ORANGE_ELF = 202,             -- 跑马灯配置sid-玩家获得橙色精灵
+--     GOLDEN_ELF = 203,             -- 跑马灯配置sid-玩家获得金色精灵
+--     GEM_UP = 301,                 -- 跑马灯配置sid-宝石升级6级以上
+--     STAR_UP = 401,                -- 跑马灯配置sid-精灵升星6星以上
+--     GM_NOTICE = 501,              -- 跑马灯配置sid-来自GM的系统公告
+--     SPELUNK_1 = 701,              -- 地心第一名
+--     SPELUNK_2 = 702,              -- 地心第二名
+--     SPELUNK_3 = 703,              -- 地心第三名
+--     PROTABLE  = 801,              -- 携带拍三个以上
+--     BUDO_ID = 901,                -- 道馆通关
+--     ELF_KING = 1100,              -- 精灵王胜利
+--     ELF_AWAKEN = 1201,            -- 精灵觉醒
+--     ELF_UPANISHADS = 1301,        -- 精灵学习奥义
+--     WORLD_BOSS  = 1508,           -- 世界boss被击败
+--     RUNE_GET = 1509,              -- 玩家获得橙色(及)以上的符文
+--     UP_WORLD_BOSS = 1514,         -- 世界boss刷新
+--     BT_EASY=1510,                 --通关埋藏之塔普通
+--     BT_DIFFICULT=1511,            --通关埋藏之塔困难
+--     ADVENTURE_FIRST=1512,         --首位通关章节地图
+--     SE_RED=1513,                  --完成红色秘境任务
+--     BADGE_INFO=1601,              --徽章升级到指定数量
+--     DRESS_TITLE=1701,             --获得时装获得称号
+--     DIAMOND_WEEK=2201,            --购买周卡
+--     DIAMOND_MONTH=2202,            --购买月卡
+--     DIAMOND_LIFELONG=2203,         --购买终身卡
+--     FUND_BUY=2204,                 --购买成长基金
+--     MEGA_REDGET=1801,              --通关mega副本获得红色钥石
+--     AWAKEN_PLAYER=2001,            --角色进阶达到高阶训练家以上
+--     RUNE_STRENGTH=2101,            --符文强化
+--     MAZE_FIVE=2205,                --宝可梦迷宫通关第五层
+--     Z_ACTIVE=2301,                 --怒气技能激活
+--     Z_UPGRADE=2302,                 --怒气技能升级
+--     FIRSTL_ATHLETICSMOD=2401,       --冠军之路第一名
+--     FIRSTL_ALONE_BATTLE=2402,       --单挑之王第一名
+--     HANG_GET_RED_EQUIP=2501,        --挂机获得红装
+--     SMELT_GET_RED_EQUIP=2502,       --熔炼获得红装
+--     MAKE_GET_RED_EQUIP=2503,        --打造获得红装
+--     HANG_GET_RED_DEVRIS=2504,       --挂机获得红色精灵碎片
+
+--     NEWHUNTING_BOSS=2698,       --新狩猎boss触发
+
+
+--     -- s_type
+--     GUILD_CHAT       = 600,       -- 公会玩家聊天
+--     GUILD_jx1        = 601,       -- 公会捐献钻石
+--     GUILD_jx2        = 602,       -- 公会捐献金币
+--     GUILD_UP_LV      = 603,       -- 公会升级
+--     GUILD_ADV_AUTO   = 604,       -- 公会自动开启冒险
+--     GUILD_ADV_HAND   = 605,       -- 公会成员开启冒险
+--     GUILD_ADV_FINISH = 606,       -- 完成冒险地图
+--     GUILD_ADV_UNLOCK = 607,       -- 解锁新的冒险地图
+--     GUILD_JION       = 608,       -- 玩家加入公会
+--     GUILD_EXIT       = 609,       -- 玩家离开公会
+--     GUILD_DUTY       = 610,       -- 玩家职务变更
+--     GUILD_APPLY_FAIL = 611,       -- 公会战报名,分配据点失败 报名公会过多
+--     GUILD_APPLY_SUC  = 612,       -- 公会战报名,分配据点成功
+-- }

+ 37 - 0
project/lib/logger.lua

@@ -0,0 +1,37 @@
+local skynet = require "skynet"
+local skynet_error = skynet.error
+local string_format = string.format
+local table_insert = table.insert
+local os_date = os.date
+local os_time = os.time
+
+local const_header = { "(T)|", "(D)|", "(I)|", "(W)|", "(E)|", "(F)|", "(TS)" }
+local header = const_header
+local function print(level, fmt, ...)
+  skynet_error(
+      os_date("[%m/%d %X]", os_time()),
+      header[level],
+      string_format(fmt, ...))
+end
+
+local logger = {}
+function logger.label(name)
+  if name then
+    header = {}
+    for _, v in ipairs(const_header) do
+      table_insert(header, v..name)
+    end
+  else
+    header = const_header
+  end
+end
+
+function logger.test(fmt, ...) print(7, fmt, ...) end
+-- function logger.test(fmt, ...)  end
+function logger.trace(fmt, ...) print(1, fmt, ...) end
+function logger.debug(fmt, ...) print(2, fmt, ...) end
+function logger.info(fmt, ...) print(3, fmt, ...) end
+function logger.warn(fmt, ...) print(4, fmt, ...) end
+function logger.error(fmt, ...) print(5, fmt, ...) end
+function logger.fatal(fmt, ...) print(6, fmt, ...) end
+return logger

+ 94 - 0
project/lib/logger_debug.lua

@@ -0,0 +1,94 @@
+local skynet = require "skynet"
+local skynet_error = skynet.error
+local string_format = string.format
+local table_insert = table.insert
+local os_date = os.date
+local os_time = os.time
+
+
+local const_header = { "(T)|", "(D)|", "(I)|", "(W)|", "(E)|", "(F)|" }
+local header = const_header
+local function print(level, fmt, ...)
+  skynet_error(
+      os_date("[%m/%d %X]", os_time()),
+      header[level],
+      string_format(fmt, ...))
+end
+
+local logger_debug = {}
+function logger_debug.label(name)
+  if name then
+    header = {}
+    for _, v in ipairs(const_header) do
+      table_insert(header, v..name)
+    end
+  else
+    header = const_header
+  end
+end
+
+local function auto_print(level,fmt)
+	local strs_tab = {}
+	local temp_tb = {}
+	local function Save(Obj, floors)
+		if type(Obj) == "number" or type(Obj) == "string" or type(Obj) == "boolean" then
+			if type(Obj) == "boolean" then
+				Obj = Obj and "bool:true" or "false"
+			end
+			table_insert(strs_tab,Obj)
+			if floors == 1 then
+				table_insert(strs_tab,",")
+			end
+			-- table_insert(strs_tab,",\n")
+		elseif type(Obj) == type({}) then
+			if not temp_tb[Obj] then
+				temp_tb[Obj] = 1
+				local beg_Blank = ""
+				local end_Blank = ""
+				for i = 1, floors do
+					beg_Blank = beg_Blank .. "   "
+				end
+				for i = 1, floors-1 do
+					end_Blank = end_Blank .. "   "
+				end
+				table_insert(strs_tab,"{\n")
+				for k,v in pairs(Obj) do
+					if tostring(k) ~= "" and v ~= Obj then
+						table_insert(strs_tab,beg_Blank)
+						table_insert(strs_tab,"[\"")
+						table_insert(strs_tab,tostring(k))
+						table_insert(strs_tab,"\"] = ")
+						Save(v, floors + 1)
+						table_insert(strs_tab,"\n")
+					end
+				end
+				table_insert(strs_tab, end_Blank)
+				table_insert(strs_tab, "}")
+				if floors ~= 1 then
+					table_insert(strs_tab, ",")
+				end
+			end
+		else
+			table_insert(strs_tab,'\"')
+			table_insert(strs_tab,type(Obj))
+			table_insert(strs_tab,'\",')
+		end
+	end
+	-- 调用 Save 方法进行日志的记录
+	Save(fmt, 1)
+
+	local strs = table.concat( strs_tab )
+	strs = type(fmt)..":"..strs
+	print(level,strs)
+end
+
+local function _logger_debug_interface(fmt, ...)
+  	auto_print(1,fmt)
+end
+function logger_debug.look(fmt, ...) 
+	-- if true then
+		-- return --线上不开放
+	-- end
+	_logger_debug_interface(fmt, ...) 
+end
+return logger_debug

+ 87 - 0
project/lib/pbSproto.lua

@@ -0,0 +1,87 @@
+local protobuf = require "protobuf" --引入文件protobuf.lua
+local logger = require "logger"
+local stringify = require "stringify"
+local skynet = require "skynet"
+
+local logger_trace = logger.trace
+local logger_warn = logger.warn
+protobuf.register_file "../src/project/proto/game.pb"
+
+
+local packname = "game." -- 包名
+local enumname = "game.msg_cmd" --枚举名字
+
+local Sproto = {}
+local tmap = {}
+
+local function charToBinary(char)
+    local binaryStr = ""
+    local ascii = string.byte(char)
+    for i = 7, 0, -1 do
+        local bit = math.floor(ascii / 2^i) % 2
+        binaryStr = binaryStr .. bit
+    end
+    return binaryStr
+end
+
+-- 定义函数将字符串转换为二进制表示
+local function stringToBinary(str)
+    local binaryStr = ""
+    for i = 1, #str do
+        binaryStr = binaryStr .. charToBinary(str:sub(i, i)) .. " "
+    end
+    return binaryStr
+end
+
+Sproto.enum_id = function (name)
+    local ret = protobuf.enum_id(enumname, "cmd_"..name)
+    if not ret then
+        logger_trace("not enum:cmd_"..name)
+    end
+    return ret
+end
+
+Sproto.decode = function(msg, sz)
+    if sz then
+        msg = skynet.tostring(msg, sz)
+		skynet.trash(msg,sz)
+    end
+    local e_id = string.unpack(">I4", msg)
+    if not e_id then
+        logger_trace("Sproto.decode not e_id")
+        return 
+    end
+    
+    local name = tmap[e_id]
+    if not name then
+        logger_trace("Sproto.decode not name, e_id:"..e_id)
+        return nil, nil 
+    end
+    local str = string.sub(msg, 5, #msg)
+    local ret = protobuf.decode(packname..name, str)
+    logger.test("protobuf.decode:%s, size:%d, args:%s",name, #msg, stringify(ret or {}))
+    local function fun(args)
+        return Sproto.encode(name.."_rsp", args)
+    end
+    return "REQUEST", name, ret, fun
+end
+
+Sproto.encode = function (name, args, ...)
+    logger.test("Sproto.encode: name:%s, args:%s", name, stringify(args or {}))
+    local e_id = Sproto.enum_id(name)
+    local ret = protobuf.encode(packname..name, args)
+    if ret and e_id then
+        return string.pack(">I4", e_id)..ret
+    end
+    return ret
+end
+
+Sproto.register_msg = function(name)
+    local e_id = Sproto.enum_id(name)
+    if e_id then
+        tmap[e_id] = name
+    else
+        logger_warn("=========register_msg err, name:%s =========:", name)
+    end
+end
+return Sproto

+ 34 - 0
project/lib/pipeline.lua

@@ -0,0 +1,34 @@
+-- 优化 redis 的管线操作,防止出现请求过多引发的操作失败
+local function pipeline(page_size)
+  local table_insert = table.insert
+  page_size = page_size or 1024
+  assert(page_size > 0)
+
+  local cache = {}
+  local page = {}
+  local function add(...)
+    table_insert(page, { ... })
+    if #page == page_size then
+      table_insert(cache, page)
+      page = {}
+    end
+  end
+
+  local function execute(redis, resp)
+    if #page > 0 then
+      table_insert(cache, page)
+      page = {}
+    end
+    if #cache > 0 then
+      local old = cache
+      cache = {}
+      for _, v in ipairs(old) do
+        redis:pipeline(v, resp)
+      end
+    end
+    return resp
+  end
+  return { add=add, execute=execute }
+end
+
+return pipeline

File diff suppressed because it is too large
+ 2955 - 0
project/lib/sha2.lua


+ 77 - 0
project/lib/stringify.lua

@@ -0,0 +1,77 @@
+local table_concat = table.concat
+local table_insert = table.insert
+local string_rep = string.rep
+
+-- return function(root)
+--   local cache = { [root] = "." }
+--   local function dump(t, space, name)
+--     local stack = {}
+--     for k, v in pairs(t) do
+--       local key = tostring(k)
+--       if cache[v] then
+--         table_insert(stack,"+" .. key .. " {" .. cache[v].."}")
+--       elseif type(v) == "table" then
+--         local new_key = name .. "." .. key
+--         local new_space = space .. (next(t,k) and "|" or " " ) .. string_rep(" ",#key)
+--         cache[v] = new_key
+--         table_insert(stack, "+" .. key .. dump(v, new_space, new_key))
+--       else
+--         table_insert(stack,"+" .. key .. " [" .. tostring(v).."]")
+--       end
+--     end
+--     return table_concat(stack, "\n"..space)
+--   end
+--   return dump(root, "", "")
+-- end
+
+function dump(tbl)
+    assert(type(tbl) == "table")
+    local str = ""
+    local tbl_record = {}
+    local function dump_table(t, space, toptable)
+        str = str .. ("{\n")
+        local keys = {}
+        for k in pairs(t) do
+            table.insert(keys, k)
+        end
+        -- table.sort(keys)
+        tbl_record[t] = true
+        for _,k in ipairs(keys) do
+            local v = rawget(t, k)
+            local tk = type(k)
+            if tk == "number" then
+                str = str .. space .. "    [" .. k .. "] = "
+            elseif tk == "string" then
+                str = str .. space .. "    " .. k .. " = "
+            else
+                str = str .. "[UNKNOWN: " .. tostring(k) .. "] = "
+            end
+
+            local tv = type(v)
+            if tv == "number" then
+                str = str .. v .. ",\n"
+            elseif tv == "string" then
+                if v:find('"') then
+                    str = str .. "[=[" .. v .. "]=],\n"
+                else
+                    str = str .. '"' .. v .. '",\n'
+                end
+            elseif tv == "boolean" then
+                str = str .. (v and "true" or "false") .. ",\n"
+            elseif tv == "table" then
+                if tbl_record[v] then
+                    str = str .. "[RECURSIVE TABLE],\n"
+                else
+                    dump_table(v, space .. "    ")
+                end
+            else
+                str = str .. "[UNKNOWN: " .. tostring(v) .. "],\n"
+            end
+        end
+        str = str .. space .. "}" .. (toptable and "" or ",") .. "\n"
+    end
+    dump_table(tbl, "", true)
+    return str
+end
+
+return dump

+ 56 - 0
project/lib/strtable.lua

@@ -0,0 +1,56 @@
+-- 字符串转变为 table表
+local strtable = {}
+function strtable.StrToTable(str)
+    -- if str == nil or type(str) ~= "string" then
+    if str == nil then
+        return
+    end
+    local z = {load("return " .. str)}
+    return z
+end
+-- table表转换为str
+
+function strtable.ToStringEx(value)
+    if type(value)=='table' then
+        return strtable.TableToStr(value)
+    elseif type(value)=='string' then
+        return "\""..value.."\""
+    else
+        return tostring(value)
+    end
+end
+
+--使用的时候是这个
+function strtable.TableToStr(t)
+    if t == nil then return "" end
+    if type(t) ~= 'table' then return t end
+    local retstr= "{"
+
+    local i = 1
+    for key,value in pairs(t) do
+        local signal = ","
+        if i==1 then
+            signal = ""
+        end
+
+        if key == i then
+            retstr = retstr..signal..strtable.ToStringEx(value)
+        else
+            if type(key)=='number' or type(key) == 'string' then
+                retstr = retstr..signal.."["..strtable.ToStringEx(key).."]="..strtable.ToStringEx(value)
+            else
+                if type(key)=='userdata' then
+                    retstr = retstr..signal.."*s"..TableToStr(getmetatable(key)).."*e".."="..strtable.ToStringEx(value)
+                else
+                    retstr = retstr..signal..key.."="..strtable.ToStringEx(value)
+                end
+            end
+        end
+
+        i = i+1
+    end
+
+    retstr = retstr.."}"
+    return retstr
+end
+return strtable

+ 13 - 0
project/lib/timer.lua

@@ -0,0 +1,13 @@
+local skynet = require "skynet"
+local function timer(ti, listener)
+  local delete = function()
+    listener = nil
+  end
+  skynet.timeout(ti, function()
+    if listener then
+      listener()
+    end
+  end)
+  return delete
+end
+return timer

+ 289 - 0
project/lib/util.lua

@@ -0,0 +1,289 @@
+local c = require "util.core"
+local os_time = os.time
+local os_date = os.date
+local math_floor = math.floor
+local logger   = require "logger"
+local util = {}
+
+local oneday = 24*3600
+
+---------------------------- Maths ----------------------------
+function util.max(a, b)
+  return a > b and a or b
+end
+function util.min(a, b)
+  return a < b and a or b
+end
+function util.clamp(a, low, up)
+  assert(up >= low)
+  if a < low then
+    return low  -- min
+  elseif a > up then
+    return up   -- max
+  else
+    return a    -- original value
+  end
+end
+
+---------------------------- Time ----------------------------
+function util.timezone()
+  return c.timezone()
+end
+function util.today(ti, sec)
+  local tz = c.timezone() * 3600
+  return ((ti or os_time()) + tz) // 86400 * 86400 - tz + (sec or 0)
+end
+function util.gettime()
+  return c.gettime()
+end
+function util.gettimeofday()
+  return c.gettimeofday()
+end
+function util.nextday(ti)
+  return util.today(ti) + 86400
+end
+function util.week(ti)
+  local w = tonumber(os_date("%w", ti))
+  if w == 0 then w = 7 end      -- 星期转换结果为 0
+  return w
+end
+function util.hours(ti)
+  return tonumber(os_date("%H", ti or os_time()))
+end
+function util.day(ti)
+  return tonumber(os_date("%d", ti or os_time()))
+end
+function util.alarm(ti) -- 整点时间
+  -- 获取下次刷新排行榜时间, 活动刷新时间
+  local temp_tm = os.date("*t", ti or os_time())
+  temp_tm.min = 0
+  temp_tm.sec = 0
+  return os_time(temp_tm)  -- 刷新战斗力时间
+end
+
+
+-- time travel
+--[[
+    获取以 hour点 minute 作为跨天点的 时间间隔天数
+    start_time : 开始的时间点
+    end_time   : 结束的时间点
+
+    hour       : 跨天的小时点
+    minute     : 跨天的分钟点
+
+    not_start  : true start_time 那一天的第一个跨点不计算 (例如:4点跨天 如果start_time 是0~4点,那么0~下一天4点是第一天)
+               : false  start_time 那一天的第一个跨点计算 (例如:4点跨天 如果start_time 是0~4点,那么4~下一天4点是第二天)
+]]
+local function get_time_clock(give_time,hour,minute)
+    -- 格式化时间为当天hour:minute
+    -- local now_tm = os_date("*t", give_time)
+    -- now_tm.hour = hour
+    -- now_tm.min = minute
+    -- now_tm.sec = 0
+    local sec = hour * 3600 + minute * 60
+    return util.today(give_time, sec)
+end
+function util.get_time_travel(start_time,end_time,hour,minute,not_start)
+
+    assert(start_time)
+    assert(end_time)
+    assert(start_time <= end_time)
+    hour = hour or 0
+    minute = minute or 0
+    not_start = not_start or false
+
+
+
+    local start_tc = get_time_clock(start_time,hour,minute)
+    local end_tc = get_time_clock(end_time,hour,minute)
+
+    local start_zero = start_tc - (hour * 3600 + minute * 60)
+    local end_zero =start_tc - (hour * 3600 + minute * 60)
+
+    local travel_days = 0
+    if start_time < start_tc then
+
+        if not_start then
+            -- 第一个跨点不计算 不用修正
+        else
+            -- 第一个跨点计算  修正到上一天
+            start_tc = start_tc - oneday
+        end
+    end
+    if (end_time < end_tc) and not (not_start and start_zero == end_zero)  then
+        end_tc = end_tc - oneday
+    end
+
+    return math_floor((end_tc - start_tc)/86400 + 1),end_tc
+
+    -- local start_tc = get_time_clock(start_time,hour,minute)
+    -- local end_tc = get_time_clock(end_time,hour,minute)
+    -- return math_floor((end_time - start_tc)/oneday) + 1,end_tc
+
+end
+
+
+---------------------------- Random ----------------------------
+local dice = {}
+local mt = { __index = dice }
+function util.newrandom(...)
+  local self = { __cobj = assert(c.newrandom(...)) }
+	return setmetatable(self, mt)
+end
+function dice:roll(...)
+  return c.rand(self.__cobj, ...)
+end
+function dice:srand(seed)
+  c.srand(self.__cobj, seed)
+end
+function dice:peekseed()
+  return c.peekseed(self.__cobj)
+end
+
+local holdrand = 1
+function util.peekseed()
+  return holdrand
+end
+function util.rand(...)
+  local r, s = c.random(...)
+  holdrand = s
+  return r
+end
+function util.srand(seed)
+  holdrand = seed
+  c.srandom(holdrand)
+end
+
+---------------------------- UUID ----------------------------
+function util.uuid()
+  return c.uuid()
+end
+
+---------------------------- Hash ----------------------------
+function util.hashcode(str)
+  return c.hashcode(str)
+end
+
+---------------------------- Table ----------------------------
+function util.length(t)
+  local n = 0
+  for _, _ in pairs(t) do
+    n = n + 1
+  end
+  return n
+end
+
+-- table 序列化
+function util.tablequeue(obj)
+  local temp = {}
+  if not obj then
+    return temp
+  end
+  for _, v in pairs(obj) do
+    table.insert(temp, v)
+  end
+  return temp
+end
+
+-- Initialize random seed for function of the util.rand
+util.srand(os_time())
+util.rand() -- discard first value to avoid undesirable correlations
+
+function util.optimize_array(a)
+  if a and next(a) then
+    return a
+  end
+end
+
+-- TODO: 计算指定时间的周一
+function util.week_monday(time)
+  time = util.today(time)
+  local w = util.week(time)
+  -- if w == 0 then w = 7 end      -- 星期转换结果为 0
+  local rettm = time - (w - 1)*86400
+  logger.trace(" 周一的时间节点 %s, 开服时间节点 %s",
+    os.date("%Y-%m-%d %H:%M:%S",tonumber(rettm)),
+    os.date("%Y-%m-%d %H:%M:%S",tonumber(time))
+  )
+  return rettm
+end
+
+-- TODO: 计算指定时间月份的 1号
+function util.month_one(time)
+  time = util.today(time)
+  local d = util.day(time)
+  local rettm = time - (d - 1)*86400
+  logger.trace(" 1 号的时间节点 %s, 开服时间节点 %s",
+    os.date("%Y-%m-%d %H:%M:%S",tonumber(rettm)),
+    os.date("%Y-%m-%d %H:%M:%S",tonumber(time))
+  )
+  return rettm
+end
+
+-- TODO: 计算开服后指定多少个月的 1号
+function util.next_month_one(time, many)
+  many = (many or 1) - 1
+  local one = util.month_one(time)
+  local onetable = os.date("*t", one)
+  local onetable_month = onetable.month + many
+  local add_year = 0
+  local set_month = onetable_month
+  if onetable_month > 12 then
+    add_year = math.modf(onetable_month/12)
+    set_month = math.fmod( onetable_month, 12 )
+  end
+  onetable.year = onetable.year+add_year
+  onetable.month = set_month
+  local newtime = os.time(onetable)
+  logger.trace(" ### 指定时间的1号 %s, 计算出来的1号 %s, many:%s",
+    os.date("%Y-%m-%d %H:%M:%S",tonumber(one)), os.date("%Y-%m-%d %H:%M:%S",tonumber(newtime)),many)
+  return newtime
+end
+
+
+function util.split(str)
+  local chunks = {str:match("(%d+)%/(%d+)%/(%d+)%/(%d+)%/(%d+)%/(%d+)")}
+  assert(#chunks == 6)
+  local year, month, day, hour, min, sec = table.unpack(chunks)
+  return os.time({ year=year, month=month, day=day, hour=hour, min=min, sec=sec })
+end
+
+
+-- function util.goods_merge(list)
+--     list = list or {}
+--     local ok, value = next(list)
+--     if not ok then
+--         return list
+--     end
+--     local t = {}
+--     if type(value) == "table" then
+--         for _, v in ipairs(list) do
+--             local id = v.id
+--             local num  = v.num
+--             t[id] = t[id] or {
+--                 id = id,
+--                 num = 0
+--             } 
+--             t[id].num = t[id].num +  num
+--         end
+--     else
+--         for i=1, #list, 2 do
+--             local id = list[i]
+--             local num = list[i+1]
+--             if num and num > 0 then
+--                 t[id] = t[id] or {
+--                     id = id,
+--                     num = 0
+--                 }
+--                 t[id].num = t[id].num +  num
+--             end
+--         end
+--     end
+
+--     return util.tablequeue(t)
+-- end
+
+
+
+return util
+

+ 234 - 0
project/model/activity/act_rank.lua

@@ -0,0 +1,234 @@
+-- 活动排行
+local logger = require "logger"
+local stringify = require "stringify"
+local asset = require "model.asset"
+local util = require "util"
+local activitytimemod
+local act_rankinglist
+local module_fun = require "model.module_fun"
+local skynet = require "skynet"
+local adventure
+local embattle
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local function func_ret(fname, character, args)
+    if not args.id then
+        return {errno = 1} -- 参数异常
+    end
+    local state = activitytimemod.get_state(character, args.id)
+    if state == STATE_CLOSE then
+        return {errno = STD_ERR.COMMON_ACT_NOT_OPEN} -- 活动未开启
+    end
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+local function get_conf(character, module_id)
+    return activitytimemod.get_conf(character, module_id)
+end
+
+local function get_info(character, module_id)
+    return activitytimemod.get_info(character, module_id)
+end
+
+local function can_upload(character, module_id)
+    act_rankinglist = act_rankinglist or skynet.localname(".rankinglist")
+    local state = activitytimemod.get_state(character, module_id)
+    if state == STATE_OPEN then
+        -- 检查是否再上传时间内
+        local conf = get_conf(character, module_id)
+        if not conf then
+            return 
+        end
+        if conf.parameter3[1] > 0 then
+            local info = get_info(character, module_id) 
+            if info and os.time() + conf.parameter3[1]*HOUR_SEC < info.endtime then
+                return true
+            end
+        end
+    end
+    return false
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据 在这里把数据初始化好
+function MODULE.parse(character)
+end
+
+function MODULE.open(character, ainfo)
+    THIS.syn(character, ainfo.activity_type)
+end
+
+function MODULE.close(character, ainfo)
+end
+
+function MODULE.continue_open(character, ainfo)
+
+end
+
+function THIS.upload(character, module_id, num, num2, temp)
+    local info = get_info(character, module_id) 
+    if not info then
+        return 
+    end
+    local data = temp or {}
+    data.num = num
+    data.num2 = num2 or os.time()
+    data.role = module_fun.simple_role(character)
+    skynet.call(act_rankinglist, "lua", "upload", info.sid, data)
+end
+
+function THIS.syn(character, module_id)
+    if module_id == MODULE_ID_SIMPLE_BATTLE_RANK then
+        adventure = adventure or require "model.adventure"
+        local data = adventure.get_simple_pass_data(character)
+        if data.id > 0 then
+            THIS.upload(character, MODULE_ID_SIMPLE_BATTLE_RANK, data.id, data.tm)
+        end
+    end
+
+    if module_id == MODULE_ID_ELITE_BATTLE_RANK then
+        adventure = adventure or require "model.adventure"
+        local data = adventure.get_elite_pass_data(character)
+        if data.id > 0 then
+            THIS.upload(character, MODULE_ID_ELITE_BATTLE_RANK, data.id, data.tm)
+        end
+    end
+
+    if module_id == MODULE_ID_POWER_RANK then
+        embattle = embattle or require "model.embattle"
+        local power = embattle.battle_power(character)
+        if power > 0 then
+            THIS.upload(character, MODULE_ID_POWER_RANK, power, 0)
+        end
+    end
+
+    if module_id == MODULE_ID_HERO_RANK then
+        embattle = embattle or require "model.embattle"
+        local power, info = embattle.max_power_hero(character)
+        if power > 0 then
+            THIS.upload(character, MODULE_ID_POWER_RANK, power, 0, {
+                id = info.id,
+                lv = info.lv
+            })
+        end
+    end
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+    character.monitor("simple_battle_pass", function(_, id) 
+        local sign = can_upload(character, MODULE_ID_SIMPLE_BATTLE_RANK)
+        if sign then
+            THIS.upload(character, MODULE_ID_SIMPLE_BATTLE_RANK, id)
+        end
+    end)
+
+    character.monitor("elite_battle_pass", function(_, id) 
+        local sign = can_upload(character, MODULE_ID_ELITE_BATTLE_RANK)
+        if sign then
+            THIS.upload(character, MODULE_ID_ELITE_BATTLE_RANK, id)
+        end
+    end)
+
+    character.monitor("power_update", function(_, power)
+        local sign = can_upload(character, MODULE_ID_POWER_RANK)
+        if sign then
+            THIS.upload(character, MODULE_ID_POWER_RANK, power, 0)
+        end
+    end)
+
+    -- character.monitor("syn_act_rank", function(_, id) 
+    --     THIS.syn(character,id)
+    -- end)
+
+    character.monitor("timer.minute", function(_, tm)
+        local sign = can_upload(character, MODULE_ID_HERO_RANK)
+        if sign then
+            THIS.syn(character,MODULE_ID_HERO_RANK)
+        end
+    end)
+
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    activitytimemod = require "model.activitytimemod"
+    act_rankinglist = skynet.localname(".act_rankinglist")
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+    adventure = adventure or require "model.adventure"
+    local data = adventure.get_elite_pass_data(character)
+    if data.id > 0 then
+        THIS.upload(character, MODULE_ID_ELITE_BATTLE_RANK, data.id, data.tm)
+    end
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+end
+
+function MODULE.get_red_point(character)
+    return
+end
+
+function THIS.act_rank_data(character, args)
+    local model_id = args.id
+    if not model_id then
+        return 1 -- 参数异常
+    end
+
+    local conf = get_conf(character, model_id)
+    if not conf then
+        return 2 -- 系统异常
+    end
+
+    local list = skynet.call(act_rankinglist, "lua", "get_ranking", conf.ID)
+
+    local ret = {
+        errno = 0,
+        list = {},
+    }
+
+    for k, v in ipairs(list) do
+        if k <= 100 then
+            ret.list[k] = v
+            ret.list[k].num2 = nil
+            ret.list[k].ranking = k
+        else
+            break
+        end
+    end
+    return 0, ret
+end
+
+function REQUEST.act_rank_data(character, args)
+    return func_ret("act_rank_data", character, args)
+end
+
+function CMD.syn_act_rank(character, id)
+    local sign = can_upload(character, id)
+    if sign then
+        THIS.syn(character,id)
+    end
+end
+
+return MODULE

+ 339 - 0
project/model/activitytimemod.lua

@@ -0,0 +1,339 @@
+--获取限时活动时间
+local skynet = require "skynet"
+local logger = require "logger"
+local stringify = require "stringify"
+local schema = require "model.schema"
+local asset = require "model.asset"
+local common_fun = require "model.common_fun"
+
+local REQUEST = {}
+local CMD = {}
+local activitytime = {}
+local activity = skynet.localname(".activitytime")
+
+local activitylist = {
+}
+
+local _M = schema.new('activitytimemod', {
+    activity_token = {              --所有活动的token
+    --[[
+        [activity_type] = {
+            state = STATE_CLOSE,
+            activity_type = 活动类型,
+            sid = 活动sid,
+            opentime = 活动开启时间,
+            endtime = 活动结束时间,
+            activity_type = 活动开启类型,
+            switch = 1 活动临时开关
+        }
+    ]]
+    },
+})
+
+local THIS = {}
+
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+
+
+function activitytime.list_request_interests() return REQUEST end
+function activitytime.list_command_interests() return CMD end
+function activitytime.parse(character) _M.load(character) end
+
+-- TODO: 处理活动状态切换
+--[[
+  activity_data_time: 开启活动数据
+]]
+local function jqui_activity(character, activity_data_time)
+    local d = _M.assert_get(character)
+    local player_activ = d.activity_token
+    local save_token = false
+
+    -- 通知活动更新
+    local function open_update(key, vey)
+        local ok, info_add = xpcall(activitytime.open, debug.traceback, character, key, vey)
+        if ok then
+            player_activ[key] = info_add
+            save_token = true
+            logger.test("activitytimemod open_update. atype:%s ok:%s, info_add:%s",key,ok, stringify(info_add))
+        else
+            logger.test("activitytimemod open_update err. atype:%s ok:%s, info_add:%s",key,ok, info_add)
+        end
+        return ok
+    end
+
+    -- 活动继开 不清除玩家老的数据
+    local function continue_open_update(key, vey)
+        local ok, info_add = xpcall(activitytime.continue_open, debug.traceback, character,key,vey)
+        if ok then
+            player_activ[key] = info_add
+            save_token = true
+            logger.test("activitytimemod continue_open_update           atype:%s ok:%s, info_add:%s",key,ok, stringify(info_add))
+        else
+            logger.test("activitytimemod continue_open_update    err       atype:%s ok:%s, info_add:%s",key,ok, info_add)
+        end
+        return ok
+    end
+
+    -- 活动关闭
+    local function close_update(key, vey)
+        local ok, str = xpcall(activitytime.close, debug.traceback,character, key, vey)
+        if ok then
+            player_activ[key].state = STATE_CLOSE
+            save_token = true
+            logger.test("activitytimemod close_update        atype:%s ok:%s.",key, ok)
+        else
+            logger.test("activitytimemod close_update  err      atype:%s ok:%s, str:%s",key, ok, str)
+        end
+        return ok
+    end
+
+    -- 已存在活动对比
+    local function compare_info(atype, ainfo, pinfo)
+        local ok
+        if pinfo.sid ~= ainfo.sid then          -- 活动id变化
+            -- 先通知关闭
+            if pinfo.state == STATE_OPEN then
+                ok = close_update(atype, pinfo)
+                if not ok then
+                    return ok
+                end
+            end
+            -- 再通知开启
+            ok = open_update(atype, ainfo)
+        else
+            if pinfo.state == STATE_CLOSE then
+                ok = continue_open_update(atype, ainfo)     -- 活动续开
+            else
+                ok = true
+            end
+        end
+        return ok
+    end
+
+    local err_type = {}                           -- 活动处理过程出现异常的活动
+    -- TODO: 活动处理主流程
+    for _, ainfo in pairs(activity_data_time) do
+        -- 玩家数据对比 新传入活动数据
+        local atype = ainfo.activity_type           -- 传入活动类型
+        local pinfo = player_activ[atype]
+        local ok
+        if not pinfo then             -- 新增活动
+            ok = open_update(atype,ainfo)
+        else                                        -- 老活动的处理
+            -- 对比玩家数据
+            ok = compare_info(atype, ainfo, pinfo)
+        end
+        if not ok then
+            err_type[atype] = atype
+        end
+    end
+
+    local del_close = {}
+    -- 对比玩家数据
+    for _, sinfo in pairs(d.activity_token) do
+        local stype = sinfo.activity_type
+        -- 玩家有得活动, 传入数据没有, 活动已经关闭
+        if not activity_data_time[stype] and sinfo.state ~= STATE_CLOSE then
+            logger.test(" == 关闭得活动 %s",stype)
+            if close_update(stype,sinfo) then       -- 通知活动关闭
+                del_close[stype] = sinfo
+            else
+                err_type[stype] = stype
+            end
+        end
+    end
+    logger.test(" -- 玩家对比传入数据后, 已经关闭得活动 %s", stringify(del_close))
+    if next(err_type or {}) then
+        logger.warn(" 活动处理异常的活动: %s", stringify(err_type))
+    end
+    -- 是否有活动发生改变
+    if save_token then
+        _M.persist(character)
+    end
+
+end
+
+function activitytime.launch(character)
+end
+
+local function project(character, finishtime_data)
+    if finishtime_data and next(finishtime_data) then
+        local finishtime_new = {}
+        for k, v in pairs(finishtime_data) do
+            local state = v.state
+            if activitylist[v.activity_type] and activitylist[v.activity_type].check_over then
+                if activitylist[v.activity_type].check_over(character) then
+                    state = STATE_OVER
+                end
+            end
+            logger.test("activitytimemod  --- 活动数据:%s,%s", k,stringify(v))
+            if v.switch == 1 then -- 关闭
+                local temp = {
+                    type = v.activity_type,
+                    state = state,
+                    opentime = v.opentime,
+                    endtime = v.endtime,
+                    name = v.name,
+                    sort = v.sort,
+                    icon = v.icon,
+                }
+                table.insert(finishtime_new,temp)
+            end
+        end
+        return finishtime_new
+    end
+end
+
+function activitytime.ready(character)
+    local activity_finishtime = skynet.call(activity, "lua", "getactivitytime")
+    jqui_activity(character, activity_finishtime)--将活动状态分发下去
+    local finishtime = project(character, activity_finishtime)
+    character.send("activity_time_list_nty",{list = finishtime})
+    local d = _M.assert_get(character)
+    logger.test("%s:ready, %s", "activitytime", stringify(d or {}))
+end
+
+function activitytime.saybye(character)
+end
+
+-- TODO: 规范数据处理
+local function set_activi_player(ainfo)
+    return {
+        state = ainfo.state,
+        activity_type = ainfo.activity_type,
+        sid = ainfo.sid,
+        opentime = ainfo.opentime,
+        endtime = ainfo.endtime,
+        open_type = ainfo.open_type,
+    }
+end
+
+-- TODO: 活动开启
+function activitytime.open(character,key,ainfo)
+    activitylist[key].open(character,ainfo)
+    return set_activi_player(ainfo)
+end
+
+-- TODO: 活动续开
+function activitytime.continue_open(character,key,ainfo)
+    if activitylist[key] and activitylist[key].continue_open then
+        activitylist[key].continue_open(character,ainfo)
+    end
+    return set_activi_player(ainfo)
+end
+
+function activitytime.close(character, key, pinfo)
+    activitylist[key].close(character, pinfo)
+end
+
+function CMD.update(character,finishtime)--活动开始
+    logger.test("activitytimemod 开始更新活动配置时间\n:%s",stringify(finishtime))
+    jqui_activity(character, finishtime)
+    character.send("activity_time_list_nty",{list = project(character, finishtime)})
+end
+
+function activitytime.get_conf(character, model_id)
+    local d = _M.assert_get(character)
+    local sid = d.activity_token[model_id] and d.activity_token[model_id].sid
+    if sid then
+        return asset.ActivityConfig_proto[sid]
+    end
+end
+
+function activitytime.get_state(character, model_id)
+    local d = _M.assert_get(character)
+    local sid = d.activity_token[model_id] and d.activity_token[model_id].sid
+    if sid then
+        return d.activity_token[model_id].state
+    end
+    return STATE_CLOSE
+end
+
+function activitytime.get_info(character, model_id)
+    local d = _M.assert_get(character)
+    local sid = d.activity_token[model_id] and d.activity_token[model_id].sid
+    return d.activity_token[model_id]
+end
+
+function THIS.activity_get_conf_list(character, args)
+    if not args.list then
+        return 1 -- 参数异常
+    end
+    local list = {}
+    local d = _M.assert_get(character)
+    for _, v in ipairs(args.list) do
+        local atype = tonumber(v)
+        if atype then
+            local data = d.activity_token[atype] 
+            logger.trace("data:"..stringify(data or {}))
+            if data and data.state == STATE_OPEN then
+                local conf = asset.ActivityConfig_proto[data.sid]
+                local temp = {
+                    type = atype,
+                    describe = conf.describe,
+                    parameter1 = conf.parameter1,
+                    parameter2 = conf.parameter2,
+                    parameter3 = conf.parameter3,
+                }
+                for _, award_id in ipairs(conf.reward)do
+                    if award_id <= 0 then
+                        break
+                    end
+                    local _, list = common_fun.get_award(award_id,"NewawardConfig_proto")                    
+                    if list then
+                        temp.reward_list  = temp.reward_list or {}
+                        table.insert(temp.reward_list, {
+                            id = award_id,
+                            list = list,
+                        })
+                    end
+                end
+
+                for _, gift_id in ipairs(conf.gift) do
+                    if gift_id <= 0 then
+                        break
+                    end
+                    local gift_conf = asset.GiftConfig_proto[gift_id]
+                    if gift_conf then
+                        temp.gift_list = temp.gift_list or {}
+                        table.insert(temp.gift_list, {
+                            ID = gift_conf.ID,
+                            library = gift_conf.library,
+                            sort = gift_conf.sort,
+                            spendtype = gift_conf.spendtype,
+                            spend = gift_conf.spend,
+                            num = gift_conf.num,
+                            daylimit = gift_conf.daylimit,
+                            weeklimit = gift_conf.weeklimit,
+                            video = gift_conf.video,
+                            icon = gift_conf.icon,
+                            parameter = gift_conf.parameter,
+                        })
+                    end
+                end
+                table.insert(list, temp)
+            end
+        end
+    end
+    return 0, {list = list}
+end
+
+function REQUEST.activity_get_conf_list(character, args)
+    return func_ret("activity_get_conf_list", character, args)
+end
+
+return activitytime

+ 882 - 0
project/model/adventure.lua

@@ -0,0 +1,882 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local reward = require "model.reward"
+local payment = require "model.payment"
+local asset = require "model.asset"
+local common_fun = require "model.common_fun"
+local module_fun = require "model.module_fun"
+local skynet = require "skynet"
+local stringify = require "stringify"
+local rankinglist
+local supreme_card
+local talent
+local hero
+
+
+local module_name = "adventure"
+local LAYER_SYS_NUM = 100 -- 波次系数(关卡*100+1为本关第一波)
+local NIL_RET = -1 -- 为nil时返回值
+
+local NULL_ADV = -1
+local SIMPLE_ADV = 0
+local ELITE_ADV = 1
+
+local MIN_ELITE_ID = 101
+
+local math_floor = math.floor
+local table_insert = table.insert
+local math_min = math.min
+
+local _M = schema.new(module_name, {
+    pass = {
+        -- [1] = {
+        --     id = 关卡;
+        --     tm = 时间
+        --     award = {123,456}
+        --     award_list = {}
+        -- }
+    },
+
+    cost = nil,
+    type = nil,
+    layer = nil,
+    trialid = 0,    -- 试玩英雄配置id
+    trialnum = 0,   -- 试玩英雄次数
+    btrial = false, -- 是否开始试玩
+})
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS= {}
+
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据
+function MODULE.parse(character)
+    local d = _M.load(character)
+    d.pass = d.pass or {}
+    if not d.pass[SIMPLE_ADV] then d.pass[SIMPLE_ADV] = {id = 0, tm = 0, award = {}} end
+    if not d.pass[ELITE_ADV] then d.pass[ELITE_ADV] = {id = 0, tm = 0, award = {}} end
+    d.trialid = d.trialid or 0
+    d.trialnum = d.trialnum or 0
+end
+
+local function sysnc_adventure(character)
+    -- if character.ban_rank ~= 0 then
+    --     return 
+    -- end
+    -- local d = _M.assert_get(character)
+    -- if d.pass[SIMPLE_ADV].id <=0 then
+    --     return
+    -- end
+    -- local data = {
+    --     num = d.pass[SIMPLE_ADV].id,
+    --     num2 = d.pass[SIMPLE_ADV].tm,
+    --     role = module_fun.simple_role(character),
+    -- }
+    -- skynet.call(rankinglist, "lua", "update", RANKING_ADV, data)
+end
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+    local d = _M.assert_get(character)
+    character.monitor("simple_battle_pass", function(_, id)
+        sysnc_adventure(character)
+    end)
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    local d = _M.assert_get(character)
+    local u = _M.assert_runtime(character)
+    rankinglist = skynet.localname(".rankinglist")
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+    local d = _M.assert_get(character)
+    sysnc_adventure(character)
+        logger.test("%s: ready, d:%s", module_name, stringify(d))
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+    sysnc_adventure(character)
+end
+
+function MODULE.gm_set_simple_adventure(character, id)
+    local d = _M.assert_get(character) or {}
+    if d.pass[SIMPLE_ADV].id < id then
+        character.dispatch("simple_battle_pass", id)
+    end
+    d.pass[SIMPLE_ADV].id = id
+    d.pass[SIMPLE_ADV].tm = os.time()
+    _M.persist(character)
+end
+
+function MODULE.gm_set_elite_adventure(character, id)
+    local d = _M.assert_get(character) or {}
+    if asset.EliteInfoConfig_proto[id] then
+        if d.pass[ELITE_ADV].id < id then
+            character.dispatch("elite_battle_pass", id)
+        end
+        d.pass[ELITE_ADV].id = id
+        d.pass[ELITE_ADV].tm = os.time()
+        _M.persist(character)
+        return true
+    end
+    return false
+end
+
+function MODULE.get_simple_pass(character)
+    local d = _M.assert_get(character) or {}
+    return d.pass[SIMPLE_ADV].id or 0
+end
+
+function MODULE.get_simple_pass_data(character)
+    local d = _M.assert_get(character) or {}
+    return d.pass[SIMPLE_ADV]
+end
+
+function MODULE.get_elite_pass(character)
+    local d = _M.assert_get(character) or {}
+    return d.pass[ELITE_ADV].id or 0
+end
+
+function MODULE.get_elite_pass_data(character)
+    local d = _M.assert_get(character) or {}
+    return d.pass[ELITE_ADV]
+end
+
+local function touch_trail(character, ttype, id)
+    -- local d = _M.assert_get(character)
+    -- if d.trialid and d.trialid > 0 then
+    --     local conf = asset.TrialConfig_proto[d.trialid]
+    --     if conf and id >= conf.parameter or id < conf.endcondition then
+    --         return 
+    --     end
+    -- end
+    -- for _, conf in pairs(asset.TrialConfig_proto) do
+    --     if id >= conf.parameter and id < conf.endcondition then
+    --         if conf.integral == ttype then
+    --             d.trialid = conf.ID
+    --             d.trialnum = 0 
+    --             -- d.btrial = ttype == 1 and true or false
+    --             _M.persist(character)
+    --             return
+    --         else
+    --             return
+    --         end
+    --     end
+    -- end
+end
+
+function THIS.get_conf_list(adv_type)
+    if adv_type == SIMPLE_ADV then return assert(asset.StageInfoConfig_proto, "StageInfoConfig_proto") 
+    elseif adv_type == ELITE_ADV then return assert(asset.EliteInfoConfig_proto, "EliteInfoConfig_proto") 
+    end
+end
+
+function THIS.get_layer_conf_list(adv_type)
+    if adv_type == SIMPLE_ADV then return assert(asset.StageConfig_proto, "StageConfig_proto") 
+    elseif adv_type == ELITE_ADV then return assert(asset.EliteStageConfig_proto, "EliteStageConfig_proto") 
+    end
+end
+
+
+function THIS.adventure_layer(character, args)
+    local d = _M.assert_get(character)
+    if not d.layer or not d.type then
+        return STD_ERR.ADVENTURE_NOT_BATTLE
+    end
+    
+    local id = math_floor(d.layer/LAYER_SYS_NUM)
+    if id ~= math_floor(args.layer/LAYER_SYS_NUM) then
+        return STD_ERR.COMMON_PARM_ERR
+    end
+
+    local conf_list = THIS.get_layer_conf_list(d.type)
+    if not conf_list or not conf_list[args.layer] then
+        return STD_ERR.COMMON_SYS_ERR
+    end
+
+    local conf = conf_list[args.layer]
+    local max_list = {}
+    local all_num = 0
+    if conf then
+        all_num = all_num + #conf.roles
+        for _, monster_id in ipairs(conf.roles) do
+            local monster_conf = asset.MonsterConfig_proto[monster_id]
+            if monster_conf then
+                max_list[monster_conf.type] = (max_list[monster_conf.type] or 0) + 1
+            end
+        end 
+    end
+
+    local kill_boss = math.min(max_list[MONESTER_TYPE_BOSS] or 0, args.monster_num or 0)
+    local all_num = math.min(all_num, (args.monster_num or 0) + (args.elite_num or 0) + (args.boss_num or 0))
+
+    if args.layer > d.layer then
+        d.layer = args.layer
+        _M.persist(character)
+        if all_num > 0 then
+            character.dispatch("kill_monster", all_num)
+        end
+        if kill_boss > 0 then
+            character.dispatch("kill_boss", kill_boss)
+        end
+    end
+
+    if d.type == SIMPLE_ADV then
+        local data = d.pass[SIMPLE_ADV]
+        if id > data.id  then
+            data.progress = math.max(d.layer%LAYER_SYS_NUM, data.progress or 0)
+            _M.persist(character)
+        end
+    end
+    return 0
+end
+
+function THIS.adventure_start(character, args)
+    local d = _M.assert_get(character)
+    local id  = args.id
+    local key = args.type or 0
+
+    -- 在冒险中
+    -- if d.layer and d.type then
+    --     return STD_ERR.ADVENTURE_IN_BATTLE -- 有冒险在进行
+    -- end
+
+    if not id or not key then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local conf_list = THIS.get_conf_list(key)
+    if not conf_list or not conf_list[id] then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    if not d.pass and not d.pass[key] then
+        return STD_ERR.COMMON_SYS_ERR -- 系统异常
+    end
+
+    local data = d.pass[key]
+
+    if conf_list[id].front > data.id then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    if key == ELITE_ADV then
+        if conf_list[id].condition > d.pass[SIMPLE_ADV].id then
+            return STD_ERR.COMMON_PARM_ERR -- 参数异常
+        end
+    end
+
+    local conf_list = assert(asset.DataConfig_proto, "DataConfig_proto")
+    local conf = conf_list[1]
+    if not conf  then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    local costnum = conf.data5[1]
+    if not costnum or costnum < 0 then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+
+    if key == SIMPLE_ADV then
+        local detail = {{
+            id = CURRENCY_ID_STM, 
+            num = costnum
+        }}
+        local receipt = {
+            module="adventure",
+            brief="闯关开始",
+            context="闯关开始:"..id,
+            notify={
+                flags="adventure_start"
+            },
+            detail=detail
+        }
+        local errno = payment(character, receipt)
+        if errno ~=0 then 
+            return errno
+        end
+    end
+
+    d.layer = id * LAYER_SYS_NUM + 1;
+    d.type = key
+    d.cost = 0;
+    _M.persist(character)
+    if key == SIMPLE_ADV then
+        touch_trail(character, 1, id)
+        character.dispatch("simple_battle", id)
+    elseif key == ELITE_ADV then
+        character.dispatch("elite_battle", id)
+    end
+    -- if key == SIMPLE_ADV and d.trialid > 0 then
+    --     local conf = asset.TrialConfig_proto[d.trialid]
+    --     if conf and id >= conf.parameter and id <= conf.endcondition and d.trialnum < conf.rank then
+    --         return 0, {
+    --             trial_id = d.trialid,
+    --             trial_num = d.trialnum,
+    --             btrial = d.btrial
+    --         }
+    --     end
+    -- end
+    return 0
+end
+
+function THIS.adventure_end(character, args)
+    local d = _M.assert_get(character) or {}
+    if not d.layer or not d.type or d.layer <= 0 then
+        return STD_ERR.ADVENTURE_NOT_BATTLE -- 没有进行中的冒险
+    end
+    local bwin = args.win
+    local id = math_floor(d.layer/LAYER_SYS_NUM)
+    if id <= 0 then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local key = d.type or 0
+    if key ~= ELITE_ADV and key ~= SIMPLE_ADV then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local award_list
+    local start_id = id*LAYER_SYS_NUM + 1
+    -- 保护下每关最多99波
+    local end_id = math_min(d.layer-1, (id+1)*LAYER_SYS_NUM-1)
+    if not d.layer then
+        return STD_ERR.ADVENTURE_NOT_BATTLE -- 没有进行中的冒险
+    end
+
+    local conf_list = THIS.get_layer_conf_list(d.type)
+    if not conf_list then
+        return STD_ERR.COMMON_SYS_ERR
+    end
+
+    local data = d.pass[key]
+
+    if bwin then
+        end_id = (id+1)*LAYER_SYS_NUM-1
+    else
+        if key == SIMPLE_ADV then
+            touch_trail(character, 2, id)
+        end
+    end
+    -- 简单关卡才有奖励
+    if key == SIMPLE_ADV then
+        local errno
+        errno, award_list = THIS.simple_award(character, start_id, end_id)
+        if errno ~= 0 then
+            return errno
+        end
+
+        if award_list and next(award_list) then
+            local desc = {
+                module="adventure",
+                brief="闯关奖励",
+                context= string.format("闯关奖励%d", id),
+                mailgen={subject=2, body=""},
+                notify={
+                    flags="adventure_end"
+                },
+                detail=award_list
+            }
+            reward(character, desc)
+        end
+    end
+
+    local max_list = {}
+    local all_num = 0
+    for confid = start_id, end_id do
+        local conf = conf_list[confid]
+        if conf then
+            all_num = all_num + #conf.roles
+            for _, monster_id in ipairs(conf.roles) do
+                local monster_conf = asset.MonsterConfig_proto[monster_id]
+                if monster_conf then
+                    max_list[monster_conf.type] = (max_list[monster_conf.type] or 0) + 1
+                end
+            end 
+        end
+    end
+
+    if id > data.id  then
+        if bwin then
+            d.pass[key].id = id
+            d.pass[key].tm = os.time()
+            d.pass[key].progress = 0
+            if key == SIMPLE_ADV then
+                character.dispatch("simple_battle_pass", id)
+                local ctx = _M.assert_runtime(character)
+                ctx.ad_award = award_list
+                ctx.id = id
+                if d.btrial then
+                    d.trialnum = d.trialnum + 1
+                end
+            elseif key == ELITE_ADV then
+                character.dispatch("elite_battle_pass", id)
+            end
+        else
+            d.pass[key].progress = math.max(d.layer%LAYER_SYS_NUM, d.pass[key].progress or 0)
+        end
+    end
+
+    local kill_boss = math.min(max_list[MONESTER_TYPE_BOSS] or 0, args.boss_num or 0)
+    local all_num = math.min(all_num, (args.monster_num or 0) + (args.elite_num or 0) + (args.boss_num or 0))
+
+    if all_num > 0 then
+        character.dispatch("kill_monster", all_num)
+    end
+
+    if kill_boss > 0 then
+        character.dispatch("kill_boss", kill_boss)
+    end
+
+    d.layer = nil;
+    d.type = nil
+    d.cost = nil
+    _M.persist(character)
+    return 0
+end
+
+local function adv_coins_add_addition(character, equip_list)
+    -- supreme_card = supreme_card or require "model.activity.supreme_card"
+    -- local unlock_supreme_card = supreme_card.card_unlock(character)
+    local add_num = 0
+    -- if unlock_supreme_card then
+    --     add_num = add_num + asset.DataConfig_proto[48].data3*100
+    -- end
+    talent = talent or require "model.talent"
+    local talent_list = talent.get_special_attr(character)
+    if talent_list then
+        add_num = add_num + (talent_list[ATTR_ADV_COIN] or 0)
+    end
+    if equip_list then
+        add_num = add_num + (equip_list[ATTR_ADV_COIN] or 0)
+    end
+    return add_num
+end
+
+local function adv_exp_add_addition(character)
+    -- supreme_card = supreme_card or require "model.activity.supreme_card"
+    -- local unlock_supreme_card = supreme_card.card_unlock(character)
+    local add_num = 0
+    -- if unlock_supreme_card then
+    --     add_num = add_num + asset.DataConfig_proto[48].data2*100
+    -- end
+    return add_num
+end
+
+local function adv_exp_stone_add_addition(character, equip_list)
+    local add_num = 0
+    talent = talent or require "model.talent"
+    local talent_list = talent.get_special_attr(character)
+    if talent_list then
+        add_num = add_num + (talent_list[ATTR_ADV_EXP_STONE] or 0)
+    end
+    if equip_list then
+        add_num = add_num + (equip_list[ATTR_ADV_EXP_STONE] or 0)
+    end
+    return add_num
+end
+
+function THIS.simple_award(character, min, max)
+    local list = {}
+    local conf_list = assert(asset.StageConfig_proto, "StageConfig_proto")
+    for i = min, max do
+        local conf = conf_list[i]
+        if not conf then
+            break
+        end
+        for _, id in ipairs(conf.rewardId or {}) do
+            if id <= 0 then
+                break
+            end
+            local errno, temp = common_fun.get_award(id)
+            if errno ~= 0 then
+                return errno
+            end
+            if temp and next(temp) then
+                if not next(list) then
+                    list = temp
+                else
+                    list = common_fun.merge2list(list, temp)                    
+                end
+            end
+        end
+    end
+
+    hero = hero or require "model.hero"
+    local equip_list = hero.get_special_attr(character)
+    local coin_addition = adv_coins_add_addition(character, equip_list)
+    local exp_addition = adv_exp_add_addition(character)
+    local exp_stone = adv_exp_stone_add_addition(character, equip_list)
+    if coin_addition > 0 or exp_addition > 0 or exp_stone > 0 then
+        list = list or common_fun.merge_award(list)
+        for _, v in ipairs(list) do
+            if v.id == CURRENCY_ID_COINS and coin_addition > 0 then
+                v.num = v.num + math.floor(v.num * coin_addition/10000)
+            end
+            if v.id == CURRENCY_ID_ROLE_EXP and exp_addition > 0 then
+                v.num = v.num + math.floor(v.num * exp_addition/10000)
+            end
+            if v.id == CURRENCY_ID_EXP_STONE and exp_stone > 0 then
+                v.num = v.num + math.floor(v.num * exp_stone/10000)
+            end
+        end
+    end
+
+    return 0, list
+end
+
+function THIS.adventure_sweep(character, args)
+    local d = _M.assert_get(character) or {}
+    local id = args.id
+    if not id then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local info_conf_list = assert(asset.StageInfoConfig_proto, "StageInfoConfig_proto")
+    if not info_conf_list[id] then
+        return STD_ERR.COMMON_PARM_ERR  -- 参数异常
+    end
+
+    if d.pass[SIMPLE_ADV].id < id then
+        return STD_ERR.ADVENTURE_NOT_PASS -- 未通关
+    end
+
+
+    local conf_list = assert(asset.DataConfig_proto, "DataConfig_proto")
+    local conf = conf_list[1]
+    if not conf  then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    local costnum = conf.data5[1]
+    if not costnum then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+
+    local pay_list = {{
+        id = CURRENCY_ID_STM, 
+        num = costnum
+    }}
+    local receipt = {
+        module="adventure",
+        brief="扫荡",
+        context="扫荡:"..id,
+        notify={
+            flags="adventure_sweep"
+        },
+        detail=pay_list
+    }
+    -- 检查消耗
+    local errno = module_fun.paymeny_check(character, pay_list)
+    if errno ~= 0 then
+        return errno
+    end
+
+    local min_id = id*LAYER_SYS_NUM+1
+    local max_id = (id+1)*LAYER_SYS_NUM-1
+    local award_list
+    errno, award_list = THIS.simple_award(character, min_id, max_id)
+    if errno ~= 0 then
+        return errno
+    end
+
+    if not award_list or not next(award_list) then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    -- 发奖励
+    local desc = {
+        module="adventure",
+        brief="扫荡",
+        context= "扫荡:"..id,
+        mailgen={subject=2, body=""},
+        notify={
+            flags="adventure_sweep"
+        },
+        detail=award_list
+    }
+
+    payment(character, receipt, true)
+    reward(character, desc)
+
+    character.dispatch("sweep_simple_battle", id)
+
+    return 0
+end
+
+function THIS.adventure_pass_award(character, args)
+    local id = args.id 
+    local key = args.type or 0
+    if not id then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    if key ~= SIMPLE_ADV and key ~= ELITE_ADV then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    
+    local d = _M.assert_get(character)
+    local data = d.pass[key]
+    if not d.pass[key] then
+        return STD_ERR.COMMON_SYS_ERR -- 系统异常
+    end
+
+    if data.id < id then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    
+    local temp_id = id - 1
+    local index = math_floor(temp_id/32)+1
+    local pos = math_floor(temp_id%32)
+    data.award = data.award or {}
+    if key == ELITE_ADV then
+        index = math_floor((temp_id - 100)/10)+1
+        pos = math_floor(temp_id%10)
+    end
+
+    if index < 1 or pos < 0 then
+        return STD_ERR.COMMON_SYS_ERR -- 参数异常
+    end
+
+    for i = 1, index do
+        data.award[i] = data.award[i] or 0
+    end
+    
+    if data.award[index] & (1<<pos) > 0 then
+        return STD_ERR.COMMON_AWARD_RECEIVED -- 已领取过奖励    
+    end
+    
+    local conf_list
+    if key == SIMPLE_ADV then
+        conf_list = assert(asset.StageInfoConfig_proto, "StageInfoConfig_proto")
+    elseif key == ELITE_ADV then
+        conf_list = assert(asset.EliteInfoConfig_proto, "EliteInfoConfig_proto")
+    else
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    local conf = conf_list[id]
+    if not conf then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    local award_list = {}
+
+    for i = 1, #conf.reward, 2 do
+        local item = conf.reward[i]
+        local num = conf.reward[i+1]
+        if item and num and item > 0 and num > 0 then
+            table_insert(award_list, {id = item, num = num})
+        else
+            break
+        end
+    end
+
+    -- 发奖励
+    local desc = {
+        module="adventure",
+        brief="通关奖励",
+        context= "通关奖励:"..id,
+        mailgen={subject=2, body=""},
+        notify={
+            flags="adventure_pass_award"
+        },
+        detail=award_list
+    }
+    reward(character, desc)
+
+    data.award[index] = data.award[index] | (1<<pos)
+    _M.persist(character)
+    return 0, {id = args.id}
+end
+
+function THIS.adventure_ad_award(character, args)
+    local ctx = _M.assert_runtime(character)
+    if ctx.ad_award and next(ctx.ad_award) then
+        local desc = {
+            module="adventure",
+            brief="闯关奖励",
+            context= string.format("闯关广告奖励%d", ctx.id),
+            mailgen={subject=2, body=""},
+            notify={
+                flags="adventure_ad_award"
+            },
+            detail=ctx.ad_award
+        }
+        reward(character, desc)
+        ctx.ad_award = nil
+        character.dispatch("watch_ad")
+    else
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    return 0
+end
+
+function THIS.adventure_trial(character, args)
+    local d = _M.assert_get(character) or {}
+    if d.trialid > 0 and  not d.btrial then
+        d.btrial = d.btrial or true
+        _M.persist(character)
+    end
+    return 0
+end
+
+function THIS.adventure_box_award(character, args)
+    local id = args.id
+    local pos = args.pos
+    if not id  or not pos or pos < 1  or pos > 10 then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local d = _M.assert_get(character) or {}
+    local data  = d.pass[SIMPLE_ADV]
+    if not data then
+        return STD_ERR.COMMON_SYS_ERR
+    end
+
+    if id > data.id + 1 then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    
+    local conf_list = THIS.get_conf_list(SIMPLE_ADV)
+    if not conf_list then
+        return STD_ERR.COMMON_SYS_ERR
+    end
+    local conf = conf_list[id]
+    if not conf.boxcondition[pos] or not conf.box[pos] then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    if id == data.id + 1 then    
+        if not data.progress or data.progress <= conf.boxcondition[pos] then
+            return STD_ERR.COMMON_AWARD_NOT_UNLOCK
+        end
+    end
+
+    data.award_list = data.award_list or {}
+    data.award_list[id] = data.award_list[id] or 0
+    local flag = 1 << pos-1
+    if data.award_list[id] & flag > 0 then
+        return STD_ERR.COMMON_AWARD_RECEIVED
+    end
+
+    local errno, award_list = common_fun.get_award(conf.box[pos])
+    if errno ~= 0 then
+        return errno
+    end
+    local desc = {
+        module="adventure",
+        brief="关卡宝箱奖励",
+        context= string.format("关卡宝箱奖励%d", id),
+        mailgen={subject=2, body=""},
+        notify={
+            flags="adventure_box_award"
+        },
+        detail=award_list
+    }
+    reward(character, desc)
+    data.award_list[id] = data.award_list[id] + flag
+    _M.persist(character)
+    return {id = args.id, pos = args.pos, data = data.award_list[id]}
+end
+
+
+function REQUEST.adventure_start(character, args)
+    return func_ret("adventure_start", character, args)
+end
+
+
+function REQUEST.adventure_end(character, args)
+    return func_ret("adventure_end", character, args)
+end
+
+function REQUEST.adventure_data(character, args)
+    local d = _M.assert_get(character) or {}
+
+    -- local elite_id = d.pass[ELITE_ADV].id+1
+    -- if elite_id <= 100 then
+    --     elite_id = MIN_ELITE_ID
+    -- end
+    -- if not asset.EliteInfoConfig_proto[elite_id] then
+    --     for id = elite_id, elite_id + 20 do
+    --         if asset.EliteInfoConfig_proto[id] then
+    --             elite_id = id
+    --             break
+    --         end
+    --     end
+    -- end
+    local list = {}
+    d.pass[SIMPLE_ADV].award_list = d.pass[SIMPLE_ADV].award_list or {}
+    for i = 1, d.pass[SIMPLE_ADV].id +1 do
+        if d.pass[SIMPLE_ADV].award_list[i] then
+            table.insert(list, d.pass[SIMPLE_ADV].award_list[i])
+        else
+            table.insert(list, 0)
+        end
+    end
+
+    return {
+        errno = 0, 
+        id1 = d.pass[SIMPLE_ADV].id +1, 
+        award1 = d.pass[SIMPLE_ADV].award,
+        -- id2 = elite_id,
+        box_award = list,
+        award2 = d.pass[ELITE_ADV].award,
+        layer = d.pass[SIMPLE_ADV].progress and d.pass[SIMPLE_ADV].progress or 0, 
+        type = d.type and d.type or NIL_RET,
+        cost = d.cost and d.cost or NIL_RET
+    }
+end
+
+
+
+function REQUEST.adventure_layer(character, args)
+    return func_ret("adventure_layer", character, args)
+end
+
+function REQUEST.adventure_sweep(character, args)
+    return func_ret("adventure_sweep", character, args)
+end
+
+function REQUEST.adventure_pass_award(character, args)
+    return func_ret("adventure_pass_award", character, args)
+end
+
+function REQUEST.adventure_ad_award(character, args)
+    return func_ret("adventure_ad_award", character, args)
+end
+
+function REQUEST.adventure_trial(character, args)
+    return func_ret("adventure_trial", character, args)
+end
+
+function REQUEST.adventure_box_award(character, args)
+    return func_ret("adventure_box_award", character, args)
+end
+
+
+return MODULE

+ 9 - 0
project/model/asset.lua

@@ -0,0 +1,9 @@
+local sharedata = require "skynet.sharedata"
+local query = sharedata.query
+local self = {}
+local mt = {
+  __index = function(_, k)
+    return query(k)
+  end
+}
+return setmetatable(self, mt)

+ 45 - 0
project/model/character.lua

@@ -0,0 +1,45 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local spawn = require "model.spawn"
+
+local character = { }
+function character.loadfrom(metadata)
+  local usercenter = skynet.localname(".usercenter")
+  local skynet_call = skynet.call
+  local skynet_sleep = skynet.sleep
+
+  local trigger = {}
+  local t = spawn().loadfrom(metadata)
+  rawset(t, "persist", function(k, v)
+    trigger[k] = v
+  end)
+
+  local k = assert(t.uid)
+  local function flushall()
+    -- Execute all asynchronous setter
+    local tmp = trigger
+    trigger = {}
+    for _, f in pairs(tmp) do
+      f()
+    end
+
+    -- Serialize dirty data to array
+    local pkg = t.pack(k)
+    if pkg then
+      -- Save data to database
+      skynet_call(usercenter, "lua", "batch_save", pkg)
+    end
+  end
+  character.flushall = flushall
+  setmetatable(character, t)
+
+  skynet.fork(function()
+    while true do
+      flushall()
+      skynet_sleep(300)
+    end
+  end)
+  return character
+end
+
+return character

+ 358 - 0
project/model/common_fun.lua

@@ -0,0 +1,358 @@
+-- 公用函数,不要加载其他模块数据
+local asset = require "model.asset"
+local util = require "util"
+local skynet = require "skynet"
+local logger = require "logger"
+
+local math_floor = math.floor
+local table_insert = table.insert
+
+local M = {}
+
+function M.tlength(list)
+    list = list or {}
+    local s = 0
+    for k, v in pairs(list) do
+        s = s + 1;
+    end
+    return s
+end
+
+-- 表格式序列号
+function M.tqueue(obj)
+    local temp = {}
+    if not obj then
+        return temp
+    end
+    for _, v in pairs(obj) do
+        table.insert(temp, v)
+    end
+    return temp
+end
+
+function M.optimize_array(a)
+    if a and next(a) then
+        return a
+    end
+end
+
+-- 合并奖励
+function M.merge_award(t)
+    local temp = {}
+    for _, v in ipairs(t or {}) do
+        local id, num = v.id, v.num
+        if not temp[id] then 
+            temp[id] = {id = id, num = 0} 
+        end
+        temp[id].num = temp[id].num + num
+    end
+    return M.tqueue(temp)
+end
+
+-- 合并支付
+function M.merge_pay(t)
+    return M.merge_award(t)
+end
+
+
+-- 合并两个表,注意这里只是浅拷贝
+function M.merge2list(l1, l2)
+    local temp = l1 or {}
+    for _, v in ipairs(l2 or {}) do
+        table_insert(l1, v)
+    end
+    return temp
+end
+
+-- 合并两个表,注意这里只是浅拷贝
+function M.award_multiplier(list, times)
+    local temp = list or {}
+    for _, v in ipairs(temp) do
+        v.num = math_floor(v.num * times)
+    end
+    return temp
+end
+
+--根据id判断物品类型
+function M.goods_type(id)
+    if id >= 0 and id < 20000 then
+        return GOODS_HERO
+    elseif id >=20000 and id < 30000 then
+        return GOODS_CARD
+    elseif id >=30000 and id < 40000 then
+        return GOODS_BOX
+    elseif id >= 100000 and id < 150000 then
+        return GOODS_MONEY
+    elseif id >= 150000 and id < 160000 then
+        return GOODS_DEBRIS
+    elseif id >= 200000 and id < 300000 then
+        return GOODS_EQUIP
+    end
+    return GOODS_NONE
+end
+
+function M.goods_check(id)
+    local gtype = M.goods_type(id)
+    if gtype == GOODS_MONEY then
+        if not asset.GoodsConfig_proto[id] then
+            return STD_ERR.COMMON_UNKNOWN_CURRENCY -- 未知的货币            
+        end
+    elseif gtype == GOODS_CARD then
+        if not asset.CardSkillConfig_proto[id] then
+            return STD_ERR.COMMON_UNKNOWN_CARD -- 未知的技能卡牌            
+        end
+    elseif gtype == GOODS_HERO then
+        if not asset.RoleConfig_proto[id] then
+            return STD_ERR.COMMON_UNKNOWN_HERO -- 未知的角色
+        end
+    elseif gtype == GOODS_DEBRIS then
+        if not asset.CardConsumeConfig_proto[id] then
+            return STD_ERR.COMMON_UNKNOWN_DEBRIS -- 未知的碎片
+        end
+    elseif gtype == GOODS_EQUIP then
+        if not asset.ArmorConfig_proto[id] then
+            return STD_ERR.COMMON_UNKNOWN_EQUIP -- 未知的装备
+        end
+    else
+        return STD_ERR.COMMON_UNKNOWN_GOODS -- 未注册的物品
+    end
+    return 0
+end
+
+function M.city_skill_type(id)
+    return math.floor(id/100)
+end
+
+function M.city_skill_level(id)
+    return id%100
+end
+
+-- 深拷贝
+function M.scopy(obj)
+    if type(obj) ~= 'table' then
+        return obj
+    end
+    local tmp = {}
+    for k,v in pairs(obj) do
+        tmp[M.scopy(k)] = M.scopy(v)
+    end
+    return tmp
+end
+
+
+-- 奖励库生成奖励函数
+function M.get_award(id, name, cur_times)
+    local list = {}
+    local name = name or "AwardConfig_proto"
+    local conf_list = assert(asset[name], name)
+    local conf = conf_list[id]
+    if not conf then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    if cur_times and conf.single and conf.single[1] ~= 0 and conf.replace then
+        local replace = 0
+        for k, v in ipairs(conf.single) do
+            if v == cur_times then
+                replace = conf.replace[k] or 0
+                break
+            end
+        end
+        if replace ~= 0 then
+            conf = conf_list[replace]
+            if not conf then
+                return STD_ERR.COMMON_CONF_ERR -- 配置异常
+            end
+        end
+    end
+    local quality = conf.quality
+
+    if conf.weightsCount > 0 then
+        local size = #conf.library
+        local all = 0
+        for i = 1, size do
+            all = all + (conf.weights[i] or 0)
+        end
+        if all <= 0 then
+            return STD_ERR.COMMON_CONF_ERR -- 配置异常
+        end
+        
+        for i = 1, conf.weightsCount do
+            local r = util.rand(1, all)
+            for pos = 1, size do
+                if r <= (conf.weights[pos] or 0) then
+                    local id = conf.library[pos]
+                    local min = conf.weightsMin[pos] or 0
+                    local max = conf.weightsMax[pos] or 0
+                    local num = min
+                    if min > max then
+                        max, min = min, max
+                    end
+                    if max ~= min then
+                        num = util.rand(min, max)
+                    end
+                    if num > 0 then
+                        table_insert(list, {id = id, num = num, quality = quality})
+                    end
+                    break
+                else
+                    r = r - (conf.weights[pos] or 0)
+                end
+            end
+        end
+    end
+    
+    if conf.oddsCount and conf.oddsCount > 0 then
+        local size = #conf.reward
+        for i = 1, conf.oddsCount do
+            for pos = 1, size do
+                local r = conf.odds[pos] or 0
+                local id = conf.reward[pos]
+                local b = false
+                if r >= MAXRAND then
+                    b = true
+                elseif r > 0 then
+                    b = util.rand(1, MAXRAND) <= r
+                end
+                if b then
+                    local min = conf.oddsMin[pos] or 0
+                    local max = conf.oddsMax[pos] or 0
+                    local num = min
+                    if min > max then
+                        max, min = min, max
+                    end
+                    if max ~= min then
+                        num = util.rand(min, max)
+                    end
+                    if num > 0 then
+                        table_insert(list, {id = id, num = num, quality = quality})
+                    end
+                end
+            end
+        end
+    end
+
+    return 0, list
+end
+
+-- 洗牌算法
+function M.table_rand(list)
+    local size = #list
+    if size <= 1 then
+        return list
+    end
+    for i = 1, size do
+        local randnum = util.rand(1, size)
+        local temp = list[i]
+        list[i] = list[randnum]
+        list[randnum] = temp
+    end
+    return list
+end
+
+
+function M.get_hero_quality(id)
+    return math_floor(id%100)
+end
+
+function M.get_hero_type(id)
+    return math_floor(id/100)
+end
+
+function M.get_equip_type(id)
+    return math_floor(id/100)
+end
+
+function M.get_equip_quality(id)
+    return math_floor(id%100)
+end
+
+--[[
+    @desc: 解析时间字符串, 返回utc时间
+]]
+function M.split(str)
+    local chunks = {str:match("(%d+)%/(%d+)%/(%d+)%/(%d+)%/(%d+)%/(%d+)")}
+    assert(#chunks == 6)
+    local year, month, day, hour, min, sec = table.unpack(chunks)
+    return os.time({ year=year, month=month, day=day, hour=hour, min=min, sec=sec })
+end
+  
+--[[
+    @desc: 生成时间字符串
+]]
+function M.time_str(utc_time)
+    local date_table = os.date("*t",utc_time)
+    local str_table = {
+        date_table.year,
+        "/",date_table.month,
+        "/",date_table.day,
+        "/",date_table.hour,
+        "/",date_table.min,
+        "/",date_table.sec,
+    }
+    return table.concat(str_table)
+end
+
+--[[
+	@desc: 等价于redis的keys命令	优点:不会造成reids锁住造成的CPU飙升 缺点:查询时间变长(可忽略级)
+    @return: { [1~n] = key}
+]]
+local function _redis_keys_func(redis_obj,match_key,can_wait)
+	local scan_func = function(cursor,match_key,key_temp)
+		local ret = redis_obj:scan(cursor,"match", match_key,"count",100 )
+		if ret[2] then
+			for _,v in ipairs(ret[2]) do
+				key_temp[v] = true
+			end
+		end
+		return tonumber(ret[1])
+	end
+	local cursor = 0
+    local key_temp = {}
+	if can_wait then
+		while true do
+			cursor = scan_func(cursor,match_key,key_temp)
+			if cursor == 0 then
+				break
+			end
+			skynet.sleep(0)
+		end
+	else
+		while true do
+			cursor = scan_func(cursor,match_key,key_temp)
+			if cursor == 0 then
+				break
+			end
+		end
+	end
+	local keys = {}
+	for k,v in pairs(key_temp) do
+		table_insert(keys,k)
+	end
+	return keys
+end
+function M.redis_keys(redis_obj,match_key)
+	return _redis_keys_func(redis_obj,match_key,false)
+end
+function M.redis_keys_wait(redis_obj,match_key)
+	return _redis_keys_func(redis_obj,match_key,true)
+end
+
+function M.get_ret_func(THIS)
+    return function(fname, character, args)
+        local f = THIS[fname]
+        if not f then
+            logger.error("func_ret not fname:%s !!!", fname)
+            return {errno = STD_ERR.COMMON_SYS_ERR}
+        end
+        local errno, ret = f(character, args)
+        if errno ~= 0 then
+            return {errno = errno}
+        end
+        ret = ret or {}
+        ret.errno = 0
+        return ret
+    end
+end
+
+return M

+ 252 - 0
project/model/currency.lua

@@ -0,0 +1,252 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local asset = require "model.asset"
+local common_fun = require "model.common_fun"
+local skynet = require "lualib.skynet"
+
+local role
+
+local MODULE_NAME = "currency"
+local MAX_CHANGE = 20000000000
+local logger_trace = logger.trace
+local table_insert = table.insert
+
+local statistics_list = {
+    CURRENCY_ID_COINS, 
+    CURRENCY_ID_STM,
+    CURRENCY_ID_RELIC_SCORE,
+    CURRENCY_ID_RELIC_COIN,
+    CURRENCY_ID_EQUIP_COIN,
+    CURRENCY_ID_DIA,
+    CURRENCY_ID_IRON,
+    CURRENCY_ID_WOOD,
+    CURRENCY_ID_ROLE_EXP,
+    CURRENCY_ID_EXP_STONE,
+    CURRENCY_ID_EXP_BOOK,
+    CURRENCY_ID_TALENT_STONE,
+    CURRENCY_ID_WEAPON_SCROLL,
+    CURRENCY_ID_JEWELRY_SCROLL,
+    CURRENCY_ID_WRISTER_SCROLL,
+    CURRENCY_ID_CUIRASS_SCROLL,
+    CURRENCY_ID_BELT_SCROLL,
+    CURRENCY_ID_SHOE_SCROLL,
+    CURRENCY_ID_DRAW_COIN,
+    CURRENCY_ID_DRAW_COIN2,
+}
+
+local _M = schema.new(MODULE_NAME, {
+    first = false,
+    data = {
+        -- [id] = {
+        --     id = 货币id;
+        --     num = 货币数量;
+        -- }
+    }
+})
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据
+function MODULE.parse(character)
+    local d = _M.load(character)
+    if not d then
+        return 
+    end
+    if not d.first then
+        d.data[CURRENCY_ID_STM] = d.data[CURRENCY_ID_STM] or {id = CURRENCY_ID_STM, num = asset.DataConfig_proto[1].data3}
+        d.first = true
+        _M.persist(character)
+    end
+    -- 加载货币配置 设置为0
+    -- local conf = assert(asset.GoodsConfig_proto, "GoodsConfig_proto")
+    -- for k, _ in pairs(conf) do
+    --     if not d.data[k] then
+    --         d.data[k] = {
+    --             id = k,
+    --             num = 0,
+    --         }
+    --     end
+    -- end
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+    local d = _M.assert_get(character)
+    character.monitor("daily_refresh", function()
+        local data = d.data[CURRENCY_ID_DACTIVITY]
+        if data then
+            local num = data.num
+            d.data[CURRENCY_ID_DACTIVITY] = nil
+            _M.persist(character)
+            character.send("pay_currency", {now = {{id = CURRENCY_ID_DACTIVITY, num = 0}}, pay = {{id = CURRENCY_ID_DACTIVITY, num = num}}})
+        end
+    end)     
+    
+    character.monitor("weekly_refresh", function()
+        local data = d.data[CURRENCY_ID_WACTIVITY]
+        if data then
+            local num = data.num
+            d.data[CURRENCY_ID_WACTIVITY] = nil
+            _M.persist(character)
+            character.send("pay_currency", {now = {{id = CURRENCY_ID_WACTIVITY, num = 0}}, pay = {{id = CURRENCY_ID_WACTIVITY, num = num}}})
+        end
+    end)
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    local d = _M.assert_get(character)
+    role = role or require "model.role"
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+  local d = _M.assert_get(character)
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+    local d = _M.assert_get(character)
+    local ret = {
+        level = character.level,
+        list = {}
+    }
+    -- for _ , key in ipairs(statistics_list) do
+    --     if d.data[key] then
+    --         table.insert(ret.list, d.data[key])
+    --     end
+    -- end
+    -- local itemavg = skynet.localname(".itemavg")
+    -- if next(ret.list) then
+    --     skynet.send(itemavg, "lua", "updata", ret)
+    -- end
+end
+
+function MODULE.get_client_data(character)
+    local d = _M.assert_get(character) or {}
+    return common_fun.tqueue(d.data)
+end
+
+-- list = {{id =1 , num = 1}} 物品id, 物品数量
+function MODULE.add_money(character, id, num, collect, dispose, addition)
+    local d = _M.assert_get(character)
+    local conf_list = assert(asset.GoodsConfig_proto, "GoodsConfig_proto")
+    local conf = conf_list[id]
+    if not conf and dispose then
+        dispose(nil, id, num)
+        return 
+    end
+
+    -- 一次超过2000亿是什么数值
+    if num >= 1 and num < MAX_CHANGE then
+        d.data[id] = d.data[id] or {id = id, num = 0}
+        d.data[id].num = d.data[id].num + math.floor(num) -- 向下取下整避免意外
+        character.dispatch("currency_add", id, num)
+        if addition then
+            addition(GOODS_MONEY, id, num)
+        end
+        if collect then
+            collect(GOODS_MONEY, d.data[id])
+        end
+        _M.persist(character)
+        return true, num, d.data[id].num
+    else
+        if dispose then
+            dispose(nil, id, num)
+        end
+    end
+end
+
+-- 支付一旦异常玩家直接报错
+function MODULE.pay_money(character, id, num)
+    local d = _M.assert_get(character)
+    if num < 0 then
+        assert(false)
+    end
+    local cur_num = 0
+    if d.data and d.data[id] and d.data[id].num then
+        cur_num = d.data[id].num
+    else
+        assert(false)
+    end
+    if cur_num < num then
+        assert(false)
+    end
+    d.data[id].num = d.data[id].num - math.floor(num)
+    _M.persist(character)
+    if id == CURRENCY_ID_STM then
+        role.stm_change(character)
+    end
+    character.dispatch("pay_money", id, num)
+    return  d.data[id].num
+end
+
+
+function MODULE.check_money(character, id, num)
+    local d = _M.assert_get(character)
+    -- 数量至少为1
+    if num < 0 then
+        return STD_ERR.PAYMENT_NUM_ERR
+    end
+
+    if not d.data or not d.data[id] then
+        local conf = assert(asset.GoodsConfig_proto, "GoodsConfig_proto")
+        if not conf[id] then
+            return STD_ERR.PAYMENT_UNKNOWN_ITEM
+        end
+        return STD_ERR.PAYMENT_NOT_ENOUGH
+    end
+
+    if not d.data[id].num then
+        return STD_ERR.COMMON_SYS_ERR
+    end
+
+    if d.data[id].num < num then
+        return STD_ERR.PAYMENT_NOT_ENOUGH
+    end
+
+    return 0
+end
+
+function MODULE.set_money(character, id, num)
+    local d = _M.assert_get(character)
+    if num < 0 then
+        return STD_ERR.PAYMENT_NUM_ERR
+    end
+
+    if not d.data or not d.data[id] then
+        local conf = assert(asset.GoodsConfig_proto, "GoodsConfig_proto")
+        if not conf[id] then
+            return STD_ERR.PAYMENT_UNKNOWN_ITEM
+        end
+    end
+    d.data[id].num = num
+    _M.persist(character)
+    return 0
+end
+
+function MODULE.get_money(character, id)
+    local d = _M.assert_get(character)
+    return d.data[id] and d.data[id].num or 0
+end
+
+-- 获取所有随机宝箱
+function MODULE.get_all_random_box(character, args)
+    local d = _M.assert_get(character)
+    local conf_list = assert(asset.GoodsConfig_proto, "GoodsConfig_proto")
+    local temp = {}
+    for _, data in pairs(d.data) do
+        local conf = conf_list[data.id]
+        if conf and conf.type == CURRENCY_TYPE_RANDOM_BOX and data.num > 0 then
+            table.insert(temp, {id = data.id, num = data.num})
+        end
+    end
+    return temp
+end
+
+return MODULE

+ 291 - 0
project/model/embattle.lua

@@ -0,0 +1,291 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local asset = require "model.asset"
+local stringify = require "stringify"
+local common_fun = require "model.common_fun"
+local hero
+-- local skill_card
+local powerfunc
+
+local math_floor = math.floor
+
+local module_name = "embattle"
+local _M = schema.new(module_name, {
+    adventure = {
+        list = {
+            -- [pos] = {
+            --     sid = sid,
+            --     pos = pos,
+            -- }
+        },
+        card_list = {}
+    }
+})
+
+
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据 在这里把数据初始化好
+function MODULE.parse(character)
+    local d = _M.load(character)
+    d.adventure = d.adventure or {}
+    d.adventure.list = d.adventure.list or {}
+    d.adventure.card_list = d.adventure.card_list or {}
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+
+end
+
+-- 
+function MODULE.launch(character)
+    local d = _M.assert_get(character)
+    hero = hero or require "model.hero"
+    -- skill_card = skill_card or require "model.skill_card"
+end
+
+-- 
+function MODULE.ready(character)
+    local d = _M.assert_get(character)
+    logger.test("%s:ready, %s", module_name, stringify(d or {}))
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+end
+
+function MODULE.battle_power(character)
+    local all = 0
+    local d = _M.assert_get(character) or {}
+    powerfunc = powerfunc or require "model.powerfunc"
+    for k, v in pairs(d.adventure.list) do
+        local hero_data = hero.hero_get(character, v.sid)
+        local power = powerfunc.build_role(character, hero_data)
+        if power and power > 0 then
+            all = all + power
+        end
+    end
+    -- for k, v in pairs(d.adventure.card_list) do
+    --     local card_data = skill_card.skill_card_get(character, v.sid)
+    --     local power = powerfunc.build_card(character, card_data)
+    --     if power and power > 0 then
+    --         all = all + power
+    --     end
+    -- end
+    return all
+end
+
+function MODULE.first_battle(character)
+    local d = _M.assert_get(character) or {}
+    local hero_list = hero.hero_get_list(character)
+    local Data_conf = asset.DataConfig_proto[4]
+    for pos, v in ipairs(Data_conf.data5 or {}) do
+        if v > 0 then
+            for _, info in pairs(hero_list) do
+                if info.id == v then
+                    d.adventure.list[pos] = {
+                        sid = info.sid,
+                        pos = pos
+                    }
+                end
+            end
+        end
+    end
+    logger.trace("adventure"..stringify(d.adventure.list))
+
+    _M.persist(character)
+end
+
+function MODULE.max_power_hero(character)
+    local d = _M.assert_get(character) or {}
+    powerfunc = powerfunc or require "model.powerfunc"
+    local max_power = 0
+    local hero_info
+    for k, v in pairs(d.adventure.list) do
+        local hero_data = hero.hero_get(character, v.sid)
+        local power = powerfunc.build_role(character, hero_data)
+        if power and power > max_power then
+            max_power = power
+            hero_info = hero_data
+        end 
+    end
+    return max_power, hero_info
+end
+
+-- 获得阵上的英雄
+function MODULE.get_embattle_hero_list(character)
+    local d = _M.assert_get(character)
+    local list = {}
+    for _, v in pairs(d.adventure.list) do
+        list[v.sid] = v.sid
+    end
+    return list
+end
+
+local function battle_sort(list, num)
+    local ret = {}
+    if num <= 0 then
+        return ret
+    end
+    for i = 1, num do
+        if list[i] then
+            table.insert(ret, list[i])
+        else
+            table.insert(ret, {pos = 0, sid = ""})
+        end
+    end
+    return ret
+end
+
+function THIS.embattle_battle(character, args)
+    local d = _M.assert_get(character)
+    local data_conf_list = assert(asset.DataConfig_proto, "DataConfig_proto")
+
+    local pos = args.pos
+    local sid = args.sid
+    if sid == "" then
+        sid = nil
+    end
+    if not pos then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local data_conf
+    local list
+    local check_func
+    if args.skill then
+        data_conf = data_conf_list[4]
+        list = d.adventure.card_list
+        -- check_func = function()
+        --     return skill_card.skill_card_check(character, sid)
+        -- end
+    else
+        data_conf = data_conf_list[3]
+        list = d.adventure.list
+        check_func = function()
+            return hero.hero_check(character, sid)
+        end
+    end
+
+    local max_pos = data_conf.data1
+
+    if pos > max_pos then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    if not sid then
+        list[pos] = nil
+        _M.persist(character)
+        return 0, {
+            list = battle_sort(d.adventure.list, data_conf_list[3].data1),
+            card_list = battle_sort(d.adventure.card_list, data_conf_list[4].data1)
+        }
+    end
+
+    -- 检查是否存在
+    if not check_func or not check_func() then
+        return STD_ERR.EMBATTLE_NOT_TARGET -- 未找到目标
+    end
+
+    local old_pos = -1
+    for _, v in pairs(list) do
+        if v.sid == sid then
+            old_pos = v.pos
+            break
+        end
+    end
+
+    if old_pos == pos then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    -- 有旧位置,先处理旧位置逻辑
+    if old_pos > 0 then
+        --相当于换位置
+        if list[pos] then
+            list[old_pos] = list[pos]
+        else
+            list[old_pos] = nil
+        end
+    else
+        if not args.skill then
+            local temp = {}
+            for _, v in pairs(d.adventure.list) do
+                -- 不用检查相同位置上的同名卡
+                if v.pos~= pos then
+                    local hero_info = hero.hero_get(character, v.sid)
+                    if hero_info then
+                        temp[common_fun.get_hero_type(hero_info.id)] = 1
+                    end
+                end
+            end
+            local hero_info = hero.hero_get(character, sid)
+            if temp[common_fun.get_hero_type(hero_info.id)] then
+                return STD_ERR.EMBATTLE_SAME_NAME_HERO -- 不满足上阵需求
+            end 
+            local hero_conf = asset.RoleConfig_proto[hero_info.id]
+            if not hero_conf then
+                return STD_ERR.COMMON_CONF_ERR
+            end
+            if hero_conf.profession ~= pos then
+                return STD_ERR.EMBATTLE_SAME_NAME_HERO
+            end
+        end
+    end
+
+    list[pos] = {
+        sid = sid,
+        pos = pos,
+    }
+    _M.persist(character)
+    return 0, {
+        list = battle_sort(d.adventure.list, data_conf_list[3].data1),
+        card_list = battle_sort(d.adventure.card_list, data_conf_list[4].data1)
+    }
+end
+
+
+function THIS.embattle_get_data(character, args)
+    local data_conf_list = assert(asset.DataConfig_proto, "DataConfig_proto")
+    local d = _M.assert_get(character)
+    return 0, {
+        list = battle_sort(d.adventure.list, data_conf_list[3].data1),
+        card_list = battle_sort(d.adventure.card_list, data_conf_list[4].data1)
+    }
+end
+
+-- 上阵
+function REQUEST.embattle_battle(character, args)
+    return func_ret("embattle_battle", character, args)
+end
+
+-- 上阵数据
+function REQUEST.embattle_get_data(character, args)
+    return func_ret("embattle_get_data", character, args)
+end
+
+
+return MODULE

+ 745 - 0
project/model/equip.lua

@@ -0,0 +1,745 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local common_fun = require "model.common_fun"
+local module_fun = require "model.module_fun"
+local asset = require "model.asset"
+local payment = require "model.payment"
+local reward = require "model.reward"
+local stringify = require "stringify"
+local keygen   = require "model.keygen"
+local embattle 
+
+local logger_trace = logger.trace
+local table_insert = table.insert
+local math_min = math.min
+
+local MAX_NUM = 2000  -- 装备背包最大数量
+local MAX_UPGRADE = 1000 -- 最大升级数量
+local module_name = "equip"
+local _M = schema.new(module_name, {
+    first = nil,
+    key = 100,
+    num = 0,
+    list = {
+    --[[
+        [sid] = {
+            sid = 唯一id,
+            id = 模板id,
+            lv = 1,
+            hero = 穿戴的英雄id
+        }
+    ]]
+    },
+})
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local tm_list = {}
+tm_list.equip_onekey_upgrade_star = 0
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    if tm_list[fname] then
+        local now = os.time()
+        if now - tm_list[fname] == 0 then
+            return {errno = STD_ERR.COMMON_CLICK_TOO_FAST} -- 点击太快了
+        else
+            tm_list[fname] = now
+        end
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据 在这里把数据初始化好
+function MODULE.parse(character)
+    local d = _M.load(character)
+    d.num = d.num or 0
+    d.list = d.list or {}
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    local d = _M.assert_get(character)
+    local u = _M.assert_runtime(character)
+    embattle = embattle or require "model.embattle"
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+    local d = _M.assert_get(character)
+    -- logger.test("%s:ready, %s", module_name, stringify(d or {}))
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+
+end
+
+
+function MODULE.equip_surplus(character)
+    local d = _M.assert_get(character)
+    return math.max((MAX_NUM - d.num), 0)
+end
+
+local function new_equip(character, id)
+    return {
+        sid = THIS.key(character),
+        id = id,
+        lv = 1
+    }
+end
+
+function MODULE.add_equip(character, id, num, collect, dispose, addition)
+    local d = _M.assert_get(character) or {}
+    local equip_conf_list = assert(asset.ArmorConfig_proto, "ArmorConfig_proto")
+    local conf = equip_conf_list[id]
+    if not conf and dispose then
+        dispose(nil, id, num)
+        return 
+    end
+
+    local surplus = MODULE.equip_surplus(character)
+    if num >= 1 and num <= MAX_NUM then
+        local add_num = math.min(surplus, num)
+        if add_num > 0 then
+            for i = 1, add_num do
+                local equip = new_equip(character, id)
+                d.list[equip.sid] = equip
+                collect(GOODS_EQUIP, equip)
+            end
+            d.num = d.num + add_num
+            _M.persist(character)
+            character.dispatch("equip_add_num", id, num)
+        end
+        addition(GOODS_EQUIP, id, add_num)
+        if add_num < num then
+            dispose(GOODS_EQUIP, id, num - add_num)
+        end
+        return true, add_num
+    else
+        if dispose then
+            dispose(nil, id, num)
+        end
+    end
+end
+
+function MODULE.equip_check(character, sid)
+    local d = _M.assert_get(character)
+    return d.list[sid] and true or false 
+end
+
+function MODULE.equip_get(character, sid)
+    local d = _M.assert_get(character)
+    return d.list[sid]
+end
+
+function MODULE.equip_wear(character, hero, old_list, new_list)
+    local d = _M.assert_get(character)
+    local equip_conf_list = assert(asset.ArmorConfig_proto, "ArmorConfig_proto")
+    local old_wear = 0
+    local new_wear = 0
+    local surplus = MODULE.equip_surplus(character)
+    local sid = hero.sid
+    local heroid = hero.id
+    local hero_conf = asset.RoleConfig_proto[heroid]
+    if not hero_conf then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+    local hero_profession = hero_conf.profession
+    for pos, esid in ipairs(new_list or {}) do
+        if esid and esid ~= "" then
+            local data = d.list[esid]
+            if not data then
+                return STD_ERR.COMMON_PARM_ERR -- 未找到装备
+            end 
+
+            local id = data.id
+            local conf = equip_conf_list[id]
+            if not conf then
+                return STD_ERR.COMMON_CONF_ERR -- 配置异常
+            end
+
+            if conf.type ~= pos then
+                return STD_ERR.EQUIP_POS_ERR -- 装备位置异常
+            end
+
+            local can_wear = false
+            for _, profession in ipairs(conf.profession) do
+                if hero_profession == profession then
+                    can_wear = true
+                    break
+                end
+            end
+
+            if not can_wear then
+                return STD_ERR.EQUIP_PROFESSION_ERR -- 装备位置异常
+            end
+
+            if data.hero and data.hero ~= sid then
+                return STD_ERR.EQUIP_BE_USED -- 装备被穿戴
+            end
+            new_wear = new_wear + 1
+        end
+    end
+
+    for _, esid in ipairs(old_list or {}) do
+        if esid and esid ~= "" and d.list[esid] and d.list[esid].hero then
+            old_wear = old_wear + 1
+        end
+    end
+
+    if surplus < old_wear - new_wear then
+        return STD_ERR.EQUIP_FULL -- 背包已满
+    end 
+    
+    for _, esid in ipairs(old_list or {}) do
+        if esid and esid ~= "" and d.list[esid] and d.list[esid].hero then
+            d.list[esid].hero = nil
+        end
+    end
+
+    for _, esid in ipairs(new_list or {}) do
+        if esid and esid ~= "" and d.list[esid] then
+            d.list[esid].hero = sid
+        end
+    end
+
+    d.num = d.num + new_wear - old_wear
+    _M.persist(character)
+    return 0
+end
+
+function MODULE.equip_remove_check(character, list)
+    local num = MODULE.equip_surplus(character)
+    return num > #list
+end
+
+function MODULE.equip_remove(character, list)
+    local d = _M.assert_get(character)
+    local add_num = 0
+    for _, esid in ipairs(list or {}) do
+        if esid and esid ~= "" and d.list[esid] and d.list[esid].hero then
+            d.list[esid].hero = nil
+            add_num = add_num + 1
+        end
+    end
+
+    d.num = d.num + add_num
+    _M.persist(character)
+    return 0
+end
+
+function MODULE.equip_save(character)
+    _M.persist(character)
+end
+
+function THIS.equip_get_data(character, args)
+    local d = _M.assert_get(character)
+    return 0, {list = common_fun.tqueue(d.list)}
+end
+
+local function get_max_upgrade(character, equip_pos, cur_lv, max_lv)
+    local num = 0
+    local max_lv = max_lv or cur_lv + MAX_UPGRADE
+    local level_conf_list = assert(asset.EquipmentLevelConfig_proto, "EquipmentLevelConfig_proto")
+    local scrol_id = asset.DataConfig_proto[6].data6[equip_pos]
+    local errno = common_fun.goods_check(scrol_id)
+    if errno ~= 0 then
+        return num
+    end
+    local goods_list = {}
+    for lv = cur_lv+1, max_lv do
+        local level_conf = level_conf_list[lv]
+        if not level_conf then
+            break
+        end
+
+        if level_conf.scrollNum > 0 then 
+            local item_id = scrol_id
+            local item_num = level_conf.scrollNum
+            if not goods_list[item_id] then
+                goods_list[item_id] = module_fun.get_goods_num(character, item_id)
+            end
+            if goods_list[item_id] < item_num then
+                break
+            end
+            goods_list[item_id] = goods_list[item_id] - item_num
+        end
+
+        if level_conf.goodNum > 0 then
+            local item_id = CURRENCY_ID_COINS
+            local item_num = level_conf.goodNum
+            if not goods_list[item_id] then
+                goods_list[item_id] = module_fun.get_goods_num(character, item_id)
+            end
+            if goods_list[item_id] < item_num then
+                break
+            end
+            goods_list[item_id] = goods_list[item_id] - item_num
+        end
+        num = lv - cur_lv
+    end
+    return num
+end
+
+local function upgrade_award(equip_pos, cur, tar, list)
+    local item_id = asset.DataConfig_proto[6].data6[equip_pos]
+    local errno = common_fun.goods_check(item_id)
+    if errno ~= 0 then
+        return errno
+    end
+
+    local level_conf_list = assert(asset.EquipmentLevelConfig_proto, "EquipmentLevelConfig_proto")
+    for lv = cur+1, tar do
+        local level_conf = level_conf_list[lv]
+        if not level_conf then
+            return STD_ERR.COMMON_SYS_ERR -- 系统异常
+        end
+        if level_conf.scrollNum > 0 then 
+
+            table_insert(list, {id = item_id, num = level_conf.scrollNum})
+        end
+        if level_conf.goodNum > 0 then
+            table_insert(list, {id = CURRENCY_ID_COINS, num = level_conf.goodNum})
+        end
+
+    end
+    return 0
+end
+
+local function reset_level(equip_pos, lv, list)
+    return upgrade_award(equip_pos, 1, lv, list)
+end
+
+function THIS.key(character)
+    local d = _M.assert_get(character)
+    d.key = d.key + 1
+    return tostring(d.key)
+end
+
+function THIS.equip_upgrade(character, args)
+    local d = _M.assert_get(character)
+    local sid = args.sid 
+    local num = args.num or 0
+    if not sid then
+        return STD_ERR.COMMON_PARM_ERR  -- 参数异常
+    end
+
+    if num < 0 or num > MAX_UPGRADE then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local data = d.list[sid]
+    local level = data.lv
+    if not data then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    local equip_conf_list = assert(asset.ArmorConfig_proto, "ArmorConfig_proto")
+    local equip_conf = equip_conf_list[data.id]
+    if not equip_conf then
+        logger_trace("ArmorConfig_proto:%d", data.id)
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    local quality_conf_list = assert(asset.EquipmentQualityConfig_proto, "EquipmentQualityConfig_proto")
+    local cur_quality = common_fun.get_equip_quality(data.id)
+    local quality_conf = quality_conf_list[cur_quality]
+    if not quality_conf then
+        logger_trace("EquipmentQualityConfig_proto:%d", cur_quality)
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    
+    if num == 0 then
+        num = get_max_upgrade(character, equip_conf.type, level, math_min(quality_conf.maxLv, character.level))
+    end
+
+    if level+num > quality_conf.maxLv or level+num > character.level then
+        return STD_ERR.EQUIP_MAX_LEVEL -- 超过最大等级
+    end
+
+    local pay_list = {}
+    local errno = upgrade_award(equip_conf.type, level, level+num, pay_list)
+    if errno ~= 0 then
+        return errno
+    end
+    local receipt = {
+        module="equip",
+        brief="装备升级",
+        context=string.format("装备id%d, 从%d升级到%d", data.id, level, level+num),
+        notify={
+            flags="equip_upgrade"
+        },
+        detail=pay_list
+    }
+    local errno = payment(character, receipt)
+    if errno ~=0 then
+        return errno
+    end
+    local pre_lv = data.lv
+    local cur_lv = data.lv + num
+    data.lv = data.lv+num
+    _M.persist(character)
+    character.dispatch("equip_upgrade", data.id, pre_lv, cur_lv)
+    return 0, {data = data}
+end
+
+
+function THIS.equip_upgrade_star(character, args)
+    local d = _M.assert_get(character)
+    local sid = args.sid
+    local same_list = args.same or {}
+    local other_list = args.other or {}
+    local item_list = args.currency or {}
+    local buse_item = next(item_list) and item_list.num and item_list.num > 0 and true or false
+    if buse_item then
+        if not item_list.id then
+            return STD_ERR.COMMON_PARM_ERR -- 参数异常
+        end
+    end
+
+    if not sid then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local data = d.list[sid]
+    if not data then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    -- 先检查是否重复
+    local temp_list = {[sid] = 1}
+    for _, v in ipairs(same_list) do
+        if temp_list[v] then
+            return STD_ERR.EQUIP_SAME_SID -- 重复的装备id
+        end
+        if not d.list[v] or d.list[v].hero then
+            return STD_ERR.COMMON_PARM_ERR -- 参数异常
+        end
+        temp_list[v] = 1
+    end
+
+    for _, v in ipairs(other_list) do
+        if temp_list[v] then
+            return STD_ERR.EQUIP_SAME_SID -- 重复的装备id
+        end
+        if not d.list[v] or d.list[v].hero then
+            return STD_ERR.COMMON_PARM_ERR -- 参数异常
+        end
+        temp_list[v] = 1
+    end
+
+    local equip_conf_list = assert(asset.ArmorConfig_proto, "ArmorConfig_proto")
+    local equip_conf = equip_conf_list[data.id]
+    local equip_type = equip_conf.type
+    if not equip_type then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    if not equip_conf then
+        logger_trace("ArmorConfig_proto:%d", data.id)
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+    
+    local cur_quality = common_fun.get_equip_quality(data.id)
+
+    local max_quality = asset.DataConfig_proto[5].data3
+    if cur_quality >= max_quality then
+        return STD_ERR.EQUIP_START_MAX -- 达到最大突破等级
+    end
+
+    local min_id = math.floor(data.id/100)*100+1
+    local max_id = min_id + 98
+    local conf_id = data.id + 1
+    if not equip_conf_list[conf_id] then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+    
+    local quality_conf_list = assert(asset.EquipmentQualityConfig_proto, "EquipmentQualityConfig_proto")
+    local quality_conf = quality_conf_list[cur_quality+1]
+    if not quality_conf then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    -- 检查消耗数量
+    if quality_conf.sameQualityNum ~= #same_list or quality_conf.anyQualityNum ~= #other_list + (buse_item and item_list.num or 0) then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    --检查同名装备消耗
+    for i = 1, #same_list do
+        local id = d.list[same_list[i]].id
+        if id < min_id or id > max_id then
+            return STD_ERR.EQUIP_SAME_PAY_ERR -- 不符合消耗条件
+        end
+        -- local conf = equip_conf_list[id]
+        local quality = common_fun.get_equip_quality(id)
+        if quality ~= quality_conf.sameQuality then
+            return STD_ERR.EQUIP_SAME_PAY_ERR -- 不符合消耗条件
+        end
+    end
+
+    --检查任意装备消耗
+    for i = 1, #other_list do
+        local id = d.list[other_list[i]].id
+        local conf = equip_conf_list[id]
+        local quality = common_fun.get_equip_quality(id)
+        if quality ~= quality_conf.anyQuality or conf.type ~= equip_type then
+            return STD_ERR.EQUIP_OTHER_PAY_ERR -- 不符合消耗条件
+        end
+    end
+
+    local del_list = {}
+    local award_list = {}
+    -- 把突破的装备排除和已装备的装备排除
+    temp_list[sid] = nil
+    for k in pairs(temp_list) do
+        if d.list[k].lv > 1 or d.list[k].hero then
+            local errno = reset_level(equip_type, d.list[k].lv, award_list)
+            if errno ~= 0 then
+                return errno
+            end
+        end
+        table_insert(del_list, k)
+    end 
+
+    -- 检查替代品消耗
+    if buse_item then
+        if quality_conf.resetGoods[equip_type] ~= item_list.id then
+            return STD_ERR.EQUIP_OTHER_PAY_ERR -- 不符合消耗条件
+        end
+        local receipt = {
+            module="equip",
+            brief="装备升星",
+            context=string.format("装备id%d, 升星为%d", data.id, conf_id),
+            notify={
+                flags="equip_upgrade_star"
+            },
+            detail={item_list}
+        }
+        local errno = payment(character, receipt)
+        if errno ~= 0 then
+            return errno
+        end
+    end
+
+    if next(award_list) then
+        local desc = {
+            module="equip",
+            brief="装备突破",
+            context= "装备突破返还",
+            mailgen={subject=2, body=""},
+            notify={
+                flags="equip_upgrade_star"
+            },
+            detail=award_list
+        }
+        reward(character, desc)
+    end
+
+    for _, v in ipairs(del_list)do
+        if d.list[v] then
+            d.list[v] = nil
+        end
+    end
+    d.list[sid].id = conf_id
+    d.num = d.num - #temp_list
+    _M.persist(character)
+    character.dispatch("equip_del", del_list)
+    character.dispatch("equip_upstart", conf_id, cur_quality, cur_quality+1)
+    return 0, {data = d.list[sid], same = args.same, other = args.other, currency = args.currency}
+end
+
+function THIS.equip_onekey_upgrade_star(character, args)
+    local d = _M.assert_get(character)
+    local temp_list = {}
+    for sid, data in pairs(d.list) do
+        -- 紫色 未上阵 未升级的装备一键突破
+        local quality = common_fun.get_equip_quality(data.id)
+        if data.lv == 1 and quality < STAR_COLOR_PURPLE and not data.hero or data.hero == "" then
+            temp_list[quality] = temp_list[quality] or {}
+            temp_list[quality][data.id] = temp_list[quality][data.id] or {}
+            table_insert(temp_list[quality][data.id], sid)
+        end
+    end
+
+    local del_list = {}
+    local change_list = {}
+    local max_quality = 1
+    for quality = 1, STAR_COLOR_PURPLE do
+        local quality_conf_list = assert(asset.EquipmentQualityConfig_proto, "EquipmentQualityConfig_proto")
+        if not quality_conf_list[quality] or quality_conf_list[quality].anyQualityNum > 0 then
+            break
+        end
+        local temp = temp_list[quality]
+        if temp then
+            for id, list in pairs(temp) do
+                local quality_conf = quality_conf_list[quality+1]
+                local equip_conf = assert(asset.ArmorConfig_proto, "ArmorConfig_proto")[id+1]
+                local num = #list
+                local step = quality_conf.sameQualityNum+1
+                assert(step > 0)
+                if equip_conf and step <= num and quality_conf.sameQuality == quality then
+                    for i = 1, num, step do
+                        if list[i+step-1] then
+                            d.list[list[i]].id = id+1
+                            table.insert(change_list, d.list[list[i]])
+                            for j = i+1, i+step-1 do
+                                d.list[list[j]] = nil
+                                table.insert(del_list, list[j])
+                            end
+                            max_quality = quality
+                            if #change_list >= 100 then
+                                goto equip_onekey_upgrade_star_break
+                            end
+                        end
+                    end
+                end
+
+            end    
+        end
+    end
+    ::equip_onekey_upgrade_star_break::
+    if not next(change_list) then
+        return STD_ERR.EQUIP_NOBODY_CAN_UP_STAR -- 没有
+    end
+    d.num = d.num - #del_list
+    _M.persist(character)
+    character.dispatch("equip_del", del_list)
+    character.dispatch("equip_upstart_times", #change_list, max_quality+1)
+    return 0, {del_list = del_list, change_list = change_list}
+end
+
+-- 重置装备
+function THIS.equip_reset(character, args)
+    local sid = args.sid 
+    if not sid then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    local d = _M.assert_get(character)
+    local data = d.list[sid]
+    if not data then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    local temp_data = common_fun.scopy(data)
+    local award_list = {}
+    local desc = {
+        module="equip",
+        brief="重置装备",
+        mailgen={subject=2, body=""},
+        notify={
+            flags="equip_reset"
+        },
+    }
+
+    local equip_conf_list = assert(asset.ArmorConfig_proto, "ArmorConfig_proto")
+    local equip_conf = equip_conf_list[data.id]
+    if not equip_conf then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    if args.level then
+        if data.lv <= 1 then
+            return STD_ERR.COMMON_PARM_ERR -- 参数异常
+        end
+        desc.context= string.format("重置装备等级, sid:%s, lv:%d", sid, data.lv)
+    else
+        local quality = common_fun.get_equip_quality(data.id)
+        local min_quality = -1
+        if quality > STAR_COLOR_RED then
+            min_quality = STAR_COLOR_RED
+        elseif quality > STAR_COLOR_ORANGE then
+            min_quality = STAR_COLOR_ORANGE
+        elseif quality > STAR_COLOR_PURPLE then
+            min_quality = STAR_COLOR_PURPLE
+        else
+            return STD_ERR.COMMON_PARM_ERR -- 参数异常
+        end
+
+        local quality_conf_list = assert(asset.EquipmentQualityConfig_proto, "EquipmentQualityConfig_proto")
+        for i = min_quality+1, quality do
+            local quality_conf = quality_conf_list[i]
+            if not quality_conf or quality_conf.sameQualityNum > 0 then
+                return STD_ERR.COMMON_CONF_ERR -- 配置异常
+            end
+            local item_id = quality_conf.resetGoods[equip_conf.type]
+            if not item_id or item_id <= 0 then
+                return STD_ERR.COMMON_CONF_ERR -- 配置异常
+            end
+            table_insert(award_list, {id = item_id, num = quality_conf.anyQualityNum})
+        end
+        local new_id = math.floor(data.id/100)*100+min_quality
+        equip_conf = equip_conf_list[new_id]
+        if not equip_conf then
+            return STD_ERR.COMMON_CONF_ERR -- 配置异常
+        end
+        desc.context = string.format("重置装备突破等级, sid:%s, id:%d==>%d", sid, data.id, new_id)
+        temp_data.id = new_id
+    end
+
+    if data.lv > 1 then
+        local errno = reset_level(equip_conf.type, data.lv, award_list)
+        if errno ~= 0 then
+            return 
+        end
+        temp_data.lv = 1
+    end
+    desc.detail = award_list
+    reward(character, desc)
+    d.list[sid] = temp_data
+    _M.persist(character)
+    return 0, {data = temp_data}
+end
+
+
+-- 装备背包数据
+function REQUEST.equip_get_data(character, args)
+    return func_ret("equip_get_data", character, args)
+end
+
+-- 装备升级
+function REQUEST.equip_upgrade(character, args)
+    return func_ret("equip_upgrade", character, args)
+end
+
+-- 装备升星
+function REQUEST.equip_upgrade_star(character, args)
+    return func_ret("equip_upgrade_star", character, args)
+end
+
+-- 一键升星
+function REQUEST.equip_onekey_upgrade_star(character, args)
+    return func_ret("equip_onekey_upgrade_star", character, args)
+end
+
+-- 重置
+function REQUEST.equip_reset(character, args)
+    return func_ret("equip_reset", character, args)
+end
+
+function MODULE.get_equip_data(character, sid)
+    local d = _M.assert_get(character)
+    return d.list[sid]
+end
+
+return MODULE

+ 62 - 0
project/model/example.lua

@@ -0,0 +1,62 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local stringify = require "stringify"
+
+local module_name = "exampledata"
+local _M = schema.new(module_name, {
+    times = 0,
+    stage = 0,
+})
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据 在这里把数据初始化好
+function MODULE.parse(character)
+    local d = _M.load(character)
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    local d = _M.assert_get(character)
+    local u = _M.assert_runtime(character)
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+    local d = _M.assert_get(character)
+    logger.test("%s:ready, %s", module_name, stringify(d or {}))
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+
+end
+
+
+return MODULE

+ 155 - 0
project/model/gm.lua

@@ -0,0 +1,155 @@
+--[[
+    客户端数据保存
+]]
+
+local skynet    = require "skynet"
+local schema    = require "model.schema"
+local logger    = require "logger"
+local cjson     = require "cjson"
+local asset     = require "model.asset"
+local stringify = require "stringify"
+local reward    = require "model.reward"
+
+local currency
+local adventure
+local relic
+
+local trace   = logger.trace
+
+local CMD      = {}
+local REQUEST  = {}
+local gm  = {}
+local cjson_decode   = cjson.decode   -- 解码
+local cjson_encode   = cjson.encode   -- 编码
+
+local _M = schema.new('gm', {
+})
+
+---------------------------------------------------------------------
+
+function gm.list_request_interests() return REQUEST end
+function gm.list_command_interests() return CMD end
+--
+function gm.parse(character)
+    _M.load(character)
+end
+
+function gm.launch(character)
+    currency = currency or require "model.currency"
+    adventure = adventure or require "model.adventure"
+end
+
+function gm.monitor(character)
+  -- TODO: 侦听事件
+  --character.monitor("", function(_, resettime)
+  --end)
+end
+
+
+-------------------------------- CMD ---------------------------------
+-- TODO: 道具处理
+function CMD.gm_item(character, args)
+end
+
+-- TODO: 调用制定功能模块
+--[[
+  args[1] = model路径(player 中填写的路径)
+  args[2] = 功能名
+  args[...] = 参数
+]]
+
+function CMD.gm_model(character, args)
+  logger.trace("args = %s", stringify(args))
+  local m = require(args[1])
+  if m and m[args[2]] then
+    local ret = {m[args[2]](character, table.unpack(args,3))}
+    return {0, cjson_encode(ret)}
+  else
+    return {1,"错误的路径名/功能名"}
+  end
+end
+
+function CMD.gm_award(character, args)
+    if type(args) ~= "table" then
+        return 2, "失败"
+    end
+    local detail = {}
+    for i = 1, #args, 2 do
+        local id = args[i]
+        local num = args[i+1]
+        if id and num then
+            table.insert(detail, {id = id, num = num})
+        end
+    end
+
+    -- 发奖励
+    local desc = {
+        module="gm_test",
+        brief="gm_award",
+        context= "gm奖励:",
+        mailgen={subject=2, body=""},
+        notify={
+            flags="gm_award"
+        },
+        detail=detail
+    }
+    reward(character, desc)
+    return 0, "成功"
+end
+
+function CMD.gm_set_money(character, args)
+    if type(args) ~= "table" then
+        return "失败"
+    end
+    for i = 1, #args, 2 do
+        local id = args[i]
+        local num = args[i+1]
+        if id and num then
+            currency.set_money(character, id, num)
+        end
+    end
+    return "成功"
+end
+
+function CMD.set_simple_adventure(character, args)
+    if type(args) ~= "table" then
+        return "失败"
+    end
+    local id = args[1]
+    if not id or id <= 0 then
+        return "失败"
+    end
+    adventure.gm_set_simple_adventure(character, id)
+    return "成功"
+end
+
+function CMD.set_elite_adventure(character, args)
+    if type(args) ~= "table" then
+        return "失败"
+    end
+    local id = args[1]
+    if not id or id <= 0 then
+        return "失败"
+    end
+    local ok = adventure.gm_set_elite_adventure(character, id)
+    if ok then
+        return "成功"
+    else
+        return "失败"
+    end
+end
+
+function CMD.gm_add_cost(character, args)
+    if type(args) ~= "table" then
+        return "失败"
+    end
+    local num = args[1]
+    if not num or num <= 0 then
+        return "参数异常"
+    end
+    relic.gm_add_cost(character, num)
+end
+
+
+
+return gm

+ 471 - 0
project/model/hero.lua

@@ -0,0 +1,471 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local common_fun = require "model.common_fun"
+local module_fun = require "model.module_fun"
+local asset = require "model.asset"
+local payment = require "model.payment"
+local reward = require "model.reward"
+local stringify = require "stringify"
+-- local keygen   = require "model.keygen"
+local embattle 
+local equip
+-- local altar
+
+local logger_trace = logger.trace
+local table_insert = table.insert
+local math_min = math.min
+
+local MAX_NUM = 999999999999  -- 英雄最大堆叠数量
+local MAX_UPGRADE = 1000 -- 最大升级数量
+local module_name = "hero"
+local _M = schema.new(module_name, {
+    list = {
+        --[[
+            [sid] = {
+                sid = 类型id,
+                id = 模板id,
+                lv = 1,
+                equip = {穿戴的装备}
+            }
+        ]]
+    },
+})
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local tm_list = {}
+tm_list.hero_onekey_upgrade_star = 0
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    if tm_list[fname] then
+        local now = os.time()
+        if now - tm_list[fname] == 0 then
+            return {errno = STD_ERR.COMMON_CLICK_TOO_FAST} -- 点击太快了
+        else
+            tm_list[fname] = now
+        end
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据 在这里把数据初始化好
+function MODULE.parse(character)
+    local d = _M.load(character)
+    d.list = d.list or {}
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    local d = _M.assert_get(character)
+    local u = _M.assert_runtime(character)
+    embattle = embattle or require "model.embattle"
+    equip = equip or require "model.equip"
+    -- altar = altar or require "model.altar"
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+    local d = _M.assert_get(character)
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+
+end
+
+
+function MODULE.add_hero(character, id, num, collect, dispose, addition)
+    local d = _M.assert_get(character) or {}
+    local role_conf_list = assert(asset.RoleConfig_proto, "RoleConfig_proto")
+    local conf = role_conf_list[id]
+    if not conf and dispose then
+        dispose(nil, id, num)
+        return 
+    end
+
+    if num >= 1 and num <= MAX_NUM then
+        local add_num = num
+        local hero_type = tostring(common_fun.get_hero_type(id))
+        if not d.list[hero_type] then
+            d.list[hero_type] = {
+                sid = hero_type,
+                id = id,
+                lv = 1,
+                num = 0,
+            }
+            add_num = num -1;
+        end
+        d.list[hero_type].num = d.list[hero_type].num + add_num
+        character.dispatch("hero_add_num", id, num)
+        collect(GOODS_HERO, d.list[hero_type])
+        addition(GOODS_HERO, id, add_num)
+        _M.persist(character)
+        return true, add_num
+    else
+        if dispose then
+            dispose(nil, id, num)
+        end
+    end
+end
+
+function MODULE.hero_check(character, sid)
+    local d = _M.assert_get(character)
+    return d.list[sid] and true or false 
+end
+
+function MODULE.hero_get(character, sid)
+    local d = _M.assert_get(character)
+    return d.list[sid]
+end
+
+function MODULE.hero_get_list(character)
+    local d = _M.assert_get(character)
+    return d.list
+end
+
+function MODULE.hero_save(character)
+    _M.persist(character)
+end
+
+function THIS.hero_get_data(character, args)
+    local d = _M.assert_get(character)
+    local ret = {}
+    -- local altar_info = altar.get_altar_info(character)
+    -- local altar_battle = altar.get_altar_battle_info(character)
+    for sid, data in pairs(d.list) do
+        -- if altar_battle[sid] then
+        --     local temp = altar.altar_change_hero_info(altar_info, data)
+        --     table.insert(ret, temp)
+        -- else
+        --     table.insert(ret, data)
+        -- end
+        table.insert(ret, data)
+    end
+    return 0, {list = ret}
+end
+
+local function get_max_upgrade(character, cur_lv, max_lv)
+    local max_lv = max_lv or cur_lv + MAX_UPGRADE
+    local level_conf_list = assert(asset.RoleLevelConfig_proto, "RoleLevelConfig_proto")
+    local num = 0
+    local goods_list = {}
+    local flag = true
+    for lv = cur_lv+1, max_lv do
+        local level_conf = level_conf_list[lv]
+        if not level_conf then
+            break
+        end
+        for i = 1, #level_conf.goods do
+            local item_id = level_conf.goods[i]
+            local item_num = level_conf.goodNum[i]
+            if not item_id or not item_num or item_id <=0 or item_num <= 0 then
+                break
+            end
+            if not goods_list[item_id] then
+                goods_list[item_id] = module_fun.get_goods_num(character, item_id)
+            end
+            if goods_list[item_id] < item_num then
+                flag = false
+                break
+            end
+            goods_list[item_id] = goods_list[item_id] - item_num
+        end
+        if not flag then
+            break
+        end
+        num = lv - cur_lv
+    end
+    return num
+end
+
+local function reset_level(lv, list)
+    local level_conf_list = assert(asset.RoleLevelConfig_proto, "RoleLevelConfig_proto")
+    for lv = 2, lv do
+        local level_conf = level_conf_list[lv]
+        if not level_conf then
+            return STD_ERR.COMMON_SYS_ERR -- 系统异常
+        end
+        for i = 1, #level_conf.goods do
+            local item_id = level_conf.goods[i]
+            local item_num = level_conf.goodNum[i]
+            if item_id and item_num and item_num > 0 then
+                table_insert(list, {id = item_id, num = item_num})
+            else
+                break
+            end
+        end
+    end
+    return 0
+end
+
+
+function THIS.hero_upgrade(character, args)
+    local d = _M.assert_get(character)
+    local sid = args.sid 
+    local num = args.num or 0
+    if not sid then
+        return STD_ERR.COMMON_PARM_ERR  -- 参数异常
+    end
+
+    if num < 0 or num > MAX_UPGRADE then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    -- local altar_battle = altar.get_altar_battle_info(character)
+    -- if altar_battle[sid] then
+    --     return STD_ERR.COMMON_PARM_ERR  -- 参数异常
+    -- end
+
+    local data = d.list[sid]
+    local level = data.lv
+    if not data then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    local hero_conf_list = assert(asset.RoleConfig_proto, "RoleConfig_proto")
+    local hero_conf = hero_conf_list[data.id]
+    if not hero_conf then
+        logger_trace("RoleConfig_proto:%d", data.id)
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    
+    if num == 0 then
+        num = get_max_upgrade(character, level)
+        if num <= 0 then
+            return STD_ERR.HERO_MAX_LEVEL -- 超过最大等级
+        end
+    end
+
+
+    local level_conf_list = assert(asset.RoleLevelConfig_proto, "RoleLevelConfig_proto")
+    local pay_list = {}
+    for i = 1, num do 
+        local conf = level_conf_list[level+i]
+        if not conf then
+            return STD_ERR.COMMON_CONF_ERR-- 配置异常
+        end
+        for j = 1, #conf.goods do
+            local item_id = conf.goods[j]
+            local item_num = conf.goodNum[j]
+            if item_id and item_num then
+                table_insert(pay_list, {id = item_id, num = item_num})
+            end
+        end
+    end
+    local receipt = {
+        module="hero",
+        brief="角色升级",
+        context=string.format("角色id%d, 从%d升级到%d", data.id, level, level+num),
+        notify={
+            flags="hero_upgrade"
+        },
+        detail=pay_list
+    }
+    local errno = payment(character, receipt)
+    if errno ~=0 then
+        return errno
+    end
+
+    local cur_lv = data.lv+num
+    local pre_lv = data.lv
+    data.lv = data.lv+num
+    _M.persist(character)
+    character.dispatch("hero_upgrade", data.id, pre_lv, cur_lv)
+    character.dispatch("hero_upgrade_info", data)
+    return 0, {data = data}
+end
+
+
+function THIS.hero_upgrade_star(character, args)
+    local d = _M.assert_get(character) or {}
+    local sid = args.sid
+
+
+    if not sid then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local data = d.list[sid]
+    if not data then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+
+    local role_conf_list = assert(asset.RoleConfig_proto, "RoleConfig_proto")
+    local role_conf = role_conf_list[data.id]
+    if not role_conf then
+        logger_trace("RoleConfig_proto:%d", data.id)
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+    
+    local cur_quality = role_conf.quality
+    if not cur_quality then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    local max_quality = asset.DataConfig_proto[5].data1
+    if cur_quality >= max_quality then
+        return STD_ERR.HERO_START_MAX -- 达到最大突破等级
+    end
+
+
+
+    local conf_id = data.id + 1
+    
+    role_conf = role_conf_list[conf_id]
+    if not role_conf or role_conf.quality ~= cur_quality+1 then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    if data.num < role_conf.consume then
+        return STD_ERR.HERO_SAME_PAY_ERR
+    end
+   
+    data.id = conf_id
+    data.num = data.num - role_conf.consume
+    _M.persist(character)
+    return 0, {data = d.list[sid]}
+end
+
+function THIS.hero_onekey_upgrade_star(character, args)
+    local d = _M.assert_get(character)
+    return 0
+end
+
+-- 
+function THIS.hero_reset(character, args)
+    return 0
+end
+
+function THIS.hero_wear_equip(character, args)
+    local sid = args.sid
+    local list = args.list or {}
+    local d = _M.assert_get(character)
+    if not sid or not d.list[sid] then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local data = d.list[sid]
+
+    -- -- 清除冗余字段
+    -- for i = #list , 1, -1 do
+    --     if list[i] == "" then
+    --         list[i] = nil
+    --     else
+    --         break
+    --     end
+    -- end
+    if #list ~= 6 and #list ~= 0 then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local errno = equip.equip_wear(character, data, data.equip, list)
+    if errno ~= 0 then
+        return errno
+    end
+    local empty = true
+    -- 清除冗余字段
+    for i = #list , 1, -1 do
+        if list[i] ~= "" then
+            empty = false
+            break
+        end
+    end
+    if empty then
+        list = nil
+    end
+    data.equip = list
+    _M.persist(character)
+    return 0, {list = args.list, data = data}
+end
+
+-- 英雄背包数据
+function REQUEST.hero_get_data(character, args)
+    return func_ret("hero_get_data", character, args)
+end
+
+-- 英雄升级
+function REQUEST.hero_upgrade(character, args)
+    return func_ret("hero_upgrade", character, args)
+end
+
+-- 英雄升星
+function REQUEST.hero_upgrade_star(character, args)
+    return func_ret("hero_upgrade_star", character, args)
+end
+
+-- 一键升星
+function REQUEST.hero_onekey_upgrade_star(character, args)
+    return func_ret("hero_onekey_upgrade_star", character, args)
+end
+
+-- 重置
+function REQUEST.hero_reset(character, args)
+    return func_ret("hero_reset", character, args)
+end
+
+-- 装备穿戴
+function REQUEST.hero_wear_equip(character, args)
+    return func_ret("hero_wear_equip", character, args)
+end
+
+-- 英雄的特殊属性
+function MODULE.get_special_attr(character)
+    local d = _M.assert_get(character)
+    local battle_list = embattle.get_embattle_hero_list(character)
+
+    local list = {}
+    for sid in pairs(battle_list or {}) do
+        local data = d.list[sid]
+        if not data then
+            goto CONTINUE
+        end
+
+        for _, esid in ipairs(data.equip or {}) do
+            if not esid or esid == "" then
+                goto CONTINUE2
+            end
+            local equip_info = equip.get_equip_data(character, esid)
+            if not equip_info then
+                goto CONTINUE2
+            end
+
+            local equip_conf = asset.ArmorConfig_proto[equip_info.id]
+            if equip_conf and equip_conf.entry and equip_conf.entry > 0 then
+                local conf = asset.EntryConfig_proto[equip_conf.entry]
+                if conf and conf.type and conf.type >= 100 then
+                    if conf.rateArr[1] and conf.rateArr[1] > 0 then
+                        list[conf.type] = (list[conf.type] or 0) + conf.rateArr[1]
+                    end
+                end
+            end
+            ::CONTINUE2::
+        end
+        ::CONTINUE::
+    end
+    logger.test("equip get_special_attr"..stringify(list or {}))
+    return list
+end
+
+return MODULE

+ 12 - 0
project/model/keygen.lua

@@ -0,0 +1,12 @@
+local skynet = require "skynet"
+local c = require "snowflake.core"
+local prefix = nil
+local function gen()
+  return prefix .. c.gen()
+end
+
+skynet.init(function()
+  local snowflake = skynet.localname(".snowflake")
+  prefix = skynet.call(snowflake, "lua", "prefix")
+end)
+return gen

+ 23 - 0
project/model/loader.lua

@@ -0,0 +1,23 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local spawn = require "model.spawn"
+local skynet_call = skynet.call
+local module = {
+}
+
+local usercenter
+local function loader(uid)
+  assert(uid)
+  usercenter = usercenter or skynet.localname(".usercenter")
+
+  local rets = skynet_call(usercenter, "lua", "load", uid)
+  local t = spawn(true).loadfrom(rets)
+  for _, elem in ipairs(module) do
+    if elem.parse then
+      elem.parse(t)
+    end
+  end
+  return t
+end
+
+return loader

+ 221 - 0
project/model/log.lua

@@ -0,0 +1,221 @@
+local skynet = require "skynet"
+require "skynet.manager"
+
+local cjson = require "cjson"
+local skynet_send = skynet.send
+local cjson_encode = cjson.encode
+cjson.encode_sparse_array(true, 1)
+
+local log = skynet.localname('.log')
+local function record(character, opcode, context)
+    log = log or skynet.localname('.log')
+    assert(character)
+    assert(opcode)
+    local bytes = cjson_encode({
+        opcode=opcode,
+        uid=character.uid,
+        nickname=character.nickname,
+        context=context,
+        date=os.time()
+    })
+    skynet_send(log, "lua", "record", character.uid, bytes)
+end
+
+-- http传送的日志(内部开发日志记录)
+local function http_record(character, content)
+  content.channel = character.channel
+  skynet_send(log, "lua", "http_record", character.uid, content)
+end
+
+local MODULE = {}
+-- function MODULE.create(character, ipaddr,device)  -- PK3-1366 数据统计
+--   local content = {
+--     id = character.uid,
+--     account = character.account,
+--     name = character.nickname,
+--     serverid = character.sid,
+--     platform = character.platform,
+--     country = "CN",   -- 默认
+--     ip = ipaddr,
+--     device = device,
+--     create_time = os.date("%Y-%m-%d %H:%M:%S",character.createtime),
+--     opcode = "register",
+--   }
+--   http_record(character, content)
+-- end
+
+-- Reference: model.player
+-- function MODULE.login(character, ipaddr)
+--   assert(ipaddr)
+--   record(character, "login", {
+--     coins=character.coins,
+--     diamonds=character.diamonds,
+--     bind_diamonds=character.bind_diamonds,
+--     budo_point = character.budo_point,
+--     smeltpoint=character.smeltpoint,
+--     friendship=character.friendship,
+--     level=character.level,
+--     ipaddr=ipaddr,
+--     manual_exp = character.manual_exp,    --PK3-1077
+--   })
+--   local content = {   --PK3-1366 数据统计
+--     id = character.uid,
+--     login_level = character.level,
+--     login_ip = ipaddr,
+--     login_time = os.date("%Y-%m-%d %H:%M:%S"),
+--     opcode = "login",
+--   }
+--   http_record(character, content)
+-- end
+
+-- Reference: model.player
+-- function MODULE.logout(character)
+--   record(character, "logout", {
+--     coins=character.coins,
+--     diamonds=character.diamonds,
+--     bind_diamonds=character.bind_diamonds,
+--     budo_point = budo_point,
+--     smeltpoint=character.smeltpoint,
+--     friendship=character.friendship,
+--     level=character.level,
+--   })
+--   local content = {   -- PK3-1366 数据统计
+--     id = character.uid,
+--     logout_level = character.level,
+--     logout_mark = "NULL-logout",
+--     logout_time = os.date("%Y-%m-%d %H:%M:%S"),
+--     online_time = os.time() - character.lastlogin,
+--     opcode = "logout",
+--   }
+--   http_record(character, content)
+-- end
+
+function MODULE.recharge(character, amount, info)
+  assert(amount)
+  record(character, "recharge", info)
+end
+
+-- Reference: model.reward
+function MODULE.reward(character, module, brief, specific, detail)
+    assert(module)
+    assert(brief)
+    assert(detail)
+    assert(type(module) == "string")
+    assert(type(brief) == "string")
+    record(character, "reward", {
+        module=module,
+        brief=brief,
+        specific=specific,
+        detail=detail
+    })
+    -- local content = {
+    --   id = character.uid,
+    --   output_time = os.date("%Y-%m-%d %H:%M:%S"),
+    --   opcode = "output",
+    --   output_type = 1,
+    --   output_mark = module,        -- 标识
+    --   output_additional = cjson_encode(specific or {}),  -- 额外参数
+    --   output_item = cjson_encode(detail),
+    -- }
+    -- http_record(character, content)
+end
+
+-- Reference: model.payment
+function MODULE.receipt(character, module, brief, specific, receipt)
+  assert(module)
+  assert(brief)
+  assert(receipt)
+  assert(type(module) == "string")
+  assert(type(brief) == "string")
+  record(character, "receipt", {
+    module=module,
+    brief=brief,
+    specific=specific,
+    receipt=receipt
+  })
+  -- logger.trace(" #### receipt %s", stringify(receipt))
+--   local content = {
+--     id = character.uid,
+--     output_time = os.date("%Y-%m-%d %H:%M:%S"),
+--     opcode = "output",
+--     output_type = 2,
+--     output_mark = module,        -- 标识
+--     output_additional = cjson_encode(specific or {}),  -- 额外参数
+--     output_item = cjson_encode(replace_payment_arr(receipt)),
+--   }
+--   http_record(character, content)
+end
+
+-- DEMO:
+--   local module = "elf"
+--   local event = "release"
+--   local specific = {
+--     [127001]=2,
+--     [2222]=3
+--   }
+--   log.fire(character, module, event, specific)
+-- function MODULE.fire(character, module, event, specific)
+--   assert(module)
+--   assert(event)
+--   assert(type(module) == "string")
+--   assert(type(event) == "string")
+--   record(character, "event", {
+--     module=module,
+--     type=event,
+--     specific=specific
+--   })
+--   -- logger.trace(" #### module, event :%s, %s",module, event)
+--   local flag = nil
+--   local output_type = nil
+--   if specific.note then
+--     if specific.note == "elf_deduct" then         -- 精灵扣除-来源 elf模块
+--       output_type = 2         -- 支出
+--       flag = {}
+--       for sid, num in pairs(specific.pokemon) do
+--         table.insert(flag, {TYPE_ELF, sid, num})
+--       end
+--     elseif specific.note == "equip_deduct" then   -- 装备扣除-来源 bag背包模块
+--       output_type = 2         -- 支出
+--       flag = {}
+--       for _, sid in pairs(specific.equip) do
+--         table.insert(flag, {GOODS_EQUIP, sid, 1})
+--       end
+--     elseif module == "elf_rune" and event == "精灵放生销毁" then              -- 精灵放生, 符文销毁 elf_rune
+--       if specific and specific.runelist and next(specific.runelist) then
+--         output_type = 2         -- 支出
+--         flag = {}
+--         for _, info in pairs(specific.runelist) do
+--           table.insert(flag, {TYPE_RUNE, info.sid, 1})
+--         end
+--       end
+--     elseif module == "elf" and specific.note == "elflv_upgrade" then        -- elf: 精灵经验扣除
+--       output_type = 2         -- 支出
+--       flag = {}
+--       table.insert(flag, {GOODS_MONEY, MONEY_EXP1, specific.exp1 or 0})
+--     elseif specific.note == "shop_system_buy" then  -- 商店
+--       output_type = 2         -- 支出
+--       flag = {}
+--       for sid, num in pairs(specific.equip) do
+--         table.insert(flag, {GOODS_EQUIP, sid, num})
+--       end
+--     elseif specific.note == "web_delitem" then                       -- 道具扣除接口
+--       output_type = 2         -- 支出
+--       flag = specific.other
+--     end
+--   end
+--   if flag then
+--     -- logger.trace(" ################## flag %s", stringify(flag))
+--     local content = {
+--       id = character.uid,
+--       output_time = os.date("%Y-%m-%d %H:%M:%S"),
+--       opcode = "output",
+--       output_type = output_type,
+--       output_mark = module,        -- 标识
+--       output_additional = cjson_encode(specific or {}),  -- 额外参数
+--       output_item = cjson_encode(flag),
+--     }
+--     http_record(character, content)
+--   end
+-- end
+
+return MODULE

+ 404 - 0
project/model/mailbox.lua

@@ -0,0 +1,404 @@
+local skynet = require "skynet"
+local schema = require "model.schema"
+local logger = require "logger"
+local keygen   = require "model.keygen"
+local cjson    = require "cjson"
+local reward   = require "model.reward"
+local util     = require "util"
+local asset    = require "model.asset"
+local log      = require "model.log"
+local common_fun = require "model.common_fun"
+
+local equip 
+local hero
+
+cjson.encode_sparse_array(true, 1)
+local s_mailbox = skynet.localname(".mailbox")
+
+local cjson_decode = cjson.decode   -- 解码
+local cjson_encode = cjson.encode   -- 编码
+local CMD      = {}
+local REQUEST  = {}
+local mailbox  = {}
+
+local TYPE_TAB = 'table'
+local _M = schema.new('mail', {
+    tm = 0,                     -- 玩家最近全服邮件收取时间
+    list = {
+    --[[
+        [id] = {
+            read = false, 是否已读
+            attach = true, 是否有附件
+            receive = true, 是否已领取
+        }
+    ]]
+    }
+})
+
+
+function mailbox.list_request_interests() return REQUEST end
+function mailbox.list_command_interests() return CMD end
+--
+function mailbox.parse(character)
+    local d = _M.load(character)
+    if not d.list then
+        d.list = {}
+        _M.persist(character)
+    end
+end
+
+local function optimize_array(a)
+    if a and next(a) then
+      return a
+    end
+end
+
+------------------------------ mailbox -------------------------------------
+-- 更新全局邮件收取时间
+local function update_tm(character)
+    local data = _M.assert_get(character)
+    data.tm = os.time()
+    _M.persist(character)
+end
+
+local function update_state(character, id, attachment)
+    local d = _M.assert_get(character)
+    d.list[id] = {
+        attach = next(attachment or {}) and true or false,
+        read = false,
+        receive = false,
+    }
+    _M.persist(character)
+    return d.list[id]
+end
+
+-- 取出服务器中的所有邮件
+local function get_ser_mail(character)
+    local data = _M.assert_get(character)
+    local tm = os.time()
+    data.tm = (data.tm == 0) and tm or data.tm
+    _M.persist(character)
+    return skynet.call(s_mailbox, "lua", "get_all_mail", data.tm, character.uid)
+end
+
+-- 检查是否能添加成功
+local function check_bag(attachment, bag_residue)
+    for k, v in pairs(attachment) do
+        local id = v.id
+        local num = v.num
+        local item_type = common_fun.goods_type(id)
+        if item_type == GOODS_HERO then
+            bag_residue.hero = (bag_residue.hero or 0) - num
+            if bag_residue.hero <= 0 then
+                return STD_ERR.HERO_FULL
+            end
+        elseif item_type == GOODS_EQUIP then
+            bag_residue.equip = (bag_residue.equip or 0) - num
+            if bag_residue.equip <= 0 then
+                return STD_ERR.EQUIP_FULL
+            end
+        end
+    end
+    return 0
+end
+
+local function will_send_award(attachment, detail)
+    for k, v in pairs(attachment) do
+        table.insert(detail, v)
+    end
+end
+
+local function tidy_award(character, list, ret_id, detail, log)
+    local bag_residue = {       -- 剩余空间
+        hero = hero.hero_surplus(character),
+        equip = equip.equip_surplus(character),
+    }
+    local sign, temp
+    for _, v in pairs(list) do
+        temp = cjson_decode(v)
+        temp.source = temp.source or {}
+        sign = check_bag(temp.attachment, bag_residue)
+        if temp.attachment and sign == 0 then
+            will_send_award(temp.attachment, detail)
+            table.insert(log, {
+                module = temp.source.module,
+                brief = temp.source.brief,
+                context = temp.source.context,
+                detail = temp.attachment or {},
+            })
+            table.insert(ret_id, temp.id)
+        elseif not temp.attachment then
+            -- return -6           -- 邮件的附件为空
+            table.insert(ret_id, temp.id)
+        else
+            return sign
+        end
+    end
+end
+
+local function send_log(character, temp_log)
+    skynet.fork(function()
+        for _, data in pairs(temp_log) do
+            local record = {
+                currency    = data.detail.currency,
+            }
+            if next(record) then
+              log.reward(character, data.module, data.brief, data.context, record)
+            end
+        end
+    end)
+end
+
+------------------------------ mailbox -------------------------------------
+--[[
+    统一邮件发送接口
+    number                  subject: 邮件的主题, 使用参数表的 mail_proto[k].type
+    string                  body: 邮件需要的参数,多参数使用  “ ,” 分割 , 无参数,为 ""
+    table                   attachment: 邮件的附件,格式按照物品添加格式处理
+    table                   source: 邮件的来源{
+                                        module,         -- 发送模块
+                                        brief,          -- 说明
+                                        context,        -- 上下文
+                                    }
+]]
+function mailbox.send(character, subject, body, attachment, source, must_mail)
+    local id = keygen()
+    local now = os.time()
+    local content = {
+  	  	id = id,
+  	  	date = now,
+  	  	subject = subject,
+  	  	body = body,
+  	  	attachment = attachment or {},
+        source = source,
+    }
+    skynet.call(s_mailbox,"lua","save_mail", id, cjson_encode(content), character.uid, must_mail)
+    local data = update_state(character, id, attachment)
+    content.read = data.read
+    content.attach  = data.attach
+    content.receive = data.receive
+    
+    if character.send then
+        content.source = nil
+        character.send('send_mail', {list = {[1] = content}})
+    end
+end
+
+
+function mailbox.ready(character)
+    local data = _M.assert_get(character)
+    if type(data) ~= TYPE_TAB then
+        assert(false,string.format("邮件系统取出玩家数据失败 %s", character.uid))
+    end
+    -- 玩家上线将自己的地址保存到服务器
+    skynet.call(s_mailbox,"lua","online", skynet.self(), character.uid, (data.tm == 0) and os.time() or data.tm)
+    update_tm(character)
+    mailbox.noread_mail(character)
+end
+
+function mailbox.saybye(character)
+  -- TODO: 玩家下线时的处理
+    update_tm(character)
+    skynet.call(s_mailbox,"lua","offline", skynet.self(), character.uid)
+end
+
+function mailbox.launch(character)
+    equip = require "model.equip"
+    hero = require "model.hero"
+end
+
+function CMD.newmail(character, content, id)
+    -- 修改玩家全服邮件最后收取时间
+    update_tm(character)
+    -- 发送邮件
+    local temp_mail = cjson_decode(content)
+    local data = update_state(character, temp_mail.id, temp_mail.attachment)
+    temp_mail.read = data.read
+    temp_mail.attach  = data.attach 
+    temp_mail.receive = data.receive
+    temp_mail.source = nil
+    character.send('send_mail', {list = {[1] = temp_mail}})
+end
+
+-- 删除邮件
+function mailbox.del_mail(character, list)
+    if #list <= 0 then
+        return STD_ERR.COMMON_PARM_ERR
+    end
+    local d = _M.assert_get(character)
+    local del = {}
+    for _, v in ipairs(list or {}) do
+        if d.list[v] and 
+            (d.list[v].attach and d.list[v].receive or false) or 
+            (not d.list[v].attach and d.list[v].read) or 
+            false then
+            table.insert(del, v)
+            d.list[v] = nil
+        end
+    end
+
+    if not next(del) then
+        return STD_ERR.COMMON_PARM_ERR
+    end
+
+    local err = skynet.call(s_mailbox,"lua","del_mail", character.uid, del)
+    _M.persist(character)
+    return err, del
+end
+
+-- 取出玩家邮件
+function mailbox.get_mail(character)
+    local err, list = get_ser_mail(character)
+    local temp = {}
+    local temp_mail = {}
+    local d = _M.assert_get(character)
+    for k, v in pairs(list or {}) do
+        temp_mail = cjson_decode(v)
+        local data = d.list[temp_mail.id]
+        if not data then
+            data = update_state(character, temp_mail.id, temp_mail.attachment)
+        end
+        temp_mail.attach = data.attach
+        temp_mail.read = data.read
+        temp_mail.receive = data.receive
+        temp_mail.source = nil
+        table.insert( temp, temp_mail)
+    end
+    return err, temp
+end
+
+-- TODO: 统计未读取邮件,并推送给客户端
+function mailbox.noread_mail(character)
+    local err, list = get_ser_mail(character)
+    local temp_mail = {}
+    local d = _M.assert_get(character)
+    local noread = 0    -- 未读的文件
+    local needread = 0  -- 有文本的文件
+    local sum = 0
+    local change = false
+    local temp_list = {}
+    for _, v in pairs(list or {}) do
+        temp_mail = cjson_decode(v)
+        local id = temp_mail.id
+        temp_list[id] = 1
+
+        if not d.list[id] then
+            change = true
+            d.list[id] = {
+                read = false,
+                attach = next(temp_mail.attachment or {}) and true or false
+            }
+        end 
+        
+        if not d.list[id].read  then
+            noread = noread + 1
+        end
+
+        if d.list[id].attach and not d.list[id].receive then
+            needread = needread + 1    
+        end
+
+        sum = sum + 1
+    end
+
+    -- 清楚因过期被删掉的邮件
+    for k in pairs(d.list or {}) do
+        if not temp_list[k] then
+            change = true
+            d.list[k] = nil
+        end
+    end
+
+    if change then
+        _M.persist(character)
+    end
+    character.send("simple_mail", {num = sum, noread = noread, needread = needread})
+    return sum, noread
+end
+
+-- 提取邮件附件
+function mailbox.get_mail_goods(character, list, bonekey)
+    if #list <= 0 then
+        return STD_ERR.MAILBOX_PARM_LIMIT            -- 参数错误
+    end
+
+    local del_list = {}
+    local d = _M.assert_get(character)
+    for _, v in ipairs(list or {}) do
+        if d.list[v] and d.list[v].attach and not d.list[v].receive then
+            table.insert(del_list, v)
+        end
+    end
+
+
+    local err, list = skynet.call(s_mailbox,"lua","get_goods",character.uid, del_list)
+    if not err then
+        return STD_ERR.MAILBOX_FAIL          -- 取出邮件失败
+    end
+    local ret_id   = {}
+    local detail   = {}
+    local temp_log = {}
+    local err = 0
+
+    -- 整理邮件奖励
+    err = tidy_award(character, list, ret_id, detail, temp_log) or 0
+    if #ret_id > 0 then
+        local _, lost = reward(character, {
+            module = "mailbox",
+            brief = 'mailbox.get_mail_goods',        --数据统计
+            context = string.format("批量领取邮件的附件,领取邮件的数量 num = %s", #ret_id),
+            detail = detail,
+            notify = {flags="get_mail_goods"},
+        })
+        assert(lost == nil, string.format("邮件系统发送奖励,奖励物品超过了背包的上限。ids = %s", cjson_encode(ret_id)))
+
+        -- 发送log
+        send_log(character, temp_log)
+        local flag = false
+        for _, mid in ipairs(ret_id) do
+            d.list[mid] = d.list[mid] or {}
+            d.list[mid].receive = true
+            flag = true
+        end
+        if flag then
+            _M.persist(character)
+        end
+    end
+    return err, optimize_array(ret_id)
+end
+
+function mailbox.flag(character, list)
+    list = list or {}
+    local d = _M.assert_get(character)
+    for _, mid in ipairs(list) do
+        d.list[mid].read = true
+    end
+    _M.persist(character)
+    return {errno = 0}
+end
+
+-- 取出玩家邮件
+function REQUEST.get_mail(character)
+    local err, list = mailbox.get_mail(character)
+    return {errno = err, list = list}
+end
+
+-- 取出邮件附件
+function REQUEST.get_mail_goods(character, args)
+    local err, ids = mailbox.get_mail_goods(character, args.list, args.bonekey)
+    return {errno = err, list = ids}
+end
+
+-- 删除邮件
+function REQUEST.del_mail(character, args)
+    local err, list = mailbox.del_mail(character, args.list)
+    return {errno = err, list = list}
+end
+
+-- 已读邮件标记
+function REQUEST.flag_mail(character, args)
+    local err = mailbox.flag(character, args.list)
+    return {errno = err}
+end
+
+return mailbox

+ 502 - 0
project/model/manual/init.lua

@@ -0,0 +1,502 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local stringify = require "stringify"
+local asset = require "model.asset"
+local module_fun = require "model.module_fun"
+local payment = require "model.payment"
+local reward = require "model.reward"
+local util = require "util"
+local rechargemod = require "model.rechargemod"
+
+local mailbox
+local currency
+
+
+local Register_model = {
+    [MODULE_ID_RELIC_MANUA] = require "model.manual.relic_manual"
+}
+
+local FIRST_MANUAL = 1      -- 免费
+local SECOND_MANUAL = 2     -- 强者战令
+local THIRD_MANUAL = 3      -- 王者战令
+
+-- 请和altar中保持一致
+local EQUIP_FREE = 2
+local HERO_FREE = 3 
+
+local module_name = "manual"
+local module_id = 38
+local _M = schema.new(module_name, {
+    open = false,
+    round = 1,
+    open_time = 0,
+    level = 0,
+    exp = 0,
+    list = {
+        -- {
+        --     lock = true,
+        --     award = 0,
+        -- }
+    },
+    privilege = { -- 英雄置换特权和装备置换特权
+        
+    }
+})
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local function func_ret(fname, character, args)
+    if args.moduleid and Register_model[args.moduleid] then
+        local f = Register_model[args.moduleid][fname]
+        if not f then
+            logger.error("func_ret not moduleid:%d, fname:%s !!!", args.moduleid, fname)
+            return {errno = STD_ERR.COMMON_SYS_ERR}
+        end
+        return f(character, args)
+    end 
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    ret.moduleid = module_id
+    return ret
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据 在这里把数据初始化好
+function MODULE.parse(character)
+    local d = _M.load(character)
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+    local d = _M.assert_get(character) or {}
+    character.monitor("currency_add", function(_, id, num)
+        if id == CURRENCY_ID_DACTIVITY then
+            THIS.manual_upgrade(character, num)
+        end
+    end)
+
+    character.monitor("daily_refresh", function()
+        if d.open then
+            THIS.reset_model(character)
+        end
+    end)
+
+    -- 特权重置
+    character.monitor("weekly_refresh", function()
+        if d.open and d.privilege then
+            d.privilege = {}
+            _M.persist(character)
+        end
+    end)
+
+    character.monitor("simple_battle_pass", function(_, id)
+        if id == asset.DataConfig_proto[12].data1 then
+            THIS.reset_model(character)
+        end
+    end)
+    
+    character.monitor("recharge.money",function(_, cfid, orderid, moneynum, giftid, id, pos)
+        if module_id == id then
+            THIS.manual_unlock(character, pos+1)
+        end
+    end)
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    currency = currency or require "model.currency"
+    mailbox = mailbox or require "model.mailbox"
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+    local d = _M.assert_get(character) or {}
+    THIS.reset_model(character)
+    if d.open and d.level > 0 and d.exp > 0 then
+        THIS.manual_upgrade(character, 0)
+    end
+    logger.test("%s:ready, %s", module_name, stringify(d or {}))
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+
+end
+
+-- 战令特权剩余次数
+function MODULE.manual_privilege_times(character, ptype)
+    local d = _M.assert_get(character) or {}
+    if not d.open then
+        return 0
+    end
+    local data, data_conf
+    if ptype == EQUIP_FREE then
+        data = d.list[SECOND_MANUAL]
+        data_conf = asset.DataConfig_proto[37]
+    elseif ptype == HERO_FREE then
+        data = d.list[THIRD_MANUAL]
+        data_conf = asset.DataConfig_proto[38]
+    else
+        return 0
+    end
+    if not data or data.lock then
+        return 0
+    end
+    d.privilege = d.privilege or {}
+    d.privilege[ptype] = d.privilege[ptype] or 0
+    return math.max(0, data_conf.data1 - d.privilege[ptype])
+end
+
+-- 战令特权使用
+function MODULE.manual_privilege_use(character, ptype)
+    local d = _M.assert_get(character) or {}
+    d.privilege = d.privilege or {}
+    d.privilege[ptype] = d.privilege[ptype] or 0
+    d.privilege[ptype] = d.privilege[ptype] + 1
+    _M.persist(character)
+    return 0
+end
+
+function THIS.reset_model(character)
+    local d = _M.assert_get(character) or {}
+    if not d.open then
+        d.round = 1
+        d.open = true
+        d.exp = currency.get_money(character, CURRENCY_ID_DACTIVITY)
+    else
+        local now = os.time()
+        local open_time = util.today(d.open_time)
+        local day = (now-open_time)/DAY_SEC
+        if day < asset.DataConfig_proto[12].data2 then
+            return 
+        end
+        local award_list =  THIS.get_award_list(character)
+        if next(award_list) then
+            local desc = {
+                module = "manual",
+                brief = "战令自动发奖",
+                context = "战令自动发奖,round:"..d.round,
+            }
+            mailbox.send(character, 6, "", award_list, desc)
+        end
+        d.round = (d.round or 0) + 1
+        d.exp = 0
+    end
+    d.privilege = {}
+    d.open_time = util.today() + 12 * 3600
+    d.level = 0
+    d.list = {}
+    for i = FIRST_MANUAL, THIRD_MANUAL do
+        d.list[i] = {
+            lock = true,
+            award = -1,
+        }
+    end
+    d.list[FIRST_MANUAL].lock = false
+    if d.exp > 0 then
+        THIS.manual_upgrade(character)
+    else
+        _M.persist(character)
+    end
+    character.send("manual_notify", {open = d.open, open_time = util.today(d.open_time), level = d.level, exp = d.exp, round = d.round, list = d.list})
+end
+
+function THIS.get_round(character)
+    local d = _M.assert_get(character) or {}
+    local round = 1
+    for _, v in pairs(asset.ManualConfig_proto) do
+        if v.sign > round then
+            round = v.sign
+        end
+    end
+    return math.min(round, d.round and d.round or 1)
+end
+
+function THIS.get_conf_list(round)
+    local ret = {}
+    for _, conf in pairs(asset.ManualConfig_proto) do
+        if conf.sign == round then
+            ret[conf.level] = conf
+        end
+    end
+    return ret
+end
+
+function THIS.manual_upgrade(character, add)
+    local d = _M.assert_get(character) or {}
+    local round = THIS.get_round(character)
+    local conf_list = THIS.get_conf_list(round)
+    local level = d.level
+    local exp = d.exp + (add or 0)
+
+    while(true) do
+        local conf = conf_list[level+1]
+        if not conf then
+            break 
+        end
+
+        if conf.exp > exp then
+            break 
+        else
+            level = level + 1
+            exp = exp - conf.exp
+        end
+    end
+    if level ~= d.level or exp ~= d.exp then
+        d.level = level 
+        d.exp = exp
+        _M.persist(character)
+    end
+end
+
+function THIS.manual_buy_exp(character, args)
+    local d = _M.assert_get(character) or {}
+    if not d.open then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    local data_conf = asset.DataConfig_proto[13]
+    if not data_conf then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    local round = THIS.get_round(character)
+    local conf_list = THIS.get_conf_list(round)
+    if not conf_list[d.level + 1] then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    local pay_list = {{id = CURRENCY_ID_DIA, num = data_conf.data2}}
+    local receipt = {
+        module="manual",
+        brief="购买战令经验",
+        context="购买战令经验",
+        notify={
+            flags="manual_buy_exp"
+        },
+        detail=pay_list
+    }
+    -- 检查消耗
+    local errno = module_fun.paymeny_check(character, pay_list)
+    if errno ~= 0 then
+        return errno
+    end
+
+    payment(character, receipt)
+    THIS.manual_upgrade(character, data_conf.data1)
+    return 0, {level = d.level, exp = d.exp}
+end
+
+function THIS.manual_get_data(character, args)
+    local d = _M.assert_get(character)
+    return 0, {open = d.open, open_time = util.today(d.open_time), level = d.level, exp = d.exp, round = d.round, list = d.list}
+end
+
+
+function THIS.get_award_list(character)
+    local d = _M.assert_get(character) or {}
+    local award_list = {}
+    if not d.open then
+        return award_list
+    end
+    local round = THIS.get_round(character)
+    local max_award = -1
+    for _, conf in pairs(asset.ManualConfig_proto) do
+        if conf.sign == round then
+            for k, v in ipairs(d.list) do
+                if not v.lock and v.award < conf.level and d.level >= conf.level then
+                    local award_conf
+                    if k == FIRST_MANUAL then
+                        award_conf = conf.brave
+                    elseif k == SECOND_MANUAL then
+                        award_conf = conf.strong
+                    elseif k == THIRD_MANUAL then
+                        award_conf = conf.king
+                    else
+                        assert(false)
+                    end
+                    max_award = math.max(max_award, conf.level)
+                    for i = 1, #award_conf, 2 do
+                        local item_id = award_conf[i]
+                        local item_num = award_conf[i+1]
+                        if item_id and item_num and item_id > 0 and item_num > 0 then
+                            table.insert(award_list, {id = item_id, num = item_num})
+                        end
+                    end                     
+                end
+            end
+        end
+    end
+    return award_list, max_award
+end
+
+function THIS.manual_get_award(character, args)
+    local d = _M.assert_get(character) or {}
+    if not d.open then  
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local award_list, max_award =  THIS.get_award_list(character)
+    if not next(award_list) then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    if not max_award then
+        return STD_ERR.COMMON_SYS_ERR -- 系统异常
+    end
+
+    -- 发奖励
+    local desc = {
+        module="manual",
+        brief="战令奖励",
+        context= "战令奖励",
+        mailgen={subject=2, body=""},
+        notify={
+            flags="manual_get_award"
+        },
+        detail=award_list
+    }
+
+    reward(character, desc)
+    for k, v in ipairs(d.list) do
+        if not v.lock then
+            v.award = max_award
+        end
+    end
+    _M.persist(character)
+    return 0, {award = max_award}
+end
+
+function THIS.manual_unlock(character, type)
+    local d = _M.assert_get(character) or {}
+    if not d.open then
+        return 
+    end
+    local data = d.list[type]
+    if not data then
+        return 
+    end
+    data.lock = false
+    if d.privilege and d.privilege[type] and d.privilege[type] > 0 then
+        d.privilege[type] = 0
+    end
+    _M.persist(character)
+    character.send("manual_notify", {open = d.open, open_time = util.today(d.open_time), level = d.level, exp = d.exp, round = d.round, list = d.list})
+end
+
+function THIS.manual_buy(character, args)
+    local d = _M.assert_get(character) or {}
+    if not d.open then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    if not args.giftid then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常    
+    end
+
+    local manual_type
+    if args.giftid == 50001 then
+        manual_type = SECOND_MANUAL
+    elseif args.giftid == 50002 then
+        manual_type = THIRD_MANUAL
+    else
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local data = d.list[manual_type]
+    if not data then
+        return STD_ERR.COMMON_SYS_ERR -- 系统异常
+    end
+
+    if not data.lock then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    local conf = asset.GiftConfig_proto[args.giftid]
+    if not conf then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+    
+    if conf.type ~= module_id then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local pay_list = {}
+    if conf.spendtype == PAY_TYPE_FREE then
+        pay_list = nil
+    elseif conf.spendtype == PAY_TYPE_ITEM then 
+        table.insert(pay_list, {id = conf.spend, num = conf.num})
+    elseif conf.spendtype == PAY_TYPE_GIFT then
+        rechargemod.get_orderid(character, conf.ID)
+        return 0
+    end
+    if pay_list then
+        if not next(pay_list) then
+            return STD_ERR.COMMON_CONF_ERR -- 配置异常
+        end
+        local receipt = {
+            module=module_name,
+            brief="购买遗迹战令",
+            context= "购买遗迹战令",
+            mailgen={subject=2, body=""},
+            notify={
+                flags="manual_buy"
+            },
+            detail=pay_list
+        }
+        local errno = payment(character, receipt)
+        if errno ~= 0 then
+            return errno
+        end
+    end
+    THIS.manual_unlock(character, manual_type)
+    return 0
+end
+
+
+function REQUEST.manual_get_award(character, args)
+    return func_ret("manual_get_award", character, args)
+end
+
+function REQUEST.manual_buy_exp(character, args)
+    return func_ret("manual_buy_exp", character, args)
+end
+
+function REQUEST.manual_get_data(character, args)
+    return func_ret("manual_get_data", character, args)
+end
+
+function REQUEST.manual_buy(character, args)
+    return func_ret("manual_buy", character, args)
+end
+
+function MODULE.get_red_point(character)
+    local d = _M.assert_get(character) or {}
+    if not d.open then
+        return false
+    end
+    local round = THIS.get_round(character)
+    for _, v in pairs(d.list) do
+        if not v.lock then
+            for _, conf in pairs(asset.ManualConfig_proto) do
+                if d.level >= conf.level and v.award < conf.level and conf.sign == round then
+                    return true, module_id
+                end 
+            end
+        end
+    end
+    return false
+end
+
+return MODULE

+ 427 - 0
project/model/manual/relic_manual.lua

@@ -0,0 +1,427 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local stringify = require "stringify"
+local asset = require "model.asset"
+local module_fun = require "model.module_fun"
+local payment = require "model.payment"
+local reward = require "model.reward"
+local util = require "util"
+local rechargemod = require "model.rechargemod"
+
+local skynet = require "skynet"
+local s_relic_manual = skynet.localname(".relic_manual")
+
+local mailbox
+local currency
+
+
+local FIRST_MANUAL = 1      -- 免费
+local SECOND_MANUAL = 2     -- 遗迹通行证
+
+local module_name = "relic_manual"
+local module_id = MODULE_ID_RELIC_MANUA
+local _M = schema.new(module_name, {
+    open = false,
+    round = 1,
+    open_time = 0,
+    level = 0,
+    exp = 0,
+    list = {
+        -- {
+        --     lock = true,
+        --     award = 0,
+        -- }
+    },
+})
+
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    ret.moduleid = module_id
+    return ret
+end
+
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据 在这里把数据初始化好
+function MODULE.parse(character)
+    local d = _M.load(character)
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+    local d = _M.assert_get(character)
+    character.monitor("currency_add", function(_, id, num)
+        if id == CURRENCY_ID_RELIC_SCORE then
+            THIS.manual_upgrade(character, num)
+        end
+    end)
+
+    character.monitor("daily_refresh", function()
+        if d.open then
+            THIS.reset_model(character)
+        end
+    end)
+
+    character.monitor("recharge.money",function(_, cfid, orderid, moneynum, giftid, id, pos)
+        if module_id == id then
+            THIS.manual_unlock(character)
+        end
+    end)
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    currency = currency or require "model.currency"
+    mailbox = mailbox or require "model.mailbox"
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+    local d = _M.assert_get(character)
+    THIS.reset_model(character)
+    if d.open and d.level > 0 and d.exp > 0 then
+        THIS.manual_upgrade(character, 0)
+    end
+    logger.test("%s:ready, %s", module_name, stringify(d or {}))
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+
+end
+
+function THIS.reset_model(character)
+    local d = _M.assert_get(character)
+    if not d.open then
+        local manual_data = skynet.call(s_relic_manual, "lua", "relic_manual_data")
+        if not manual_data then
+            return 
+        end
+        d.round = manual_data.round
+        d.open = true
+        d.exp = 0
+        d.open_time = manual_data.open_time
+    else
+        local now = os.time()
+        local open_time = util.today(d.open_time)
+        local day = (now-open_time)/DAY_SEC
+        if day < asset.DataConfig_proto[22].data2 then
+            return 
+        end
+        local manual_data = skynet.call(s_relic_manual, "lua", "relic_manual_data")
+        if manual_data.round == d.round then
+            return 
+        end
+        local award_list =  THIS.get_award_list(character)
+        if next(award_list) then
+            local desc = {
+                module = "manual",
+                brief = "战令自动发奖",
+                context = "战令自动发奖,round:"..d.round,
+            }
+            mailbox.send(character, 6, "", award_list, desc)
+        end
+        d.round = manual_data.round
+        d.exp = 0
+        d.open_time = manual_data.open_time
+    end
+    d.level = 0
+    d.list = {}
+    for i = FIRST_MANUAL, SECOND_MANUAL do
+        d.list[i] = {
+            lock = true,
+            award = -1,
+        }
+    end
+    d.list[FIRST_MANUAL].lock = false
+    if d.exp > 0 then
+        THIS.manual_upgrade(character)
+    else
+        _M.persist(character)
+    end
+    THIS.send_data(character)
+end
+
+function THIS.get_round(character)
+    local d = _M.assert_get(character)
+    local round = 1
+    for _, v in pairs(asset.RelicmanualConfig_proto) do
+        if v.sign > round then
+            round = v.sign
+        end
+    end
+    return math.min(round, d.round and d.round or 1)
+end
+
+function THIS.get_conf_list(round)
+    local ret = {}
+    for _, conf in pairs(asset.RelicmanualConfig_proto) do
+        if conf.sign == round then
+            ret[conf.level] = conf
+        end
+    end
+    return ret
+end
+
+function THIS.manual_upgrade(character, add)
+    local d = _M.assert_get(character)
+    local round = THIS.get_round(character)
+    local conf_list = THIS.get_conf_list(round)
+    local level = d.level
+    local exp = d.exp + (add or 0) 
+
+    while(true) do
+        local conf = conf_list[level+1]
+        if not conf then
+            break 
+        end
+
+        if conf.exp > exp then
+            break 
+        else
+            level = level + 1
+            exp = exp - conf.exp
+        end
+    end
+    if level ~= d.level or exp ~= d.exp then
+        d.level = level 
+        d.exp = exp
+        _M.persist(character)
+    end
+end
+
+-- function THIS.manual_buy_exp(character, args)
+--     local d = _M.assert_get(character)
+--     if not d.open then
+--         return STD_ERR.COMMON_PARM_ERR -- 参数异常
+--     end
+--     local data_conf = asset.DataConfig_proto[13]
+--     if not data_conf then
+--         return STD_ERR.COMMON_CONF_ERR -- 配置异常
+--     end
+
+--     local round = THIS.get_round(character)
+--     local conf_list = THIS.get_conf_list(round)
+--     if not conf_list[d.level + 1] then
+--         return STD_ERR.COMMON_PARM_ERR -- 参数异常
+--     end
+--     local pay_list = {{id = CURRENCY_ID_DIA, num = data_conf.data2}}
+--     local receipt = {
+--         module="relic_manual",
+--         brief="购买战令经验",
+--         context="购买战令经验",
+--         notify={
+--             flags="manual_buy_exp"
+--         },
+--         detail=pay_list
+--     }
+--     -- 检查消耗
+--     local errno = module_fun.paymeny_check(character, pay_list)
+--     if errno ~= 0 then
+--         return errno
+--     end
+
+--     payment(character, receipt)
+--     THIS.manual_upgrade(character, data_conf.data1)
+--     return 0, {level = d.level, exp = d.exp}
+-- end
+
+
+function THIS.manual_get_data(character, args)
+    local d = _M.assert_get(character) or {}
+    return 0, {open = d.open, open_time = util.today(d.open_time), level = d.level, exp = d.exp, round = d.round, list = d.list}
+end
+
+
+function THIS.get_award_list(character)
+    local d = _M.assert_get(character) or {}
+    local award_list = {}
+    if not d.open then
+        return award_list
+    end
+    local round = THIS.get_round(character)
+    local max_award = -1
+    for _, conf in pairs(asset.RelicmanualConfig_proto) do
+        if conf.sign == round then
+            for k, v in ipairs(d.list) do
+                if not v.lock and v.award < conf.level and d.level >= conf.level then
+                    local award_conf
+                    if k == FIRST_MANUAL then
+                        award_conf = conf.ordinary
+                    elseif k == SECOND_MANUAL then
+                        award_conf = conf.noble
+                    else
+                        assert(false)
+                    end
+                    max_award = math.max(max_award, conf.level)
+                    for i = 1, #award_conf, 2 do
+                        local item_id = award_conf[i]
+                        local item_num = award_conf[i+1]
+                        if item_id and item_num and item_id > 0 and item_num > 0 then
+                            table.insert(award_list, {id = item_id, num = item_num})
+                        end
+                    end                     
+                end
+            end
+        end
+    end
+    return award_list, max_award
+end
+
+function THIS.manual_get_award(character, args)
+    local d = _M.assert_get(character) or {}
+    if not d.open then  
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local award_list, max_award = THIS.get_award_list(character)
+    if not next(award_list) then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    if not max_award then
+        return STD_ERR.COMMON_SYS_ERR -- 系统异常
+    end
+
+    -- 发奖励
+    local desc = {
+        module="relic_manual",
+        brief="战令奖励",
+        context= "战令奖励",
+        mailgen={subject=2, body=""},
+        notify={
+            flags="manual_get_award"
+        },
+        detail=award_list
+    }
+
+    reward(character, desc)
+    for k, v in ipairs(d.list) do
+        if not v.lock then
+            v.award = max_award
+        end
+    end
+    _M.persist(character)
+    return 0, {award = max_award}
+end
+
+function THIS.send_data(character)
+    local d = _M.assert_get(character)
+    character.send("manual_notify", {moduleid = module_id, open = d.open, open_time = util.today(d.open_time), level = d.level, exp = d.exp, round = d.round, list = d.list})
+end
+
+function THIS.manual_unlock(character)
+    local d = _M.assert_get(character)
+    if not d.open then
+        return 
+    end
+    local data = d.list[SECOND_MANUAL]
+    if not data then
+        return 
+    end
+    data.lock = false
+    _M.persist(character)
+    THIS.send_data(character)
+end
+
+function THIS.manual_buy(character, args)
+    local d = _M.assert_get(character)
+    if not d.open then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local data = d.list[SECOND_MANUAL]
+    if not data then
+        return STD_ERR.COMMON_SYS_ERR -- 系统异常
+    end
+
+    if not data.lock then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local conf = asset.GiftConfig_proto[50050]
+    if not conf then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+    local pay_list = {}
+    if conf.spendtype == PAY_TYPE_FREE then
+        pay_list = nil
+    elseif conf.spendtype == PAY_TYPE_ITEM then 
+        table.insert(pay_list, {id = conf.spend, num = conf.num})
+    elseif conf.spendtype == PAY_TYPE_GIFT then
+        rechargemod.get_orderid(character, conf.ID)
+        return 0
+    end
+    if pay_list then
+        if not next(pay_list) then
+            return STD_ERR.COMMON_CONF_ERR -- 配置异常
+        end
+        local receipt = {
+            module=module_name,
+            brief="购买遗迹战令",
+            context= "购买遗迹战令",
+            mailgen={subject=2, body=""},
+            notify={
+                flags="manual_buy"
+            },
+            detail=pay_list
+        }
+        local errno = payment(character, receipt)
+        if errno ~= 0 then
+            return errno
+        end
+    end
+    THIS.manual_unlock(character)
+    return 0
+end
+
+
+function MODULE.manual_get_award(character, args)
+    return func_ret("manual_get_award", character, args)
+end
+
+-- function MODULE.manual_buy_exp(character, args)
+--     return func_ret("manual_buy_exp", character, args)
+-- end
+
+function MODULE.manual_get_data(character, args)
+    return func_ret("manual_get_data", character, args)
+end
+
+function MODULE.manual_buy(character, args)
+    return func_ret("manual_buy", character, args)
+end
+
+function MODULE.get_red_point(character)
+    local d = _M.assert_get(character) or {}
+    if not d.open then
+        return false
+    end
+    local round = THIS.get_round(character)
+    for k, v in pairs(d.list) do
+        if not v.lock then
+            for _, conf in pairs(asset.RelicmanualConfig_proto) do
+                if d.level >= conf.level and v.award < conf.level and conf.sign == round then
+                    return true, module_id
+                end 
+            end
+        end
+    end
+    return false
+end
+
+return MODULE

+ 93 - 0
project/model/module_fun.lua

@@ -0,0 +1,93 @@
+-- 模块通用函数
+-- 需要加载其他模块的放这里
+local common_fun = require "model.common_fun"
+local asset = require "model.asset"
+
+local currency
+
+local module_fun = {}
+
+function module_fun.get_goods_num(character, id)
+    currency = currency or require "model.currency"
+    if not id then
+        return 0
+    end
+    local gtype = common_fun.goods_type(id)
+    if gtype == GOODS_MONEY then
+        return currency.get_money(character, id)
+    end
+    return 0
+end
+
+function module_fun.reward_check(list)
+    if type(list) ~= "table" then
+        return STD_ERR.COMMON_AWARD_FORMAT_ERR
+    end
+    for _, v in ipairs(list or {}) do
+        if type(v) ~= "table" then
+            return STD_ERR.COMMON_AWARD_FORMAT_ERR
+        end
+        local id = v.id
+        local num = v.num 
+        if not id or not num or num < 0 then
+            return STD_ERR.COMMON_AWARD_FORMAT_ERR
+        end
+        local errno = common_fun.goods_check(id)
+        if errno ~= 0 then
+            return errno
+        end
+    end
+    return 0
+end
+
+function module_fun.paymeny_check(character, list)
+    currency = currency or require "model.currency"
+
+    if not next(list or {}) then
+        return STD_ERR.PAYMENT_NUM_ERR  -- 消耗数量异常
+    end
+
+    for _, v in ipairs(list) do
+        if type(v) ~= "table" then
+            return STD_ERR.PAYMENT_LIST_ERR  -- 支付格式异常
+        end
+
+        local num = v.num 
+        local id = v.id
+        if not num or not id then
+            return STD_ERR.PAYMENT_LIST_ERR  --  支付格式异常
+        end
+        if num <= 0 then
+            return STD_ERR.PAYMENT_NUM_ERR  -- 消耗数量异常
+        end
+
+        local gtype = common_fun.goods_type(id)
+        if gtype == GOODS_MONEY then
+            local errno = currency.check_money(character, id, num)
+            if errno ~= 0 then
+                return errno
+            end 
+        else
+            return STD_ERR.PAYMENT_UNKNOWN_ITEM -- 支付未注册的道具 
+        end
+
+        return  0
+    end
+end
+
+function module_fun.buy_gift(character, conf)
+    if not conf then
+        return STD_ERR.COMMON_PARM_ERR
+    end
+end
+
+function module_fun.simple_role(character)
+    return {
+        uid = character.uid,
+        nickname = character.nickname,
+        avatar = character.avatar,
+        level = character.level
+    }
+end
+
+return module_fun

+ 39 - 0
project/model/mq.lua

@@ -0,0 +1,39 @@
+-- 消息队列子系统
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+
+local mq = skynet.localname(".mq")
+local CMD = {}
+local MODULE = {}
+function MODULE.list_command_interests() return CMD end
+function MODULE.monitor(character)
+    -- 在各模块 ready 接口执行之后
+    character.monitor('ready', function()
+        -- 注册到消息中心
+        skynet.call(mq, "lua", "subscribe", character.uid)
+        -- 有些模块需要确切的知道,离线消息处理完毕;
+        local co = coroutine.running()
+        skynet.fork(function()
+            character.dispatch("mq.ready")
+            skynet.wakeup(co)
+        end)
+        skynet.wait(co)
+    end)
+
+    -- 在各模块 saybye 接口执行之前
+    character.monitor('disconnect', function()
+        -- 从消息中心注销自己
+        skynet.call(mq, "lua", "unsubscribe", character.uid)
+    end)
+
+    -- logger.trace("mq.monitor")
+end
+
+function CMD.publish(character, signame, ...)
+  logger.trace("publish: %s", signame)
+  
+  character.dispatch(signame, ...)
+end
+
+return MODULE

+ 60 - 0
project/model/payment.lua

@@ -0,0 +1,60 @@
+-- 支付模块
+local log = require "model.log"
+local common_fun = require "model.common_fun"
+local module_fun = require "model.module_fun"
+local currency
+
+
+
+
+local function payment(character, receipt, not_check)
+    assert(character)
+    assert(receipt)
+    local module = assert(receipt.module)
+    local brief = assert(receipt.brief)
+    local context = receipt.context
+    local record = {}
+    local detail = common_fun.merge_award(assert(receipt.detail))
+
+
+
+    local errno = 0
+    if not not_check then
+        errno = module_fun.paymeny_check(character, detail)
+    end
+
+    if errno ~= 0 then
+        return errno
+    end
+
+
+    local now = {}
+    local debris_list = {}
+    for _, v in ipairs(detail) do
+        local num = v.num 
+        local id = v.id
+        local gtype = common_fun.goods_type(id)
+        currency = currency or require "model.currency"
+        if gtype == GOODS_MONEY then
+            local cur = currency.pay_money(character, id, num)
+            table.insert(now, {id = id, num = cur})
+        end
+    end
+
+    -- 记录日志
+    if next(detail) then
+        log.receipt(character, module, brief, context, detail)
+    end
+
+    --向客户端发送货币
+    if next(now) then
+        character.send("pay_currency", {now = now, pay = detail})
+    end
+
+    if next(debris_list) then
+        character.send("pay_debris_nty", {list = debris_list})
+    end
+    return 0
+end
+
+return payment

+ 607 - 0
project/model/player.lua

@@ -0,0 +1,607 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local queue = require "skynet.queue"
+local util = require "util"
+local logger = require "logger"
+local stringify = require "stringify"
+local character = require "model.character"
+local log = require "model.log"
+local reward = require "model.reward"
+local payment = require "model.payment"
+local mailbox
+local shengtian
+local pbSproto = {}
+
+local skynet_retpack = skynet.retpack
+local skynet_now = skynet.now
+local skynet_sleep = skynet.sleep
+local string_format = string.format
+local traceback = debug.traceback
+local max = util.max
+local trace = logger.trace
+-- local manage_proxy = skynet.localname(".manage_proxy")
+local loginserver = skynet.localname(".loginserver")
+local s_public = skynet.localname(".public")
+
+local module = {
+    require "model.rechargemod",
+    require "model.currency",
+    require "model.role",
+    require "model.adventure",
+    require "model.mailbox",
+    require "model.hero",
+    require "model.embattle",
+    require "model.equip",
+    require "model.talent",
+    require "model.time_box",
+    
+    require "model.red_point",
+    require "model.presence",
+    -- 必须在最下面
+    require "model.mq",
+    require "model.gm",
+
+}
+
+local REQUEST = setmetatable({}, {
+    __newindex = function (t, k, v)
+        local f = pbSproto.register_msg
+        if f then
+            f(k)
+        end
+        rawset(t, k, v)
+    end}
+)
+
+local CMD = {}
+
+local ipaddr                  -- 玩家的ip地址
+local kick                    -- 将玩家踢下线的函数
+local send                    -- 发送网络消息的函数
+local throw_exception         -- 抛出异常的函数
+local lasttime                -- 接受上一条消息的时间戳
+local synchronized = queue()  -- 临界区
+local namecenter
+local usercenter
+local player = {}
+local function keepalive()
+  local args = { time=0 }
+  local lastping = 0
+  lasttime = skynet_now()
+  while true do
+    local now = skynet.now()
+    if lasttime+36000 > now then
+      lastping = max(lastping, lasttime)
+      if lastping+1000 <= now then
+        args.time = now
+        lastping = now
+        send('ping', args)
+      end
+      skynet_sleep(100)
+    else
+      kick("heartbeat timeout")
+      break
+    end
+  end
+end
+
+local function watchtime()
+  local os_time = os.time
+  local dispatch_event = character.dispatch
+  local daily_refresh = util.today(character.daily_refresh)
+  local weekly_refresh = util.today(character.weekly_refresh)
+  local today = util.today()
+  local four = today + 4*3600        -- 凌晨4点
+
+  local minute = os_time() + 60             -- 每分钟(不是零秒)
+  while true do
+    local now = os_time()
+    if now >= four then
+      dispatch_event("timer.four", four)
+      four = four + 86400 -- 明天凌晨4点
+    end
+    
+    if now >= minute then
+      dispatch_event("timer.minute", now)
+      minute = minute+60
+    end
+    if now >= daily_refresh then
+      dispatch_event("daily_refresh", now)
+      daily_refresh = util.today(now)+24*3600
+      character.set_daily_refresh(daily_refresh+12*3600)--防止冬令夏令时的问题选个中午12点作为起始时间
+    end
+
+    if now >= weekly_refresh then
+        dispatch_event("weekly_refresh", now)
+        local add_day = 1 - tonumber(os.date("%w"))
+        if add_day <= 0 then
+            add_day = add_day + 7
+        end
+        weekly_refresh = util.today(now)+add_day*24*3600
+        character.set_weekly_refresh(weekly_refresh+12*3600)--防止冬令夏令时的问题选个中午12点作为起始时间
+    end
+    dispatch_event("timer.sec", now)
+    skynet.sleep(100)
+  end
+end
+
+local isready = false
+local function ready(bfirst)
+  -- 侦听事件
+  for _, elem in ipairs(module) do
+    if elem.monitor then
+      local ok, msg = xpcall(elem.monitor, traceback, character)
+      if not ok then
+        logger.error(msg)
+      end
+    end
+  end
+
+  -- 上线处理逻辑
+  for _, elem in ipairs(module) do
+    if elem.launch then
+      local ok, msg = xpcall(elem.launch, traceback, character)
+      if not ok then
+        logger.error(msg)
+      end
+    end
+  end
+
+-- 同步角色基础数据
+  local currency = require "model.currency"
+  send('user', {
+    uid = character.uid,
+    rename_time = character.rename_time,
+    nickname = character.nickname,
+    level = character.level,
+    exp = character.exp,
+    avatar = character.avater,
+    currency = currency.get_client_data(character),
+    svrtime = os.time(),
+    createtime = character.createtime,
+    sid = character.sid,
+    bfirst = bfirst,
+
+  })
+
+    -- 前后端同步数据
+    for _, elem in ipairs(module) do
+        if elem.ready then
+            local ok, msg = xpcall(elem.ready, traceback, character)
+            if not ok then
+                logger.error(msg)
+            end
+        end
+    end
+
+ 
+
+    -- 执行到这里,标志着用户真正进入了游戏
+    isready = true
+    character.set_lastlogin(os.time())
+    -- character.dispatch("ready")
+    skynet.fork(function()
+        watchtime()
+    end)
+
+    -- mailbox = mailbox or require "model.mailbox"
+    -- local detail = {{id = CURRENCY_ID_COINS, num = 100}}
+    -- local desc = {
+    --     module = "login",
+    --     brief = "登录奖励",
+    --     context = "登录奖励",
+    -- }
+    -- mailbox.send(character, 2, "", detail, desc)
+    -- skynet.fork(function()
+    --     skynet.sleep(300)
+    --     logger.trace("login_over")
+    --     -- send('login_over',{})
+    -- end)
+    -- 检查玩家是否上报过角色创建
+    -- if not character.reported then
+    --   log.create(character, ipaddr, deviceid)
+    --   character.set_reported(os.time())
+    -- end
+    character.dispatch("ready")
+    return {errno = 0}
+end
+
+-- IOS 转移开发团队后, 用户迁移
+local IOS_MI_SIGN = 2     -- 0:功能关闭 1:生成转移标识  2:账号转移阶段
+local function migration_ios(args)
+    local ret =skynet.call(s_public, "lua","migration_ios", "migration", args,IOS_MI_SIGN)
+    if IOS_MI_SIGN ==1 then
+        return ret
+    elseif IOS_MI_SIGN ==2 then
+        if ret then
+            args.account = ret
+        end
+    else
+        logger.trace("##### migration_ios 功能暂未开启")
+    end
+end
+
+local function enter(args)
+  local sid = args.sid            -- 服务器唯一标识
+  local account = args.account    -- 帐号唯一标识(渠道分配)
+  local channel = args.channel    -- 渠道唯一标识
+
+  -- local appid = args.appid        -- App唯一标识
+  -- local platform = args.platform  -- 操作系统(pc,ios,android等)
+  -- local version = args.version    -- 客户端版本号
+  -- local deviceid = args.deviceid or "NULL-device"   -- 玩家设备号
+
+  -- 检查登录参数是否完整
+  if sid == nil       then return { errno = 255 } end
+  if account == nil   then return { errno = 255 } end
+  -- if appid == nil     then return { errno=STD_ERR.PLYAER_PARM_LIMIT or 255 } end
+  if channel == nil   then return { errno = 255 } end
+  -- if platform == nil  then return { errno=STD_ERR.PLYAER_PARM_LIMIT or 255 } end
+  -- if version == nil   then return { errno=STD_ERR.PLYAER_PARM_LIMIT or 255 } end
+
+  -- IOS 转移开发团队后用户迁移 CDPK3-297 IOS用户迁移
+  -- local migration
+  -- logger.trace("############## args.migration %s", args.migration or 'nil')
+  -- -- if (args.sdk or 0) == 4 or (args.sdk or 0) == "4" then     -- 使用苹果登录
+  -- if true then
+  --   logger.trace("####### migration_ios 处理")
+  --   migration = migration_ios(args)
+  -- end
+
+  -- 登记到用户中心
+  local errno, rets = skynet.call(usercenter, "lua", "login", args, skynet.self())
+  if errno == 1 then return { errno=STD_ERR.PLYAER_ERR_SERVERID or 8 } end -- 不支持的服务器ID
+  if errno == 2 then return { errno=STD_ERR.PLYAER_OTHER_LOGIN or 4 } end -- 其它设备正在登录
+  if errno == 3 then return { errno=STD_ERR.PLYAER_MAINTAIN or 9 } end -- 正在维护
+  assert(errno == 0)
+
+  -- 初始化 character 对象并载入角色数据
+  character.loadfrom(rets)
+  character.login_args = args     -- 玩家登录传递的参数
+  local now = os.time()
+  if character.forbidden > now then
+    return { errno=STD_ERR.PLYAER_FORBID or 3 }  -- 被禁用的帐号
+  end
+
+  -- 给模块一个解析数据的机会
+  for _, elem in ipairs(module) do
+    if elem.parse then
+      local ok, msg = xpcall(elem.parse, traceback, character)
+      if not ok then
+        logger.error(msg)
+      end
+    end
+  end
+
+  -- 生成登录log, 并关联一些函数
+  -- log.login(character, ipaddr)
+  character.send = send
+  character.kick = kick
+  character.throw_exception = throw_exception
+  local first = false
+  if character.nickname then
+    if character.channel == "shengtian" then
+        shengtian = shengtian or require "service.loginserver.shengtian"
+        local content = {
+            account = character.account,
+            uid = character.uid,
+            name = character.nickname,
+            server = character.sid,
+            level = character.level,
+            power = character.power,
+        }
+        shengtian.upload(5, content)
+    end
+  else
+    first = true
+    local ok, name = skynet.call(namecenter, "lua", "register_nickname", "", character.uid)
+    if not ok then
+        return { errno=STD_ERR.PLYAER_DUPLICATION_NAME}
+    end
+    character.set_nickname(name)
+    if character.channel == "shengtian" then
+        shengtian = shengtian or require "service.loginserver.shengtian"
+        local content = {
+            account = character.account,
+            uid = character.uid,
+            name = character.nickname,
+            server = character.sid,
+            level = character.level,
+            power = character.power,
+        }
+        shengtian.upload(1, content)
+    end
+  end
+  logger.label(string_format("<Agent %s@%s.%s>", character.channel, character.uid, character.nickname))
+  return ready(first)
+
+--   if character.nickname then
+--     character.set_nickname("player")
+--     logger.label(string_format("<Agent %s@%s.%s>", character.channel, character.uid, character.nickname))
+--     return ready()
+--   end
+--   return { errno=STD_ERR.PLYAER_NO_PLAYER}   -- 未创建角色
+end
+
+function player.start(ctx)
+  local args = assert(ctx.args)
+  ipaddr = assert(ctx.ipaddr)
+  kick = assert(ctx.kick)
+  send = assert(ctx.send)
+  throw_exception = assert(ctx.throw_exception)
+  namecenter = skynet.localname(".namecenter")
+  usercenter = skynet.localname(".usercenter")
+  pbSproto = ctx.pbSproto
+  for key in pairs(REQUEST) do
+    pbSproto.register_msg(key)  
+  end
+  for _, elem in ipairs(module) do
+    local list_request_interests = elem.list_request_interests
+    if list_request_interests then
+      local rets = list_request_interests() or {}
+      for k, f in pairs(rets) do
+        assert(not rawget(REQUEST, k),k)
+        REQUEST[k] = f
+      end
+    end
+
+    local list_command_interests = elem.list_command_interests
+    if list_command_interests then
+      local rets = list_command_interests() or {}
+      for k, f in pairs(rets) do
+        assert(not rawget(CMD, k))
+        CMD[k] = f
+      end
+    end
+  end
+
+  -- 心跳检查
+  -- skynet.fork(keepalive)
+  return enter(args)
+end
+
+local func_switch
+local function dispatch_request(pname, args)
+  local f = REQUEST[pname]
+  assert(f, string_format("Not found request '%s'", pname))
+--   func_switch = func_switch or require "model.func_switch"
+--   local errno = func_switch.check_condition(character, args)
+--   logger.trace("dispatch_request:%s",pname)
+--   if errno == 0 then
+    return f(character, args)
+--   end
+--   return {errno = errno}
+end
+
+function player.request(pname, args)
+  -- 以串行化的方式执行玩家请求(简化协程挂起带来的复杂性)
+  lasttime = skynet_now()
+  return synchronized(dispatch_request, pname, args)
+end
+
+function player.response(pname, args, callback)
+  trace("dispath response '%s'", pname)
+  lasttime = skynet_now()
+  return synchronized(callback, character, args)
+end
+
+function player.command(session, cmd, ...)
+  local f = CMD[cmd]
+  assert(f, string_format("Not found command '%s'", cmd))
+  if session == 0 then
+    f(character, ...)
+  else
+    skynet_retpack(f(character, ...))
+  end
+end
+
+function player.disconnect()
+  
+  if not isready then
+    skynet.call(usercenter, "lua", "logout", character.uid, skynet.self())
+    return
+  end
+
+    -- 先派发离线事件
+  repeat
+    local ok, msg = xpcall(character.dispatch, traceback, "disconnect")
+    if not ok then
+      logger.error(msg)
+    end
+  until 0
+
+  -- 调用每个模块的下线接口(倒序)
+  for i=#module, 1, -1 do
+    local elem = module[i]
+    if elem.saybye then
+      local ok, msg = xpcall(elem.saybye, traceback, character)
+      if not ok then
+        logger.error(msg)
+      end
+    end
+  end
+
+  local ok, msg = xpcall(function()
+    -- if character.level >= 20 and character.mission > 10030 then -- PK3-919
+    --   local backups_role = skynet.localname(".backups_role")
+
+    --   skynet.send(backups_role, "lua", "logout", character.uid,character.sid,character.backups_all())
+    -- end
+  end, traceback)
+  if not ok then
+    logger.error(msg)
+  end
+
+  -- 下线时的最后处理
+  character.set_lastlogout(os.time())
+  character.flushall()
+  -- log.logout(character, ipaddr)
+    skynet.call(usercenter, "lua", "logout", character.uid, skynet.self())
+end
+
+function REQUEST.create(_, args)
+  local nickname = args.nickname or ""  -- 角色名
+  local deviceid = args.deviceid or "NULL-device"   -- 玩家设备号
+
+  -- local elf = assert(args.elf)          -- 初始精灵
+  local len = string.len(nickname)
+  if len < 3 or len > 32 then return { errno=STD_ERR.PLYAER_LLLEGAL_NAME } end  -- 非法角色名
+  if character.nickname then return { errno=STD_ERR.PLYAER_LLLEGAL_OPERATION } end   -- 非法操作
+
+  -- 注册角色名
+--   local ok = skynet.call(namecenter, "lua", "register_nickname", nickname, character.uid)
+--   if not ok then
+--     return { errno=STD_ERR.PLYAER_DUPLICATION_NAME }    -- 角色重名
+--   end
+
+  logger.label(string_format("<Agent %s@%s.%s>", character.channel, character.uid, nickname))
+  character.set_nickname(nickname)
+  return ready()
+end
+
+-- function REQUEST.pong(_, args)
+--   -- Nothing
+-- end
+
+local facebook = nil
+-- function REQUEST.binding(_, args)
+--   local account = assert(character.account)
+--   local channel = assert(character.channel)
+--   local new_account = assert(args.cloud_id)
+--   local passwd = assert(args.cloud_pass)
+
+--   if character.fb then
+--     return { errno=6 }    -- 已绑定
+--   end
+
+--   facebook = facebook or skynet.localname(".facebook")
+--   local errno = skynet.call(facebook, "lua", "redirect", account, channel, new_account, passwd)
+--   if errno == 400 then
+--     return { errno=2 }  -- 参数错误
+--   elseif errno == 409 then
+--     return { errno=1  } -- 帐号已存在
+--   elseif errno == 502 then
+--     return { errno=5 }  -- 网络错误
+--   end
+--   character.set_fb(new_account)
+--   return { errno=0 }
+-- end
+
+-- 处理广播消息
+function CMD.broadcast(character, pname, args)
+  if isready then
+    return send(pname, args)
+  end
+end
+
+--兑换CDK
+local mailbox
+local parse
+-- function REQUEST.gdkReq(character,args)
+--   local serial = args.serial
+--   -- local cdktypenum = character.cdktypenum
+--   -- cdktypenum = cdktypenum or {}
+--   -- if cdktypenum[serial] then
+--     -- return {errno = STD_ERR.PLYAER_CDK_YET_SET}     -- CDK 已经使用过了
+--   -- end
+--   local serial = string.upper(serial)                 --PK3-1388 增加CDK统计
+--   local ok, errno, awardinfo = skynet.call(manage_proxy, "lua","rpc_call",
+--     "span_cdk","use_cdk_state",
+--     character.uid, serial, option.sid, character.nickname, character.vip   --PK3-1388 增加CDK统计 PK3-1400 VIP等级限制
+--   )
+--   if not ok then
+--     logger.trace(" 使用ckd 服务器异常报错 %s", errno)
+--     errno = STD_ERR.PLYAER_CDK_ERR
+--   end
+--   if errno ~= 0 then
+--     return {errno = errno}
+--   end
+
+--   -- 检查玩家是否使用过该cdk
+--   mailbox = mailbox or require "model.mailbox"
+--   mailbox.send(character, 20, "", awardinfo,{
+--         module = "cdk",         -- 发送模块
+--         brief = "礼包",          -- 说明
+--         context=string.format("cdk_type:%s", serial),        -- 上下文
+--   })
+--   -- cdktypenum[serial] = awardinfo
+--   -- character.set_cdktypenum(cdktypenum)
+--   return {errno = 0}
+-- end
+
+-- function REQUEST.login(character)
+--   return {errno = 2000001}  -- 异常的登陆请求
+-- end
+
+-- function REQUEST.logout(character)
+--   logger.trace("## 玩家主动离线")
+--   kick("heartbeat logout")
+--   return {errno = 0}
+-- end
+
+function REQUEST.ping(character)
+  return {time = os.time()}
+end
+
+-- TODO: 游客绑定facebook 或 google 账号
+-- function REQUEST.tourist_binding(_, args)
+--   logger.trace(" #### 请求账号绑定 %s", stringify(args))
+--   local account = args.account
+--   local token = args.token
+--   local state = args.state--1是facebook 2是google  3 IOS
+
+--   if not(account and token and state) then
+--     return {errno = STD_ERR.PLYAER_PARM_LIMIT}     -- 参数异常
+--   end
+
+--   -- 检查账号是否绑定
+--   if character.facebook then
+--     return {errno = STD_ERR.PLYAER_BING_FACEBOOK}
+--   end
+--   if character.google then
+--     return {errno = STD_ERR.PLYAER_BING_GOOGLE}
+--   end
+--   if character.ios then
+--     return {errno = STD_ERR.PLYAER_BING_IOS}
+--   end
+
+--   -- 检查玩家是否为游客
+--   if type(character.tourist) ~= "number" or character.tourist == 1 then   -- nil/1:非游客账号 2: 游客账号
+--     return {errno = STD_ERR.PLYAER_BING_NO_TOURIST}   -- 玩家非游客 绑定失败
+--   end
+
+--   local par = {channel = character.channel, token = token, account = account, appid = args.appid, sdk = nil}
+--   if state == 1 then        -- Facebook
+--     -- par.sdk = 'facebook'
+--     par.sdk = 2
+--     -- 登陆账号
+--   elseif state == 2 then    -- Google
+--     -- par.sdk = 'google'
+--     par.sdk = 3
+--   elseif state == 3 then
+--     par.sdk = 4
+--   else
+--     return {errno = STD_ERR.PLYAER_PARM_LIMIT}     -- 参数异常
+--   end
+
+--   -- Verify login token
+--   local succ = skynet.call(loginserver, "lua", "login", par)
+--   if succ then
+--     -- usercenter 账号指向修改
+--     local errno = skynet.call(usercenter, "lua", "bingding", character.channel, character.sid, account,character.uid)
+--     if errno ~= 0 then return {errno = errno} end
+--     if 1 == state then
+--       character.set_facebook(account)
+--     elseif 2 == state then
+--       character.set_google(account)
+--     elseif 3 == state then
+--       character.set_ios(account)
+--     end
+--     character.set_account(account)
+--     character.set_tourist(1)    -- 游客标识修改
+--     return {errno = 0}
+--   else
+--     return {errno = STD_ERR.PLYAER_BING_LOGIN_FAIL}     -- 登陆验证异常 登陆失败
+--   end
+-- end
+return player

+ 349 - 0
project/model/powerfunc.lua

@@ -0,0 +1,349 @@
+local asset = require "model.asset"
+local equip = require "model.equip"
+local talent = require "model.talent"
+local logger = require "logger"
+local stringify = require "stringify"
+
+local RATENUM = 10000
+
+
+local function nan_to_zero(value)
+    -- 检查值是否为 NaN
+    local key = tostring(value)
+    if key == "nan" or key == "-nan" then
+        return 0
+    else
+        return value
+    end
+end
+
+local this = {}
+
+local ENTRY = {
+    HP = 1,
+    attack = 2,
+    spellAttack = 3,
+    defense = 4,
+    spellDefense = 5,
+    attackCrit = 6,
+    critNum = 7,
+    hit = 8,
+    dodge = 9,
+    attackSpeed = 10,
+    moveSpeed = 11,
+    realAttack = 12,
+    addRoleHP = 13,
+    talAddHP = 14,
+    talAddattack = 15,
+    talAddspellAttack = 16,
+    talAdddefense = 17,
+    talAddspellDefense = 18,
+    equipAddAttack = 19,
+    talAddAllDefense = 20,
+    HPRate = 21,
+    attackRate = 22,
+    spellAttackRate = 23,
+    defenseRate = 24,
+    spellDefenseRate = 25,
+    attackCritRate = 26,
+    critNumRate = 27,
+    hitRate = 28,
+    dodgeRate = 29,
+    attackSpeedRate = 30,
+    moveSpeedRate = 31,
+    realAttackRate = 32,
+}
+
+do 
+    local o = {}
+    for k, v in pairs(ENTRY) do
+        o[v] = k
+    end
+    setmetatable(ENTRY, {__index = o})
+end
+
+local function toFixed(a, b)
+    local num = 10 ^ b
+    local ret = math.floor(a * num + 0.5)/num
+    return ret
+end
+
+function this.buildIBaseAttr(cfg, isZero)
+    local baseAttr = {
+        HP= isZero and 0 or cfg.HP,
+        attack= isZero and 0 or cfg.attack,
+        spellAttack= isZero and 0 or cfg.spellAttack,
+        realAttack= isZero and 0 or cfg.realAttack,
+        defense= isZero and 0 or cfg.defense,
+        spellDefense= isZero and 0 or cfg.spellDefense,
+        attackSpeed= isZero and 0 or cfg.attackSpeed,
+        attackCrit= isZero and 0 or cfg.attackCrit,
+        critNum= isZero and 0 or cfg.critNum,
+        hit= isZero and 0 or cfg.hit,
+        dodge= isZero and 0 or cfg.dodge,
+        moveSpeed= isZero and 0 or cfg.moveSpeed,
+    }
+    return baseAttr
+end
+
+function this.entryBaseAttrAdd(baseAttr, entryID)
+    local EntryConfig = asset.EntryConfig_proto
+    for _, id in ipairs(entryID) do
+        local entryCfg = EntryConfig[id]
+        if entryCfg.type >= ENTRY.HP and entryCfg.type <= ENTRY.realAttack then
+            local key = ENTRY[entryCfg.type]
+            baseAttr[key] = baseAttr[key] + entryCfg.parmArr[1]
+        end
+
+        if entryCfg.type == ENTRY.talAddAllDefense then
+            baseAttr.defense = baseAttr.defense + entryCfg.parmArr[1]
+            baseAttr.spellDefense = baseAttr.spellDefense + entryCfg.parmArr[1]
+        end
+    end
+end
+
+function this.initTalentAdd(character)
+    local talent_data = talent.get_talent_data(character)
+    local EntryConfig = asset.EntryConfig_proto
+    local TalentConfig = asset.TalentConfig_proto
+
+    local profession_entrys = {}
+    local all_entrys = {}
+    local addAllAttrRateArr = {
+        [ENTRY.talAddHP] = ENTRY.HP,
+        [ENTRY.talAddattack] = ENTRY.attack,
+        [ENTRY.talAddspellAttack] = ENTRY.spellAttack,
+        [ENTRY.talAdddefense] = ENTRY.defense,
+        [ENTRY.talAddspellDefense] = ENTRY.spellDefense,
+    }
+
+    for _, talent_conf in pairs(TalentConfig) do
+        local data = talent_data[talent_conf.talentType]
+        if data and data.id >= talent_conf.ID then
+            local conf = EntryConfig[talent_conf.entryID]
+            if conf and conf.type then
+                if addAllAttrRateArr[conf.type] then
+                    table.insert(all_entrys, talent_conf.entryID)
+                else
+                    for _, v in ipairs(conf.profession) do
+                        profession_entrys[v] = profession_entrys[v] or {}
+                        table.insert(profession_entrys[v], talent_conf.entryID)
+                    end
+                end
+            end
+        end  
+    end
+
+    local talentAdds = {}
+    local EntryConfig = asset.EntryConfig_proto
+    for i = 1,  6 do
+        local talentAdd =  this.buildIBaseAttr(nil, true)
+        local entryIDs = profession_entrys[i] or {}
+        this.entryBaseAttrAdd(talentAdd, entryIDs)
+
+        for _, id in ipairs(all_entrys) do
+            local entryCfg = EntryConfig[id]
+            local key = addAllAttrRateArr[entryCfg.type]
+            if key and ENTRY[key] then
+                talentAdd[ENTRY[key]] = talentAdd[ENTRY[key]] * (1+ entryCfg.rateArr[1] /RATENUM)
+            end
+        end
+        talentAdds[i] = talentAdd
+    end
+    return talentAdds
+end
+
+function this.changeBaseAttr(baseAttr, changeAttr, isAdd)
+    baseAttr.HP = isAdd and baseAttr.HP + changeAttr.HP or changeAttr.HP
+    baseAttr.attack = isAdd and baseAttr.attack + changeAttr.attack or changeAttr.attack
+    baseAttr.attackSpeed = isAdd and baseAttr.attackSpeed + changeAttr.attackSpeed or changeAttr.attackSpeed
+    baseAttr.attackCrit = isAdd and baseAttr.attackCrit + changeAttr.attackCrit or changeAttr.attackCrit
+    baseAttr.critNum = isAdd and baseAttr.critNum + changeAttr.critNum or changeAttr.critNum
+    baseAttr.defense = isAdd and baseAttr.defense + changeAttr.defense or changeAttr.defense
+    baseAttr.dodge = isAdd and baseAttr.dodge + changeAttr.dodge or changeAttr.dodge
+    baseAttr.hit = isAdd and baseAttr.hit + changeAttr.hit or changeAttr.hit
+    baseAttr.spellAttack = isAdd and baseAttr.spellAttack + changeAttr.spellAttack or changeAttr.spellAttack
+    baseAttr.spellDefense = isAdd and baseAttr.spellDefense + changeAttr.spellDefense or changeAttr.spellDefense
+    baseAttr.realAttack = isAdd and baseAttr.realAttack + changeAttr.realAttack or changeAttr.realAttack
+    baseAttr.moveSpeed = isAdd and baseAttr.moveSpeed + changeAttr.moveSpeed or changeAttr.moveSpeed
+    return baseAttr
+end
+
+function this.buildIEquip(equip)
+    local ArmorConfig = asset.ArmorConfig_proto
+    local curCfg = ArmorConfig[equip.id]
+    if (equip.id > 0 and not curCfg)  then
+        return
+    end
+    local baseAttr = this.buildIBaseAttr(curCfg)
+
+    --等级加成为固定值
+
+    local lvAddAttr = {"HP", "attack", "spellAttack"}
+    for _, attr in ipairs(lvAddAttr) do
+        if (baseAttr[attr] and baseAttr[attr] > 0) then
+            baseAttr[attr] = baseAttr[attr] + (equip.lv - 1) * curCfg.increaseLv
+        end
+    end
+    baseAttr.entry = curCfg.entry
+    return baseAttr
+end
+    
+function this.setIBaseAttr(obj, cfg, lvAdd, equips, talentAdd)
+    --装备加成
+    local attr = this.buildIBaseAttr(nil, true) or {}
+    local allEntryArr = {}
+    if equips then
+        for _, data in pairs(equips) do
+            local add_attr = this.buildIEquip(data)
+            if add_attr then
+                this.changeBaseAttr(attr, add_attr, true)
+                if add_attr.entry > 0 then
+                    table.insert(allEntryArr, add_attr.entry)
+                end
+            end
+        end
+    end
+
+    obj.HP = (cfg.HP or 0) * lvAdd + attr.HP
+    obj.attack = (cfg.attack or 0) * lvAdd + attr.attack
+    obj.attackSpeed = (cfg.attackSpeed or 0) * lvAdd + attr.attackSpeed
+    obj.attackCrit = (cfg.attackCrit or 0) * lvAdd + attr.attackCrit
+    obj.critNum = (cfg.critNum or 0) * lvAdd + attr.critNum
+    obj.defense = (cfg.defense or 0) * lvAdd + attr.defense
+    obj.dodge = (cfg.dodge or 0) * lvAdd + attr.dodge
+    obj.hit = (cfg.hit or 0) * lvAdd + attr.hit
+    obj.spellAttack = (cfg.spellAttack or 0) * lvAdd + attr.spellAttack
+    obj.spellDefense = (cfg.spellDefense or 0) * lvAdd + attr.spellDefense
+    obj.realAttack = (cfg.realAttack or 0) * lvAdd + attr.realAttack
+    obj.moveSpeed = (cfg.moveSpeed or 0) + (attr.moveSpeed or 0)
+
+
+
+    if obj.hero then
+        local profession = cfg.profession
+        if(talentAdd and talentAdd[profession]) then
+            this.changeBaseAttr(obj, talentAdd[profession], true)
+        end
+    end
+
+    local EntryConfig = asset.EntryConfig_proto
+    -- 所有角色卡牌基础属性万分比词条加成
+    for _, entry in ipairs(allEntryArr) do
+        if entry == ENTRY.addRoleHP then
+            obj.HP = obj.HP * (1 + EntryConfig[entry].rateArr[1] / RATENUM)
+        elseif entry == ENTRY.equipAddAttack then
+            obj.attack =  obj.attack * (1 + EntryConfig[entry].rateArr[1] / RATENUM)
+        end
+    end
+
+    obj.HP = math.floor(obj.HP)
+    obj.attack = math.floor(obj.attack)
+    obj.attackSpeed = math.floor(obj.attackSpeed)
+    obj.attackCrit = math.floor(obj.attackCrit)
+    obj.critNum = math.floor(obj.critNum)
+    obj.defense = math.floor(obj.defense)
+    obj.dodge = math.floor(obj.dodge)
+    obj.hit = math.floor(obj.hit)
+    obj.spellAttack = math.floor(obj.spellAttack)
+    obj.spellDefense = math.floor(obj.spellDefense)
+    obj.realAttack = math.floor(obj.realAttack)
+    obj.moveSpeed = math.floor(obj.moveSpeed)
+end
+
+function this.getPowerByAttr(obj)
+    local defenseCoe = toFixed((1 / toFixed((1 - obj.defense / (obj.defense + 325)), 2)), 2)
+    -- 法术防御系数 1 - 法术防御/(法术防御+防御常数)
+    local spellDefenseCoe = toFixed((1 / toFixed((1 - obj.spellDefense / (obj.spellDefense + 325)), 2)), 2)
+
+    --攻速系数 攻速/比例值
+    local attackSpeedCoe = toFixed((obj.attackSpeed / RATENUM), 2)
+
+    --暴击系数 暴击伤害倍数/比例值 * 暴击率/比例值 * 2
+    local critCoe = toFixed((obj.critNum / RATENUM), 2) * toFixed((obj.attackCrit / RATENUM), 2)* 2
+
+    --命中系数 1/攻击CD/比例值 * 命中率/比例值
+    local hitCoe = toFixed((1 / toFixed((obj.attackSpeed / RATENUM), 2)), 2) * toFixed((obj.hit / RATENUM), 2)
+
+    --闪避系数 1/(1-闪避率/比例值)
+    local dodgeCoe = toFixed((1 / toFixed((1 - obj.dodge / RATENUM), 2)), 2)
+    
+
+    defenseCoe = nan_to_zero(defenseCoe)
+    spellDefenseCoe = nan_to_zero(spellDefenseCoe)
+    attackSpeedCoe = nan_to_zero(attackSpeedCoe)
+    critCoe = nan_to_zero(critCoe)
+    hitCoe = nan_to_zero(hitCoe)
+    dodgeCoe = nan_to_zero(dodgeCoe)
+
+    -- logger.trace(defenseCoe)
+    -- logger.trace(spellDefenseCoe)
+    -- logger.trace(attackSpeedCoe)
+    -- logger.trace(critCoe)
+    -- logger.trace(hitCoe)
+    -- logger.trace(dodgeCoe)
+    -- logger.trace(stringify(obj))
+    obj.power =
+        obj.attack * (1 + attackSpeedCoe + critCoe + hitCoe) * 2 +
+        obj.spellAttack * (1 + attackSpeedCoe + critCoe + hitCoe) * 2 +
+        obj.HP * (1 + defenseCoe + spellDefenseCoe + dodgeCoe) * 0.8
+    --取地板值
+    obj.power = math.floor(obj.power)
+    return obj.power
+end
+
+function this.buildIRole(character, hero)
+    local RoleConfig = asset.RoleConfig_proto
+    local iRole = {}
+    if hero.id > 0 and not RoleConfig[hero.id] then
+        return 0
+    end
+    local baseAttr = this.buildIBaseAttr(RoleConfig[hero.id])
+    local talents = this.initTalentAdd(character)
+    iRole = {
+        cfg = RoleConfig[hero.id],
+        hero = hero,
+        power =  0,
+        equips = {},
+    }
+    setmetatable(iRole, {__index = baseAttr})
+    for k, sid in pairs(hero.equip or {}) do
+        if sid and sid ~= "" then
+            local equip_data = equip.equip_get(character, sid)
+            if equip_data then
+                iRole.equips[k] = equip_data
+            end
+        end
+    end
+    -- logger.trace(stringify(iRole.equips))
+    --比例值(万分比)
+    --等级加成 1 + 等级/100
+    local lvAdd = 1 + toFixed(((iRole.hero.lv - 1) / 100), 2)
+    this.setIBaseAttr(iRole, iRole.cfg, lvAdd, iRole.equips, talents)
+    -- 物理防御系数 1 - 物理防御/(物理防御+防御常数)
+    return this.getPowerByAttr(iRole)
+end
+
+
+function this.buildICard(character, card)
+    local iCard
+    local CardSkillConfig = asset.CardSkillConfig_proto
+    if (card.id > 0 and not CardSkillConfig[card.id]) then
+        return 0
+    end
+
+    local baseAttr = this.buildIBaseAttr(CardSkillConfig[card.id])
+    iCard = {
+        cfg= CardSkillConfig[card.id],
+        card= card,
+        power= 0,
+    }
+
+    -- -- -----卡牌战斗力不受装备,天赋加成----
+    --等级加成 1 + 等级/比例值
+    local lvAdd = 1 + toFixed(((iCard.card.lv - 1) / 100), 2)
+    this.setIBaseAttr(iCard, iCard.cfg, lvAdd)
+    return this.getPowerByAttr(iCard)
+end
+
+return {build_role = this.buildIRole, build_card = this.buildICard}

+ 64 - 0
project/model/presence.lua

@@ -0,0 +1,64 @@
+local schema = require "model.schema"
+local skynet = require "skynet"
+local embattle = require "model.embattle"
+local module_fun = require "model.module_fun"
+-- local rankinglist
+
+local module_name = "presence"
+local _M = schema.new(module_name)
+
+local function update(character)
+    if character.ban_rank ~= 0 then
+        return 
+    end
+    if character.level <= 1 then
+        return 
+    end
+    
+    local power = embattle.battle_power(character)
+    local data = {
+        num = power,
+        num2 = 0,
+        role = module_fun.simple_role(character),
+    }
+    -- skynet.call(rankinglist, "lua", "update", RANKING_POWER, data)
+    character.set_power(power)
+    character.dispatch("power_update", power)
+end
+
+local MODULE = {}
+
+-- TODO: 解析/升级模块数据 在这里把数据初始化好
+function MODULE.parse(character)
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    -- rankinglist = skynet.localname(".rankinglist")
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+    update(character)
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+    skynet.fork(
+        function() 
+            while(true) do
+                update(character)
+                skynet.sleep(100*60*3)
+            end
+        end
+    )
+end
+
+
+
+return MODULE

+ 487 - 0
project/model/quest/init.lua

@@ -0,0 +1,487 @@
+local skynet = require "skynet"
+local schema = require "model.schema"
+local manager = require "model.quest.manager"
+local logger = require "logger"
+local asset = require "model.asset"
+local stringify = require "stringify"
+local reward = require "model.reward"
+local common_fun = require "model.common_fun"
+local currency
+
+local daily_qlst
+local weekly_qlst
+local achi_qlst
+local newx_achi
+local main_quest_conf
+local next_main
+
+local module_id = MODULE_ID_QUEST
+local module_name = "quest" 
+local _M = schema.new(module_name, {
+    resettime = 0,        -- 每日任务的重置时间
+    dlv = 0,              -- 日常刷新时等级
+    wlv = 0,              -- 周常刷新时等级
+    daward = 0,           -- 已领取的奖励
+    waward = 0,           -- 已领取的周常奖励   
+    daily = {
+    --[[
+        [id] = {
+            id = id,
+            progress = 0,
+            state = QUEST_PROGRESS,
+        }
+    ]]
+    },           -- 日常任务
+    weekly = {},          -- 周常任务
+    achievement = {},     -- 成就任务
+})
+
+local CMD = {}
+local REQUEST = {}
+local MODULE = {}
+local THIS= {}
+
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+local function quest_complate(character, data)
+    character.send("quest_cmplate_notify", {data = data})
+end
+
+-- 找出新增的任务并自动添加到玩家身上
+local function accept_new_quest(character, list, qlst)
+    local quest_func = {
+        save = _M.persist,
+        send = quest_complate,
+    }
+    local num = 0
+
+    -- manager.clear(character, DAILY_QUEST)
+    for id, conf in pairs(qlst) do
+        if list[id] then
+            manager.load(character, list[id], conf, quest_func)
+        else
+            num = num + 1
+            list[id] = {id = id}
+            manager.accept(character, list[id], conf, quest_func)
+            -- trace("Quest: 开启日常任务 %s", sid)
+        end
+    end
+    return num > 0
+end
+
+-- 找出新增的任务并自动添加到玩家身上
+local function accept_new_achi_quest(character, list, qlst)
+    local quest_func = {
+        save = _M.persist,
+        send = quest_complate,
+    }
+    local num = 0
+
+    -- manager.clear(character, DAILY_QUEST)
+    for id, conf in pairs(qlst) do
+        local id = id
+        local conf = conf
+        if list[id] and list[id].progress == QUEST_END then
+            local next_conf = newx_achi(id)
+            if next_conf then
+                conf = next_conf
+                id = next_conf.id
+            end
+        end
+
+        if list[id] then
+            manager.load(character, list[id], conf, quest_func)
+        else
+            num = num + 1
+            list[id] = {id = id}
+            manager.accept(character, list[id], conf, quest_func)
+            -- trace("Quest: 开启日常任务 %s", sid)
+        end
+    end
+    return num > 0
+end
+
+
+-- 移除不存在的任务
+local function flush_invalid_quest(character, list, qlst)
+    local invalid = {}
+    for sid, _ in pairs(list) do
+        if qlst[sid] == nil then
+            table.insert(invalid, sid)
+            -- trace("Quest: 不存在的日常任务 %d, 将被移除", sid)
+        end
+    end
+    for _, sid in ipairs(invalid) do
+        list[sid] = nil
+    end
+
+    local num = #invalid
+    return num > 0
+end
+
+local function accept_new_main_quest(character, conf)
+    manager.clear(character, MAIN_QUEST)
+    if not conf then
+        return 
+    end
+    local d = _M.assert_get(character) or {}
+    local quest_func = {
+        save = _M.persist,
+        send = quest_complate,
+    }
+    if not d.main_quest or d.main_quest.id ~= conf.id then
+        d.main_quest = {id = conf.id}
+        manager.accept(character, d.main_quest, conf, quest_func)
+        return true
+    else
+        manager.load(character, d.main_quest, conf, quest_func)
+    end
+end
+
+-- 更新任务表
+local function init_quest_list(character)
+    local d = _M.assert_get(character) or {}
+    d.dlv = d.dlv or character.level
+    local qlst = daily_qlst(d.dlv)
+    local dirty = flush_invalid_quest(character, d.daily, qlst)
+    dirty = accept_new_quest(character, d.daily, qlst) or dirty
+
+    d.wlv = d.wlv or character.level
+    qlst = weekly_qlst(d.wlv)
+    dirty = flush_invalid_quest(character, d.weekly, qlst)
+    dirty = accept_new_quest(character, d.weekly, qlst) or dirty
+
+    local achi_list = {}
+    for id in pairs(d.achievement) do
+        table.insert(achi_list, id)
+    end
+    qlst = achi_qlst(achi_list)
+    dirty = flush_invalid_quest(character, d.achievement, qlst)
+    dirty = accept_new_achi_quest(character, d.achievement, qlst) or dirty
+    if dirty then
+        _M.persist(character)
+    end
+
+    local main_quest_id = d.main_quest and d.main_quest.id
+    local main_conf = main_quest_conf(main_quest_id)
+    if main_conf then
+        accept_new_main_quest(character, main_conf)
+    end
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+function MODULE.parse(character)
+    local d = _M.load(character)
+    local addr = skynet.localname(".quest")
+    daily_qlst = function(lv) return skynet.call(addr, "lua", "daily_qlst", lv) end
+    weekly_qlst = function(lv) return skynet.call(addr, "lua", "weekly_qlst", lv) end
+    achi_qlst = function(list) return skynet.call(addr, "lua", "achi_qlst", list) end
+    newx_achi = function(id) return skynet.call(addr, "lua", "newx_achi", id)end
+    main_quest_conf = function(id) return skynet.call(addr, "lua", "get_main", id) end
+    next_main = function(id) return skynet.call(addr, "lua", "next_main", id) end
+end
+
+local function reset_daily(character)
+    local d = _M.assert_get(character)
+    d.daily = {}
+    manager.clear(character, DAILY_QUEST)
+    d.dlv = character.level
+    d.daward = 0
+    local qlst = daily_qlst(d.dlv)
+    accept_new_quest(character, d.daily, qlst)
+    _M.persist(character)
+end
+
+local function reset_weekly(character)
+    local d = _M.assert_get(character)
+    d.weekly = {}
+    manager.clear(character, WEEKLY_QUEST)
+    d.wlv = character.level
+    d.waward = 0
+    local qlst = weekly_qlst(d.dlv)
+    accept_new_quest(character, d.weekly, qlst)
+    _M.persist(character)
+end
+
+function MODULE.monitor(character)
+    character.monitor("daily_refresh", function()
+        reset_daily(character)
+    end)
+    
+    character.monitor("weekly_refresh", function()
+        reset_weekly(character)
+    end)
+end
+
+function MODULE.launch(character)
+    currency = currency or require "model.currency"
+    init_quest_list(character)
+end
+
+function MODULE.ready(character)
+    local d = _M.assert_get(character) or {}
+    logger.test("%s:ready, %s", module_name, stringify(d or {}))
+end
+
+function MODULE.saybye(character)
+end
+
+
+function THIS.quest_get_data(character, args)
+    local d = _M.assert_get(character) or {}
+    return 0, {
+        dlv = d.dlv, 
+        wlv = d.wlv, 
+        daward = d.daward, 
+        waward = d.waward, 
+        daily = common_fun.tqueue(d.daily),
+        weekly = common_fun.tqueue(d.weekly),
+        achievement = common_fun.tqueue(d.achievement),
+        main_quest = d.main_quest,
+    }
+end
+
+function THIS.quest_get_award(character, args)
+    local id = args.id 
+    if not id then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    local quest_conf_list = asset.TaskConfig_proto
+    local quest_conf = quest_conf_list[id]
+    if not quest_conf then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end 
+
+    local d = _M.assert_get(character)
+    local quest_type = quest_conf.type
+    local quest = nil
+    if quest_type == DAILY_QUEST then
+        quest = d.daily[id]
+    elseif quest_type == WEEKLY_QUEST then
+        quest = d.weekly[id]
+    elseif quest_type == ACHI_QUEST then
+        quest = d.achievement[id]
+    elseif quest_type == MAIN_QUEST then
+        quest = d.main_quest
+    end
+
+    if not quest then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    if quest.state ~= QUEST_REACHED then
+        return STD_ERR.QUEST_NOT_COMPLATE -- 任务未完成
+    end
+
+    local award_list = {}
+    for i = 1, #quest_conf.reward, 2 do
+        local item_id = quest_conf.reward[i]
+        local item_num = quest_conf.reward[i+1]
+        if item_id and item_num and item_id > 0 and item_num > 0 then
+            table.insert(award_list, {id = item_id, num = item_num})
+        end
+    end
+    
+    local desc = {
+        module="quest",
+        brief="任务奖励",
+        context= "领取任务奖励:"..id,
+        mailgen={subject=2, body=""},
+        notify={
+            flags="quest_get_award"
+        },
+        detail=award_list
+    }
+
+    quest.state = QUEST_END
+    if quest_type == ACHI_QUEST then
+        local next_quest_conf = newx_achi(quest.id)
+        if next_quest_conf then
+            manager.del(character, next_quest_conf)
+            d.achievement[id] = nil
+            d.achievement[next_quest_conf.id] = {id = next_quest_conf.id}
+            manager.accept(character, d.achievement[next_quest_conf.id], next_quest_conf, {save = _M.persist})
+            quest = d.achievement[next_quest_conf.id]
+        end
+    elseif quest_type == MAIN_QUEST then
+        local conf = next_main(quest.id)
+        accept_new_main_quest(character, conf)
+        quest = d.main_quest
+    end
+    reward(character, desc)
+    _M.persist(character)
+    return 0, {id = args.id, data = quest}
+end
+
+function THIS.quest_active_award(character, args)
+    local d = _M.assert_get(character)
+    if not args.type then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    local lv, award, active
+    if args.type == DAILY_QUEST then
+        lv = d.dlv
+        award = d.daward
+        active = currency.get_money(character, CURRENCY_ID_DACTIVITY)
+    elseif args.type == WEEKLY_QUEST then
+        lv = d.wlv
+        award = d.waward
+        active = currency.get_money(character, CURRENCY_ID_WACTIVITY)
+    else
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local max_award = 0
+    local award_list = {}
+    for _, conf in pairs(asset.ActiverewardConfig_proto) do
+        if conf.type == args.type and lv >= (conf.interval[1] or 0) and lv <= (conf.interval[2] or 0) and award < conf.active and active >= conf.active then
+            max_award = math.max(max_award, conf.active)
+            for i = 1, #conf.reward, 2 do
+                local item_id = conf.reward[i]
+                local item_num = conf.reward[i+1]
+                if item_id and item_num and item_id > 0 and item_num > 0 then
+                    table.insert(award_list, {id = item_id, num = item_num})
+                end
+            end
+        end
+    end
+
+    if not next(award_list) then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local desc = {
+        module="quest",
+        brief="活跃奖励",
+        context= "领取活跃奖励:"..args.type,
+        mailgen={subject=2, body=""},
+        notify={
+            flags="quest_active_award"
+        },
+        detail=award_list
+    }
+
+    reward(character, desc)
+    if args.type == DAILY_QUEST then
+        d.daward = max_award
+    elseif args.type == WEEKLY_QUEST then
+        d.waward = max_award
+    end
+    _M.persist(character)    
+    return 0, {type = args.type, award = max_award}
+end
+
+function REQUEST.quest_get_data(character, args)
+    return func_ret("quest_get_data", character, args)
+end
+
+function REQUEST.quest_get_award(character, args)
+    return func_ret("quest_get_award", character, args)
+end
+
+function REQUEST.quest_active_award(character, args)
+    return func_ret("quest_active_award", character, args)
+end
+
+local function set_quest_complate(character, id)
+    local d = _M.assert_get(character) or  {}
+    local quest_conf_list = asset.TaskConfig_proto
+    local quest_conf = quest_conf_list[id]
+    if not quest_conf then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end 
+    local quest_type = quest_conf.type
+    local quest = nil
+    if quest_type == DAILY_QUEST then
+        quest = d.daily[id]
+    elseif quest_type == WEEKLY_QUEST then
+        quest = d.weekly[id]
+    elseif quest_type == ACHI_QUEST then
+        quest = d.achievement[id]
+    end
+    if not quest_conf then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local quest_type = quest_conf.type
+    local quest = nil
+    if quest_type == DAILY_QUEST then
+        quest = d.daily[id]
+    elseif quest_type == WEEKLY_QUEST then
+        quest = d.weekly[id]
+    elseif quest_type == ACHI_QUEST then
+        quest = d.achievement[id]
+    end
+
+    if not quest then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    quest.progress = quest_conf.parameter
+    quest.state = QUEST_REACHED
+    quest_complate(character, quest)
+    _M.persist(character)
+    return 0
+end
+
+
+function CMD.gm_quest_complate(character, args)
+    local errno = set_quest_complate(character, args[1])
+    if errno ~= 0 then
+        return "失败"
+    end
+    return "成功"
+end
+
+function MODULE.get_red_point(character)
+    local d = _M.assert_get(character) or {}
+    local data = d.daily or {}
+    for _, v in pairs(data) do
+        if v.state == QUEST_REACHED then
+            return true, module_id
+        end
+    end
+
+    data = d.achievement
+    for _, v in pairs(data) do
+        if v.state == QUEST_REACHED then
+            return true, module_id
+        end
+    end
+
+    data = d.weekly
+    for _, v in pairs(data) do
+        if v.state == QUEST_REACHED then
+            return true, module_id
+        end
+    end
+
+    local dactive = currency.get_money(character, CURRENCY_ID_DACTIVITY)
+    local wactive = currency.get_money(character, CURRENCY_ID_WACTIVITY)
+    for _, conf in pairs(asset.ActiverewardConfig_proto) do
+        if conf.type == DAILY_QUEST and d.dlv >= (conf.interval[1] or 0) and d.dlv <= (conf.interval[2] or 0) and d.daward < conf.active and dactive >= conf.active then
+            return true, module_id
+        end
+        if conf.type == WEEKLY_QUEST and d.wlv >= (conf.interval[1] or 0) and d.wlv <= (conf.interval[2] or 0) and d.waward < conf.active and wactive >= conf.active then
+            return true, module_id
+        end
+    end
+
+end
+
+
+return MODULE

+ 529 - 0
project/model/quest/manager.lua

@@ -0,0 +1,529 @@
+-- 任务系统开发
+local stats = require "model.stats"
+local schema = require "model.schema"
+local util = require "util"
+local logger = require "logger"
+
+local min = math.min
+local max = math.max
+local _M = schema.new("quest_manager")
+local MODULE = {}
+local quest = {}
+-- 创建一个任务对象
+function quest:new(character, rawdata, conf, func)
+    local o = {
+        rawdata = rawdata, -- 存放数据 eg: d.daily/d.achievementd等
+        required = conf.required, -- 要求1
+        required2 = conf.required2,
+        model = assert(conf.model), -- 模块 eg:每日任务/成就/历练
+        send = func.send,
+        save = assert(func.save),   -- 保存数据的函数
+    }
+    if rawdata.state == QUEST_PROGRESS then -- 进行中的任务
+        if rawdata.progress >= o.required then
+            rawdata.state = QUEST_REACHED
+            rawdata.progress = o.required
+        end
+    elseif rawdata.state == QUEST_REACHED then -- 已达成任务
+        if rawdata.progress < o.required then
+            rawdata.state = QUEST_PROGRESS
+        end
+    elseif rawdata.state == QUEST_END then -- 已结束任务
+        if rawdata.progress ~= o.required then
+            rawdata.progress = o.required
+        end
+    else
+        assert(false, rawdata.state)
+    end
+    self.__index = self
+    return setmetatable(o, self)
+end
+
+-- 任务进度处理
+function quest:progress(character, value, added) 
+    assert(value > 0)
+    local rawdata = self.rawdata
+    if rawdata.state == QUEST_PROGRESS then
+        if added then
+            rawdata.progress = min(self.required, rawdata.progress + value)
+        else
+            rawdata.progress = min(self.required, max(rawdata.progress, value))
+        end
+        if rawdata.progress == self.required then
+            rawdata.state = QUEST_REACHED
+        end
+
+        if MAIN_QUEST == self.model or rawdata.state == QUEST_REACHED then
+            if self.send then
+                self.send(character, rawdata)
+            end
+        end
+        self.save(character) -- 保存数据
+        return true
+    end
+end
+
+-- 多条件
+function quest:progress2(character, value, value2, added)
+    assert(value > 0)
+    local rawdata = self.rawdata
+    if self.required2 ~= value2 then
+        return true
+    end
+    if rawdata.state == QUEST_PROGRESS then
+        if added then
+            rawdata.progress = min(self.required, rawdata.progress + value)
+        else
+            rawdata.progress = min(self.required, max(rawdata.progress, value))
+        end
+        if rawdata.progress == self.required then
+            rawdata.state = QUEST_REACHED
+        end
+        if rawdata.state == QUEST_REACHED then
+            if self.send then
+                self.send(character, rawdata)
+            end
+        end
+        self.save(character) -- 保存数据
+        return true
+    end
+end
+
+local prototype = {}
+local function assert_check(rawdata)
+    assert(rawdata.id > 0, rawdata.id)
+    assert(rawdata.progress, rawdata.id)
+    assert(rawdata.state, rawdata.id)
+end
+
+local function create_quest(character, rawdata, conf, quest_func, addnew, func)
+    if addnew then
+        rawdata.state = QUEST_PROGRESS
+        if conf.reset then
+            rawdata.progress = 0
+        else
+            rawdata.progress = assert(func())
+        end
+    end
+    assert_check(rawdata)
+    return quest:new(character, rawdata, conf, quest_func)
+end
+
+local function simple_creator(character, rawdata, conf, quest_func, addnew, ...)
+    local args = {...}
+    return create_quest(character, rawdata, conf, quest_func, addnew, function()
+        return assert(stats.fetch(character, table.unpack(args)))
+    end)
+end
+
+prototype[1] = function(character, rawdata, conf, quest_func, addnew) 
+    assert(quest_func.save)
+    local list = {}
+    function list:progress()
+        if list.time and list.time == util.today() then
+            list.time = nil
+            return 
+        end
+        if rawdata.state == QUEST_PROGRESS then
+            rawdata.progress = min(conf.required, rawdata.progress + 1)
+            if rawdata.progress == conf.required then
+                rawdata.state = QUEST_REACHED
+            end
+            if rawdata.state == QUEST_REACHED then
+                if self.send then
+                    self.send(character, rawdata)
+                end
+            end
+            quest_func.save(character) -- 保存数据
+            return true
+        end
+    end
+
+    if addnew then
+        rawdata.state = QUEST_PROGRESS
+        rawdata.progress = 1
+        list.time = util.today()
+    end
+
+    if rawdata.state == QUEST_PROGRESS then -- 进行中的任务
+        if rawdata.progress >= conf.required then
+            rawdata.state = QUEST_REACHED
+            rawdata.progress = conf.required
+        end
+    elseif rawdata.state == QUEST_REACHED then -- 已达成任务
+        if rawdata.progress < conf.required then
+            rawdata.state = QUEST_PROGRESS
+        end
+    elseif rawdata.state == QUEST_END then -- 已结束任务
+        if rawdata.progress ~= conf.required then
+            rawdata.progress = conf.required
+        end
+    else
+        assert(false, rawdata.state)
+    end
+    return list
+end
+
+prototype[2] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "enter_simple_battle") 
+end
+
+prototype[3] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "daily_battle") 
+end
+
+prototype[4] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "buy_shop_item") 
+end
+
+prototype[5] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "hero_upgrade") 
+end
+
+prototype[6] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "draw_times") 
+end
+
+prototype[7] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "recharge_times") 
+end
+
+prototype[8] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "equip_upgrade") 
+end
+
+prototype[9] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "telent_activate") 
+end
+
+prototype[10] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "skill_card_upgrade") 
+end
+
+prototype[11] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "add_stm_times") 
+end
+
+prototype[12] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "buy_stm_times") 
+end
+
+prototype[13] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "watch_ad_times") 
+end
+
+-- prototype[14] = function(character, rawdata, conf, quest_func, addnew) 
+--     return simple_creator(character, rawdata, conf, quest_func, addnew, "tower_battle") 
+-- end
+
+prototype[14] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "relic_battle_num") 
+end
+
+prototype[15] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "elite_battle") 
+end
+
+prototype[16] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "hero_upstart") 
+end
+
+prototype[17] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "equip_upstart") 
+end
+
+prototype[18] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "skill_card_upstart") 
+end
+
+prototype[19] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "building_upgrade") 
+end
+
+prototype[20] = function(character, rawdata, conf, quest_func, addnew) 
+    return create_quest(character, rawdata, conf, quest_func, addnew, function()
+        return character.level
+    end)
+end
+
+prototype[21] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "hero_max_level") 
+end
+
+prototype[22] = function(character, rawdata, conf, quest_func, addnew) 
+    return create_quest(character, rawdata, conf, quest_func, addnew, function()
+        return assert(stats.hero_lv(character, conf.require2))
+    end)
+end
+
+prototype[23] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "simple_battle_pass") 
+end
+
+prototype[24] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "elite_battle_pass") 
+end
+
+prototype[25] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "relic_battle_num") 
+end
+
+prototype[26] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "add_coin") 
+end
+
+
+prototype[27] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "consume_diamond") 
+end
+
+prototype[28] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "hero_add_num") 
+end
+
+prototype[29] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "equip_add_num") 
+end
+
+prototype[30] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "skill_card_add_num") 
+end
+
+prototype[31] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "building_explore_times") 
+end
+
+prototype[32] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "city_skill_upgrade") 
+end
+
+prototype[33] = function(character, rawdata, conf, quest_func, addnew) 
+    return simple_creator(character, rawdata, conf, quest_func, addnew, "draw_equip_times") 
+end
+
+local function update_progress(character, type, value, added)
+    local ctx = _M.assert_runtime(character)
+    for _, list in pairs(ctx.trigger) do
+        local trigger = list[type]
+        if trigger then
+            for k, child in ipairs(trigger) do
+                child:progress(character, value, added) 
+            end
+        end
+    end
+end
+
+local function update_progress2(character, type, value, value2, added)
+    local ctx = _M.assert_runtime(character)
+    for _, list in pairs(ctx.trigger) do
+        local trigger = list[type]
+        if trigger then
+            for k, child in ipairs(trigger) do
+                child:progress2(character, value, value2, added) 
+            end
+        end
+    end
+end
+
+local function register_event_interests(character)
+    local monitor = character.monitor
+    monitor("stats.login", function(_, value)
+        update_progress(character, 1, value, true)
+    end)
+
+    monitor("stats.enter_simple_battle", function(_, value)
+        update_progress(character, 2, value, true)
+    end)
+
+    monitor("stats.daily_battle", function(_, value)
+        update_progress(character, 3, value, true)
+    end)
+
+    monitor("stats.buy_shop_item", function(_, value)
+        update_progress(character, 4, value, true)
+    end)
+
+    monitor("stats.hero_upgrade", function(_, value)
+        update_progress(character, 5, value, true)
+    end)
+
+    monitor("stats.draw_times", function(_, value)
+        update_progress(character, 6, value, true)
+    end)
+
+    monitor("stats.recharge_times", function(_, value)
+        update_progress(character, 7, value, true)
+    end)
+
+    monitor("stats.equip_upgrade", function(_, value)
+        update_progress(character, 8, value, true)
+    end)
+
+    monitor("stats.telent_activate", function(_, value)
+        update_progress(character, 9, value, true)
+    end)
+
+    monitor("stats.skill_card_upgrade", function(_, value)
+        update_progress(character, 10, value, true)
+    end)
+
+    monitor("stats.add_stm_times", function(_, value)
+        update_progress(character, 11, value, true)
+    end)
+
+    monitor("stats.buy_stm_times", function(_, value)
+        update_progress(character, 12, value, true)
+    end)
+
+    monitor("stats.watch_ad_times", function(_, value)
+        update_progress(character, 13, value, true)
+    end)
+
+    -- monitor("stats.tower_battle", function(_, value)
+    --     update_progress(character, 14, value, true)
+    -- end)
+    monitor("stats.relic_battle_num", function(_, value)
+        update_progress(character, 14, value, true)
+    end)
+
+    monitor("stats.elite_battle", function(_, value)
+        update_progress(character, 15, value, true)
+    end)
+
+    monitor("stats.hero_upstart", function(_, value)
+        update_progress(character, 16, value, true)
+    end)
+    
+    monitor("stats.equip_upstart", function(_, value)
+        update_progress(character, 17, value, true)
+    end)
+
+    monitor("stats.skill_card_upstart", function(_, value)
+        update_progress(character, 18, value, true)
+    end)
+
+    monitor("stats.building_upgrade", function(_, value)
+        update_progress(character, 19, value, true)
+    end)
+
+    monitor("role_level", function(_, pre, cur)
+        update_progress(character, 20, cur, false)
+    end)
+
+    monitor("stats.hero_max_level", function(_, value)
+        update_progress(character, 21, value, false)
+    end)
+
+    monitor("stats.hero_lv", function(_, value, value2)
+        update_progress2(character, 22, value, value2, false)
+    end)
+
+    monitor("stats.simple_battle_pass", function(_, value)
+        update_progress(character, 23, value, false)
+    end)
+
+    monitor("stats.elite_battle_pass", function(_, value)
+        update_progress(character, 24, value, false)
+    end)
+
+    monitor("stats.relic_battle_num", function(_, value)
+        update_progress(character, 25, value, true)
+    end)
+
+    monitor("stats.add_coin", function(_, value)
+        update_progress(character, 26, value, true)
+    end)
+
+    monitor("stats.consume_diamond", function(_, value)
+        update_progress(character, 27, value, true)
+    end)
+
+    monitor("stats.hero_add_num", function(_, value)
+        update_progress(character, 28, value, true)
+    end)
+
+    monitor("stats.equip_add_num", function(_, value)
+        update_progress(character, 29, value, true)
+    end)
+
+    monitor("stats.skill_card_add_num", function(_, value)
+        update_progress(character, 30, value, true)
+    end)
+
+    monitor("stats.building_explore_times", function(_, value)
+        update_progress(character, 31, value, true)
+    end)
+
+    monitor("stats.city_skill_upgrade", function(_, value)
+        update_progress(character, 32, value, true)
+    end)
+
+    monitor("stats.draw_equip_times", function(_, value)
+        update_progress(character, 33, value, true)
+    end)
+
+end
+
+function MODULE.monitor(character)
+end
+
+function MODULE.parse(character)
+    _M.load(character)
+    local ctx = _M.assert_runtime(character)
+    ctx.trigger = {}
+    register_event_interests(character)
+end
+
+-- 加载一个任务
+function MODULE.load(character, rawdata, conf, quest_func)
+    local ctx = _M.assert_runtime(character)
+    local type = conf.type or 0
+    local name = conf.model
+    assert(type > 0)
+
+    local gen = assert(prototype[type], type)
+    ctx.trigger[name] = ctx.trigger[name] or {}
+    ctx.trigger[name][type] = ctx.trigger[name][type] or {}
+    table.insert(ctx.trigger[name][type], gen(character, rawdata, conf, quest_func, false))
+end
+
+-- 接受一个新任务
+function MODULE.accept(character, rawdata, conf, quest_func)
+    local ctx = _M.assert_runtime(character)
+    local type = conf.type or 0
+    local name = conf.model
+    assert(type > 0)
+
+    local gen = assert(prototype[type], type)
+    ctx.trigger[name] = ctx.trigger[name] or {}
+    ctx.trigger[name][type] = ctx.trigger[name][type] or {}
+    table.insert(ctx.trigger[name][type], gen(character, rawdata, conf, quest_func, true))
+end
+
+-- 清空内存数据
+function MODULE.clear(character, name)
+    assert(name)
+    local ctx = _M.assert_runtime(character)
+    ctx.trigger[name] = {}
+    -- logger.trace("清空任务的 %s模块的内存", name)
+end
+
+-- 清空内存数据
+function MODULE.del(character, conf)
+    local name = conf.model
+    local type = conf.type or 0
+    assert(type > 0)
+    local ctx = _M.assert_runtime(character)
+    local trigger = ctx.trigger[name] or {}
+    for k, v in ipairs(trigger[type] or {}) do
+        if v.rawdata.id == conf.id then
+            table.remove(trigger[type], k)
+            return true
+        end
+    end 
+    return false
+    -- logger.trace("清空任务的 %s模块的内存", name)
+end
+
+
+return MODULE

+ 392 - 0
project/model/rechargemod.lua

@@ -0,0 +1,392 @@
+--玩家充值处理
+local skynet = require "skynet"
+local logger = require "logger"
+local asset = require "model.asset"
+local schema = require "model.schema"
+local common_fun = require "model.common_fun"
+local reward = require "model.reward"
+local httpc = require "http.httpc"
+local cjson    = require "cjson"
+local stringify = require "stringify"
+
+local log = require "model.log"
+local shengtian
+
+local logger_info = logger.info
+
+local function deal_num(num)
+    local num1 = tonumber(string.format("%.02f", num))
+    local num2 = tonumber(string.format("%.1f", num))
+    local num3 = math.floor(num)
+    if math.abs(num1 - num2) >= 0.01 then
+        return num1
+    end
+    if math.abs(num2 - num3) >= 0.1 then
+        return num2
+    end
+    return num3    
+end
+
+local skynet_call = skynet.call
+local REQUEST = {}
+local CMD = {}
+local recharge = {}
+local rechargeser
+local _M = schema.new('rechargedata', {
+})
+
+function recharge.list_request_interests() return REQUEST end
+function recharge.list_command_interests() return CMD end
+function recharge.parse(character)
+  local d = _M.load(character)
+end
+
+-- TODO: 充值成功推送给客户端
+local function recharge_succeed(character,orderid, info)
+  logger.trace(" ######### orderid %s", orderid)
+  local ok, info = pcall(function()
+    character.send("recharge_succeed", {
+      orderid = orderid,
+    })
+    -- log
+    log.recharge(character, info.pay_price,info)  -- 数据统计
+  end)
+  if not ok then
+    logger.trace(" ## 通知客户端充值成功呢异常 %s", info)
+  end
+end
+
+
+function recharge.monitor(character)
+    --玩家充值时间
+    character.monitor("recharge", function(_, cfid, orderid, moneynum, appname, giftid)
+        logger_info("玩家充值了礼包:%d, cfid:%s, orderid:%s",giftid, cfid, orderid)
+        local conf = asset.GiftConfig_proto[giftid]
+        if not conf or conf.spend ~= cfid then
+            logger.error("recharge err, orderid:%s,  giftid:%d", orderid, giftid)
+        end
+        character.dispatch("recharge.money", cfid, orderid, moneynum, giftid, conf.type, conf.sort)
+        skynet_call(rechargeser,"lua", "orderid_refresh", character.uid, orderid)
+        -- 充值金额大于零, 修改玩家充值总金额数量 -- 玩家金额统计
+        if moneynum > 0 then
+            character.set_money(character.money + moneynum)
+        end
+
+        log.recharge(character, moneynum , {
+          pay_platform = character.platform,
+          pay_orderID = orderid,
+          pay_id = cfid,
+          pay_price = moneynum,
+          pay_level = character.level,
+          pay_time = os.date("%Y-%m-%d %H:%M:%S"),
+          id = character.uid,
+        })
+
+        character.send("recharge_success", {
+            id = orderid,
+        })
+
+        -- local MONEY_TYPE = assert(asset.variable_proto[1851].data1)
+        -- local MONEY_TYPE = 1
+        -- recharge_succeed(character,orderid,{  --PK3-1085
+        --   pay_platform = character.platform,
+        --   pay_orderID = orderid,
+        --   pay_id = cfid,
+        --   pay_type = MONEY_TYPE,
+        --   pay_price = moneynum*copies,    --多份购买
+        --   pay_configtype = MONEY_TYPE,
+        --   pay_configprice = moneynum*copies,    --多份购买
+        --   pay_level = character.level,
+        --   pay_time = os.date("%Y-%m-%d %H:%M:%S",now),
+        --   id = character.uid,
+        -- })
+    end)
+
+    character.monitor("recharge.money",function(_, cfid, orderid, moneynum, giftid, modleid)
+        -- 钻石商店自己发奖
+        if modleid == MODULE_ID_DIA_SHOP then
+            return 
+        end
+        local conf = asset.GiftConfig_proto[giftid]
+        local award_list
+        if conf.award and conf.award > 0 then
+            local errno = 0
+            errno, award_list = common_fun.get_award(conf.award, "NewawardConfig_proto")
+            if errno ~= 0 then
+                logger.warn("计费点奖励异常, errno:%d, giftid:%d, awardid:%d", errno, giftid, conf.award)
+                return 
+            end
+        end
+        award_list = award_list or {}
+        for i = 1, #conf.library, 2 do
+            local item_id = conf.library[i]
+            local item_num = conf.library[i+1]
+            if item_id and item_num then
+                table.insert(award_list, {id = item_id, num = item_num})
+            end
+        end
+        -- 发奖励
+        local desc = {
+            module="recharge",
+            brief="充值礼包",
+            context= "充值礼包:"..giftid,
+            mailgen={subject=2, body=""},
+            notify={
+                flags="recharge_money"
+            },
+            detail=award_list,
+            must_mail = true
+        }
+        reward(character, desc)
+    end)
+  
+end
+
+-- function recharge.create_order(character, args)
+--     -- 检查具体的档位 等等
+--     -- 暂时无需要, 不开发
+--     local cfid = args.cfid
+--     local act_id = args.act_id or 0
+--     local act_type = args.act_type or 0
+--     local goods_id = args.goods_id or 0
+--     local login_args = character.login_args
+--     local platform = login_args.platform
+--     local appname = login_args.appname
+  
+--     local conf = util.recharge_conf(cfid)
+--     local errno = check_rechage_limit(character, conf[MONEY_STR])
+--     if errno ~= 0 then
+--       return 0, {reason = errno}
+--     end
+  
+--     local EDITOR_MODEL = true
+  
+--     if (character.channel == 'editor' and LAUNCH.private_gm) or LAUNCH.debug_model then
+--       skynet.fork(function()
+--           skynet.sleep(0)
+--           logger.trace("启动editor模式充值")
+--           local conf = util.recharge_conf(cfid)
+--           local temp = {
+--             orderid = "editor_orderid",
+--             cfid = cfid,
+--             act_id = act_id,
+--             act_type = act_type,
+--             goods_id = goods_id,
+--             moneynum = conf[MONEY_STR]
+--           }
+--           character.dispatch("recharge.money", temp)
+--       end)
+--       return 0, {reason = 0}
+
+    -- local ok,herrno,info = pcall(httpc.post, RECHARGE_SERVICE, '/getorderid', {
+    --     userid = character.account,			      -- 平台或渠道的账号id
+    --     serverid = character.sid,		          -- 游戏服务器id
+    --     uid = character.uid,		              -- 游戏内的账号id
+    --     platform = platform,	                -- 平台id(Andorid、iOS)
+    --     channel = character.channel,		      -- 渠道标识
+    --     product_id = cfid,	                  -- 计费点名称(用的计费点id)
+    --     remarks  = 'remarks',                 -- ,
+    --     appname = appname or 'appname',       -- 玩家使用的包
+    --     act_id = act_id or 0,                 -- 活动唯一id
+    --     act_type = act_type or 0,             -- 活动类型
+    --     goods_id = goods_id or 0,             -- 商品对应配置id
+    -- })
+    -- -- logger.trace("########## ok:%s, info:%s, info2:%s", ok, herrno or 'nil', info or 'nil')
+    -- if herrno ~= 200 then
+    --     return 0, {reason = STD_ERR.RECHARGE_CREATE_HTTPE_ERR}    -- http请求异常
+    -- end
+
+    -- -- info 解码
+    -- local order = cjson.decode(info)
+    -- -- logger.trace("充值系统 返回"..stringify(order))
+    -- if order.erron ~= 0 then
+    --     logger.trace("[充值系统] 订单创建失败 %s", stringify(order))
+    --     return 0, {reason = STD_ERR.RECHARGE_CREATE_FAIL}       -- 订单创建失败
+    -- end
+
+    -- -- skynet.fork(function(  )
+    -- --   skynet.sleep(300)
+    -- --   -- MODULE.test_recharge(character, order.desc)
+    -- -- end)
+    -- return 0, {orderid = order.desc, goods_id = cfid}
+--     end
+
+function recharge.get_orderid(character, giftid)
+    local conf = asset.GiftConfig_proto[giftid]
+    if not conf then
+        return 0
+    end
+    local cfid = conf.spend
+    if not cfid then
+        return 0
+    end
+    local orderid = "1"
+    local recharge_conf = asset.RechargeConfig_proto[cfid]
+    if not recharge_conf then
+        return 0
+    end
+
+    local moneytype = asset.DataConfig_proto[19].data1
+    local moneynum
+    if moneytype == 1 then
+        moneynum = recharge_conf.my
+    elseif moneytype == 2 then
+        moneynum = recharge_conf.rmb
+    elseif moneytype == 3 then
+        moneynum = recharge_conf.tw
+    elseif moneytype == 4 then
+        moneynum = recharge_conf.hk
+    end
+
+    if LAUNCH.test_recharge then
+        character.dispatch("recharge.money", cfid, orderid, moneynum, giftid, conf.type, conf.sort)
+        return 
+    end
+
+    local login_args = character.login_args
+    local platform = login_args.platform
+    local appname = login_args.appname
+
+    local ok,herrno,info = pcall(httpc.post, RECHARGE_SERVICE, '/getorderid', {
+        userid = character.account,			      -- 平台或渠道的账号id
+        serverid = character.sid,		          -- 游戏服务器id
+        uid = character.uid,		              -- 游戏内的账号id
+        platform = platform,	                -- 平台id(Andorid、iOS)
+        channel = character.channel,		      -- 渠道标识
+        product_id = giftid,	                  -- 礼包id
+        cfid = cfid,	                        -- 计费点id
+        remarks  = 'remarks',                 -- ,
+        appname = appname or 'appname',       -- 玩家使用的包
+        act_id = conf.type,
+    })
+
+    if herrno ~= 200 then
+        logger.trace("[充值系统] 订单创建失败.errno %s", herrno)
+        return 0
+    end
+
+    -- info 解码
+    local order = cjson.decode(info)
+    if order.erron ~= 0 then
+        logger.trace("[充值系统] 订单创建失败 %s", stringify(order))
+        return 0
+    end
+    
+    local now = os.time()
+    local ret = {
+        id = order.desc,
+        cfid = cfid,
+        time = now
+    }
+
+    if character.channel == "shengtian" then
+        shengtian = shengtian or require "service.loginserver.shengtian"
+        local content = {
+            fuse_uid = character.account,
+            order_id = order.desc,
+            product_id = conf.association,
+            product_name = conf.name,
+            price = deal_num(moneynum/MONEY_PARAM),
+            role_id = character.uid,
+            role_name = character.nickname,
+            role_level = character.level,
+            server_code = character.sid,
+            server_name = character.sid,
+            extension = character.login_args.extension,
+            time = now
+        }
+        ret.sign = shengtian.recharge_sign(content)
+        ret.cfid = giftid
+    end
+    
+    logger.trace("[充值系统] 订单创建成功"..stringify(order))
+    character.send("create_order_nty", ret)
+    return 0
+end
+
+function recharge.launch(character)
+  rechargeser = rechargeser or skynet.localname(".recharge")
+end
+
+
+function recharge.ready(character)
+end
+
+
+function recharge.saybye(character)
+end
+
+local function get_accesstoken(character)
+    local host = "https://accounts.google.com"
+    local url = "/o/oauth2/token"
+    local grant_type = "refresh_token"
+    local refresh_token = "1//0e3sYXF6c7hfMCgYIARAAGA4SNwF-L9IrVKAk6l2BMnTqOTqTxZ6_6Sy7y5LHfKUYqXQFvHuOu8WUuTq31l1kGTeLvylvo-hg8pc"
+    local client_id = "1053688457546-djajb15ii7v0q6pjsh7361fpsh97353g.apps.googleusercontent.com"
+    local client_secret = "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"
+    local ok,herrno,info = pcall(httpc.post, host, url, {
+        grant_type = grant_type,
+        refresh_token = refresh_token,
+        client_id = client_id,
+        client_secret = client_secret,
+    })
+    logger.trace("get_accesstoken ok:%s, herrno:%s, info:%s", ok, herrno or 'nil', info or 'nil')
+    if herrno ~= 200 then
+        return nil
+    end
+
+    -- info 解码
+    local order = cjson.decode(info)
+    logger.trace("get_accesstoken 返回"..stringify(order))
+    return order.access_token
+end
+
+function REQUEST.recharge_result(character, args)
+    local access_token = get_accesstoken(character)
+    if not access_token then
+        return {errno = 1} -- 系统异常
+    end 
+
+    local str = "/androidpublisher/v3/applications/%s/purchases/products/%s/tokens/%s?access_token=%s"
+    local packageName = "com.blackart.b22"     
+    local productId = args.cfg_id  
+    local token = args.purchase_token
+
+    local url = "https://androidpublisher.googleapis.com"
+    local from = string.format(str, packageName, productId, token, access_token)
+    local ok,herrno,inapp_purchase_data = pcall(httpc.get, url, from)
+    logger.trace("########## ok:%s, herrno:%s, inapp_purchase_data:%s", ok, herrno or 'nil', inapp_purchase_data or 'nil')
+    if herrno ~= 200 then
+        return {errno = 2} -- 未找到订单
+    end
+
+    -- info 解码
+    local order = cjson.decode(inapp_purchase_data)
+    logger.trace("recharge_result 返回"..stringify(order))
+    if order.purchaseState ~= 0 then
+        return {errno = 3} -- 未购买        
+    end
+
+    if not order.orderId then
+        return {errno = 4} -- 未找到订单id        
+    end
+
+    local ok,herrno,info = pcall(httpc.post, RECHARGE_SERVICE, '/googleplay', {
+        orderid = args.order_id,                  -- 订单id
+        userid = character.account,			      -- 平台或渠道的账号id
+        serverid = character.sid,		          -- 游戏服务器id
+        uid = character.uid,		              -- 游戏内的账号id
+        channel = character.channel,		      -- 渠道标识
+        cfid = args.cfg_id,	                      -- 计费点名称(用的计费点id)
+        channel_order_id = order.orderId          -- 渠道订单id
+    })
+
+    logger.trace("googleplay ok:%s, herrno:%s, info:%s", ok, herrno or 'nil', info or 'nil')
+    if herrno ~= 200 then
+        return {errno = 2} -- 未找到订单
+    end
+
+    return {errno = 0}
+end
+
+
+return recharge

+ 80 - 0
project/model/red_point.lua

@@ -0,0 +1,80 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local stringify = require "stringify"
+
+local module_name = "red_point"
+local _M = schema.new(module_name)
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local register_list = {
+    -- require "model.draw",
+}
+
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据 在这里把数据初始化好
+function MODULE.parse(character)
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+
+end
+
+function THIS.get_red_point_list(character)
+    local list = {}
+    for _, model in ipairs(register_list) do
+        local f = model.get_red_point
+        if f then
+            local ok, p1, p2 = pcall(f, character)
+            if ok then
+                if p1 and p2 then
+                    table.insert(list, {id = p2, red = p1})
+                end
+            else
+                logger.warn(p1)
+            end
+        end
+    end
+    return 0, {list = list}
+end
+
+function REQUEST.get_red_point_list(character)
+    return func_ret("get_red_point_list", character)
+end
+
+
+return MODULE

+ 150 - 0
project/model/reward.lua

@@ -0,0 +1,150 @@
+local logger = require "logger"
+local log = require "model.log"
+local stringify = require "stringify"
+local common_fun = require "model.common_fun"
+
+local currency
+local hero
+local equip
+local mailbox
+local time_box
+-- local role 
+
+local table_insert = table.insert
+
+local function batch(character, detail, collect, dispose, addition)
+    currency = currency or require "model.currency"
+    hero = hero or require "model.hero"
+    equip = equip or require "model.equip"
+    time_box = time_box or require "model.time_box"
+    if detail then
+        for _, v in ipairs(detail) do
+            local id = v.id
+            local num = v.num
+            local gtype = common_fun.goods_type(id)
+            if gtype == GOODS_NONE then
+                dispose(gtype, id, num)
+            elseif gtype == GOODS_MONEY then
+                currency.add_money(character, id, num, collect, dispose, addition)
+            elseif gtype == GOODS_HERO then
+                hero.add_hero(character, id, num, collect, dispose, addition)
+            elseif gtype == GOODS_EQUIP then
+                equip.add_equip(character, id, num, collect, dispose, addition)
+            elseif gtype == GOODS_BOX then
+                time_box.add_box(character, id, num, collect, dispose, addition)
+            end
+        end
+    end
+end
+
+local function _simple(character, detail, module, brief, context)
+    assert(character)
+    assert(detail)
+    -- 新增数量,用于日志存储
+    local added = {}
+    local addition = function(type, id, num)
+        local list = {id = id, num = num}
+        if type == GOODS_MONEY then
+            added.currency = added.currency or {}
+            table_insert(added.currency, list)
+        elseif type == GOODS_HERO then
+            added.heroes = added.heroes or {}
+            table_insert(added.heroes, list)
+        elseif type == GOODS_EQUIP then
+            added.equip = added.equip or {}
+            table_insert(added.equip, list)
+        elseif type == GOODS_BOX then
+            added.box = added.box or {}
+            table_insert(added.box, list)
+        end
+    end
+
+    -- 将立即发放物品, 发送给客户端
+    local cache = {}
+    local collect = function(type, list)
+        if type == GOODS_MONEY then
+            cache.currency = cache.currency or {}
+            table_insert(cache.currency, list)
+        elseif type == GOODS_HERO then
+            cache.heroes = cache.heroes or {}
+            table_insert(cache.heroes, list)
+        elseif type == GOODS_EQUIP then
+            cache.equip = cache.equip or {}
+            table_insert(cache.equip, list)
+        elseif type == GOODS_BOX then
+            cache.box = cache.box or {}
+            table_insert(cache.box, list)
+        end
+    end
+    
+    local lost = nil    -- 丢弃的道具/装备(背包满)
+    local errlist = nil     -- 配置中不存在的道具统一放这里
+    local dispose = function(type, id, num)
+        local list = {id = id, num = num}
+        if type == nil or type == GOODS_NONE then
+            errlist = errlist or {}
+            table_insert(errlist, list)
+        else 
+            lost = lost or {}
+            table_insert(lost, list)
+        end
+    end
+
+    -- 开始发放逻辑
+    batch(character, detail, collect, dispose, addition)
+    if added then
+        -- TODO: 在这里记录奖励日志
+        if next(added) then
+          log.reward(character, module, brief, context, added)
+        end
+    end
+    return cache, lost, errlist
+end
+
+local function reward(character, desc)
+    assert(character)
+    assert(desc)
+
+    assert(type(desc.detail) == "table")
+    local detail = common_fun.merge_pay(desc.detail)
+    local module = assert(desc.module)
+    local brief = assert(desc.brief)
+    local notify = assert(desc.notify)
+
+    local context = desc.context
+    local client, lost, errlist = _simple(
+        character, detail, module, brief, context)
+
+    if notify.flags and next(client) then
+        -- 立刻与客户端同步奖励数据
+        character.send("reward_info", {
+        added=client, -- 客户端需要当前量
+        -- lost= lost,
+        -- extra=notify.extra,
+        flags=notify.flags
+        })
+    end
+
+    if errlist then
+        logger.error("REWARD ERR !!!! module:%s,  brief:%s, errlist:%s", module, brief, stringify(errlist or {}))
+    end
+
+    -- 当存在丢弃物品时(装备道具), 通过邮件发送(mailgen有效)
+    local mailgen = desc.mailgen
+    local falg = false
+    if lost and mailgen then
+        falg = true
+        local subject = assert(mailgen.subject)
+        local body = assert(mailgen.body)
+        local specific = {
+            module=module,
+            brief=brief,
+            context=context
+        }
+        mailbox = mailbox or require "model.mailbox"
+        mailbox.send(character, subject, body, lost, specific, desc.must_mail)
+    end
+    return falg
+end
+
+return reward

+ 358 - 0
project/model/role.lua

@@ -0,0 +1,358 @@
+local skynet = require "skynet"
+local schema = require "model.schema"
+local logger = require "logger"
+local asset = require "model.asset"
+local reward = require "model.reward"
+local common_fun = require "model.common_fun"
+local stringify = require "stringify"
+local embattle
+local mailbox
+local namecenter
+local shengtian
+
+local math_max = math.max
+local math_min = math.min
+local table_insert = table.insert
+
+local MAX_STM = 100
+
+local currency
+
+local module_name = "role"
+local _M = schema.new(module_name, {
+    login_tims = 0; -- 登录次数
+    re_stm = 0;
+    first = false;
+})
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据
+function MODULE.parse(character)
+  local d = _M.load(character)
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+
+    local d = _M.assert_get(character)
+    local reset_stm = THIS.reset_stm(d, character)
+    character.monitor('timer.sec', function(_, ti)
+        reset_stm(ti)
+    end)
+    character.monitor("currency_add", function(_, id, num)
+        if id == CURRENCY_ID_ROLE_EXP then
+            THIS.role_add_exp(character, num)
+        end
+    end)
+
+    character.monitor("role_level", function(_, pre, cur)
+        shengtian = shengtian or require "service.loginserver.shengtian"
+        local content = {
+            account = character.account,
+            uid = character.uid,
+            name = character.nickname,
+            server = character.sid,
+            level = character.level,
+            power = character.power,
+        }
+        shengtian.upload(2, content)
+    end)
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+  local d = _M.assert_get(character)
+  currency = currency or require "model.currency"
+  embattle = embattle or require "model.embattle"
+  namecenter = skynet.localname(".namecenter")
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+    local d = _M.assert_get(character)
+    if not d.first then
+        THIS.first_login(d, character)
+        d.first = true
+    end
+    d.login_tims = math_max(0, d.login_tims) + 1
+    _M.persist(character)
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+
+end
+
+-- -- TODO: 在这定义网络请求的处理代码
+-- function REQUEST.echo(character, args)
+-- end
+
+function MODULE.stm_change(character)
+  local u = _M.assert_runtime(character)
+  u.stm_change = true
+end
+
+function THIS.check_stm_change(d, character, bsend)
+    local d = d or _M.assert_get(character)
+    local bfull = THIS.check_stm_full(d, character)
+
+    local change = false
+    -- 满了且有重置时间
+    if bfull and d.re_stm > 0 then
+        d.re_stm = 0
+        change = true
+    end
+
+    -- 没满也没有重置时间
+    if not bfull and d.re_stm <= 0 then
+        d.re_stm = os.time()
+        change = true
+    end
+
+    if change or bsend then
+        local time = d.re_stm
+        if time > 0 then
+            local conf = assert(asset.DataConfig_proto[1], "DataConfig_proto[1]")
+            local one_time = conf.data1
+            time = time + one_time
+        end
+        character.send("city_change", {currency = {id = CURRENCY_ID_STM, num = currency.get_money(character, CURRENCY_ID_STM)}, time = time})
+        if change then
+            _M.persist(character)  
+        end
+    end
+    return false
+end
+
+function THIS.check_stm_full(d, character)
+    local d = d or _M.assert_get(character)
+    local num = THIS.max_stm(d, character) - currency.get_money(character, CURRENCY_ID_STM)
+    return num <= 0, num
+end
+
+function THIS.max_stm(d, character)
+    return asset.DataConfig_proto[2].data3
+end
+
+function THIS.first_login(d, character)
+    local DataConf = asset.DataConfig_proto[2]
+    local errno, award_list = common_fun.get_award(DataConf.data2)
+    if errno ~= 0 then
+        return errno
+    end
+    if not award_list or not next(award_list) then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    -- 发奖励
+    local desc = {
+        module="first_login",
+        brief="首次登录",
+        context= "首次登录",
+        mailgen={subject=2, body=""},
+        notify={
+            flags="first_login"
+        },
+        detail=award_list
+    }
+    reward(character, desc)
+    embattle.first_battle(character)
+    -- mailbox = mailbox or require "model.mailbox"
+    -- local desc = {
+    --     module = "首次登录",
+    --     brief = "首次登录",
+    --     context = "首次登录",
+    -- }
+    -- mailbox.send(character, 3, "", {}, desc)
+    
+    -- currency.add_money(character, CURRENCY_ID_STM, MAX_STM)
+end
+
+function THIS.reset_stm(d, character)
+    local conf = assert(asset.DataConfig_proto[1], "DataConfig_proto[1]")
+    local one_time = conf.data1
+    local one_add = conf.data2
+    local u = _M.assert_runtime(character)
+    u.stm_change = true
+    u.stm_send = true
+    return function (ti)
+        if d.re_stm > 0 then
+            if d.re_stm + one_time <= ti then
+                local bfull, max_add = THIS.check_stm_full(d, character)
+                if not bfull then
+                    local times = math.floor((ti - d.re_stm) / one_time)
+                    local add_num = times * one_add
+                    if add_num >= max_add then
+                        currency.add_money(character, CURRENCY_ID_STM, max_add)
+                        d.re_stm = 0
+                    else
+                        currency.add_money(character, CURRENCY_ID_STM, add_num)
+                        d.re_stm = d.re_stm + times * one_time
+                    end
+                else
+                    d.re_stm = 0
+                end
+
+                u.stm_change = true
+                u.stm_send = true
+                _M.persist(character)
+            end
+        end
+        if u.stm_change then
+            THIS.check_stm_change(d, character, u.stm_send)
+            u.stm_change = false 
+            u.stm_send = false
+        end
+    end
+end
+
+function THIS.role_get_client_data(character)
+    local level = character.level
+    local exp = character.exp
+    local conf = asset.RanksLevelConfig_proto[level+1]
+    if not conf then
+        exp = 0
+    else
+        exp = math_min(conf.exp, exp)
+    end
+    return {level = level, exp = exp}
+end
+
+function THIS.role_add_exp(character, num)
+    local conf = asset.RanksLevelConfig_proto[character.level+1]
+    if not conf then
+        character.set_exp(0)
+    else
+        if num > 0 then
+            character.set_exp(character.exp + num)
+        end
+    end
+end
+
+function THIS.role_get_data(character, args)
+    return 0, THIS.role_get_client_data(character)
+end
+
+function THIS.role_upgrade(character, args)
+    local exp = character.exp
+    local level = character.level
+    local award_list = {}
+    while true do
+        local conf = asset.RanksLevelConfig_proto[level+1]
+        if not conf then
+            exp = 0
+            break
+        end
+        if exp > conf.exp then
+            exp = exp - conf.exp
+            level = level + 1
+            for i = 1, #conf.award, 2 do 
+                local id = conf.award[i]
+                local num = conf.award[i+1]
+                if id and num and id > 0 and num > 0 then
+                    table_insert(award_list, {id = id, num = num})
+                else
+                    break
+                end
+            end
+        else
+            break
+        end
+    end
+    if level ~= character.level then
+        if next(award_list) then
+            local desc = {
+                module="role",
+                brief="队伍等级提升",
+                context= string.format("队伍等级提升%d=>%d", character.level, level),
+                mailgen={subject=2, body=""},
+                notify={
+                    flags="role_upgrade"
+                },
+                detail=award_list
+            }
+            reward(character, desc) 
+        end
+        local pre = character.level
+        character.set_level(level)
+        character.set_exp(exp)
+        character.dispatch("role_level", pre, level)
+    end
+    return 0, THIS.role_get_client_data(character)
+end
+
+function THIS.role_rename(character, args)
+    local name = args.name
+    if not name then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    if name == character.nickname then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local length = string.len(name)
+    if length <=1 or length >21 then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    if string.match(name, "[%p]") then
+        return -- 参数异常
+    end
+
+    if os.time() - character.rename_time < 48*HOUR_SEC then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local errno = skynet.call(namecenter, "lua", "rename", character.nickname, name, character.uid)
+    if errno ~= 0 then
+        return errno
+    end
+
+    local oldname = character.nickname
+    character.set_nickname(name)
+    character.set_rename_time(os.time())
+    skynet.call(namecenter, "lua", "unlock_name", oldname)
+    return 0, {name = args.name, time = character.rename_time}
+end
+
+function REQUEST.role_get_data(character, args)
+    return func_ret("role_get_data", character, args)
+end
+
+function REQUEST.role_upgrade(character, args)
+    return func_ret("role_upgrade", character, args)
+end
+
+function REQUEST.role_rename(character, args)
+    return func_ret("role_rename", character, args)
+end
+
+function CMD.ban_rank(character, ban)
+    if character.ban_rank ~= ban then
+        character.set_ban_rank(ban)
+    end
+end
+
+return MODULE

+ 31 - 0
project/model/schema.lua

@@ -0,0 +1,31 @@
+local schema = {}
+function schema.new(k, default_value)
+  assert(k)
+  if default_value then
+    local self = {
+      assert_get = function(t) return assert(t.getspecific(k).get()) end,
+      assert_runtime = function(t) return assert(t.getspecific(k).runtime) end,
+      persist = function(t) t.persist(k, function() t.getspecific(k).save() end)
+      end,
+      load = function(t)
+        if t.readonly then
+          rawset(t, 'persist', function(...) end)
+        end
+        return t.register_specific(k, function()
+          return default_value
+        end)
+      end
+    }
+    return self
+  else
+    local self = {
+      assert_get = function(t) return assert(false, k) end,
+      assert_runtime = function(t) return assert(t.getspecific(k).runtime) end,
+      persist = function(t) return assert(false, k) end,
+      load = function(t) return t.register_specific(k) end
+    }
+    return self
+  end
+end
+
+return schema

+ 237 - 0
project/model/spawn.lua

@@ -0,0 +1,237 @@
+local skynet = require "skynet"
+local util = require "util"
+local logger = require "logger"
+
+local packstring = skynet.packstring
+local unpack = skynet.unpack
+local intmax = math.maxinteger
+local clamp = util.clamp
+local table_insert = table.insert
+local traceback = debug.traceback
+
+local spawn = function(readonly)
+  -- 首先,定义角色默认的数据结构
+  local datasource = {
+    -- TODO: 在下面定义 Number 和 String 类型的字段
+    uid                 = nil,          -- 角色ID
+    sid                 = nil,          -- 服务器ID
+    account             = nil,          -- 帐号唯一标识(渠道分配)
+    appid               = nil,          -- App唯一标识
+    channel             = nil,          -- 渠道唯一标识
+    platform            = nil,          -- 操作系统(pc,ios,android等)
+    tourist             = nil,          -- 游客标识:1:非游客账号 2: 游客账号    -- CDPK3-005 海外SDK
+    reported            = nil,          -- 玩家角色数据上报 --PK3-1366 数据统计
+
+    createtime          = nil,          -- 创号时间
+    lastlogin           = nil,          -- 最后登录时间
+    lastlogout          = nil,          -- 最后下线时间
+    forbidden           = 0,            -- 封号标志0是正常,非0解封号时间
+    silent              = 0,            -- 禁言标志0是正常,非0解禁言时间
+    fb                  = nil,          -- 玩家绑定faceboik帐号使用的云帐号
+
+    facebook            = nil,          -- 玩家绑定facebook账号
+    google              = nil,          -- 玩家绑定google账号
+    ios                 = nil,          -- 玩家绑定ios账号
+
+    nickname            = nil,          -- 角色名
+    rename_time         = 0,            -- 改变名字的时间
+    level               = 1,            -- 队伍等级
+    exp                 = 0,            -- 队伍经验
+    avater              = 0,            -- avater
+    vip                 = 0,            -- vip等级
+    updiamond           = 0,            -- vip经验
+    money               = 0,            -- 充值金额
+    daily_refresh       = 0,            -- 每日刷新时间
+    weekly_refresh      = 0,            -- 每周刷新时间
+    ban_rank            = 0,            -- 禁用排行
+    power               = 0,            -- 战斗力
+
+    -- TODO: 在下面定义二进制类型的字段 (BLOB)
+    role                = nil,          -- 角色数据
+    currency            = nil,          -- 货币
+    adventure           = nil,          -- 冒险
+    building            = nil,          -- 建筑
+    mail                = nil,          -- 邮件
+    hero                = nil,          -- 英雄
+    embattle            = nil,          -- 阵容
+    debris              = nil,          -- 碎片
+    skill_card          = nil,          -- 技能卡牌
+    equip               = nil,          -- 装备
+    talent              = nil,          -- 天赋
+    sign_in             = nil,          -- 签到
+    stats               = nil,          -- 任务数据统计
+    quest               = nil,          -- 任务
+    manual              = nil,          -- 战令
+    draw                = nil,          -- 抽奖
+    grocery             = nil,          -- 杂货店
+    time_box            = nil,          -- 宝箱
+
+    activitytimemod     = nil,          -- 活动时间
+ 
+  }
+
+  -- 定义角色数据的只读元表
+  local mt = {
+    __index = datasource,
+    __newindex = function(t, k, v) assert(false, "attempt to update a read-only table") end
+  }
+
+  -- 定义 setter 的基础函数
+  local tracker = {}
+  local function setter(k, v)
+    tracker[k] = v
+    datasource[k] = v
+  end
+  local function n_setter(k, v, low, up)
+    v = clamp(v, low, up or intmax)
+    tracker[k] = v
+    datasource[k] = v
+  end
+
+  -- TODO: 在下面定义字段的 setter
+  local self = { readonly=readonly }
+  function self.set_exp(value, up) n_setter('exp', value, 0, up) end
+  function self.set_avater(value, up) n_setter('avater', value, 0, up) end
+
+  function self.set_forbidden(value, up) n_setter('forbidden', value, 0, up) end
+  function self.set_silent(value, up) n_setter('silent', value, 0, up) end
+  function self.set_lastlogin(value, up) n_setter('lastlogin', value, 0, up) end
+  function self.set_lastlogout(value, up) n_setter('lastlogout', value, 0, up) end
+  function self.set_level(value, up) n_setter('level', value, 1, up) end
+  function self.set_rename_time(value, up) n_setter('rename_time', value, 1, up) end
+  function self.set_nickname(value) setter('nickname', value) end
+  function self.set_vip(value, up) n_setter('vip', value, 0, up) end
+  function self.set_updiamond(value, up) n_setter('updiamond', value, 0, up) end--累计充值钻石
+  function self.set_money(value, up) n_setter('money', value, 0, up) end--累计充值的金额
+  function self.set_daily_refresh(value, up) n_setter('daily_refresh', value, 0, up) end
+  function self.set_weekly_refresh(value, up) n_setter('weekly_refresh', value, 0, up) end
+  function self.set_ban_rank(value, up) n_setter('ban_rank', value, 0, up) end
+  function self.set_power(value, up) n_setter('power', value, 0, up) end
+
+  function self.set_currency(value) setter('currency', value) end
+  function self.set_role(value) setter('role', value) end
+  function self.set_adventure(value) setter('adventure', value) end
+  function self.set_building(value) setter('building', value) end
+  function self.set_mail(value) setter('mail', value) end
+  function self.set_hero(value) setter('hero', value) end
+  function self.set_embattle(value) setter('embattle', value) end
+  function self.set_debris(value) setter('debris', value) end
+  function self.set_skill_card(value) setter('skill_card', value) end
+  function self.set_equip(value) setter('equip', value) end
+  function self.set_talent(value) setter('talent', value) end
+  function self.set_sign_in(value) setter('sign_in', value) end
+  function self.set_stats(value) setter('stats', value) end
+  function self.set_quest(value) setter('quest', value) end
+  function self.set_manual(value) setter('manual', value) end
+  function self.set_draw(value) setter('draw', value) end
+  function self.set_grocery(value) setter('grocery', value) end
+  function self.set_time_box(value) setter('time_box', value) end
+
+
+  --------------------------------- setter end ---------------------------------
+
+  local function loadfrom(metadata)
+    assert(metadata)
+    local dest = datasource
+    for i=1, #metadata, 2 do
+      local k = metadata[i]
+      local v = metadata[i+1]
+      if k == "nickname" then
+        dest[k] = v
+      else
+        dest[k] = tonumber(v) or v
+      end
+    end
+    dest.uid = tostring(dest.uid)
+    return self
+  end
+  local function pack(k)
+    if next(tracker) then
+      local pkg = { k }
+      for field, value in pairs(tracker) do
+        table_insert(pkg, field)
+        table_insert(pkg, value)
+      end
+      tracker = {}  -- Must do it
+      return pkg
+    end
+  end
+  local function packall(k)
+    tracker = datasource
+    return pack(k)
+  end
+  -- TODO: 备份抛弃的数据
+  local backups_discard = {
+    presence = 'presence',
+  }
+  local function backups_all()
+    local ret = {}
+    for k, v in pairs(datasource) do
+      if not backups_discard[k] then
+        if type(v) == 'number'
+          or k == 'uid'
+          or k == 'channel'
+          or k == 'nickname'
+          or k == 'account'
+          or k == 'appid'
+          or k == 'platform'
+        then
+          ret[k] = v
+        else
+          ret[k] = unpack(v)
+        end
+      end
+    end
+    return ret
+  end
+
+  local listener = {}
+  local function dispatch(signame, ...)
+    local sigslot = listener[signame] or {}
+    for i, v in ipairs(sigslot) do
+        local ok,info = xpcall(v, traceback, self, ...)
+        if not ok then
+          logger.trace("dispatch errno %s, errno info %s", signame, info)
+        end
+    end
+  end
+  local function monitor(signame, f)
+    listener[signame] = listener[signame] or {}
+    table_insert(listener[signame], f)
+  end
+
+  local specific = {}
+  local function getspecific(k) return specific[k] end
+  local function register_specific(k, alloc)
+    assert(not specific[k], k)
+    local d = nil
+    local v = datasource[k]
+    if v then
+      d = unpack(v)
+    elseif alloc then
+      d = alloc()
+    end
+    local f = rawget(self, 'set_'..k)
+    local function get() return d end
+    local function save()
+      assert(f, string.format("attempt to call a nil value(field 'set_%s')", k))
+      f(packstring(d))
+    end
+    specific[k] = { get=get, save=save, runtime={} }
+    return d
+  end
+
+  -- Ok, let's go on
+  self.loadfrom = loadfrom
+  self.pack = pack
+  self.packall = packall
+  self.dispatch = dispatch
+  self.monitor = monitor
+  self.getspecific = getspecific
+  self.register_specific = register_specific
+  self.backups_all = backups_all
+  self.__index = self
+  return setmetatable(self, mt)
+end
+
+return spawn

+ 527 - 0
project/model/stats.lua

@@ -0,0 +1,527 @@
+local schema = require "model.schema"
+local map = {
+    -- character
+    login                       = 1001, -- 登录次数
+    recharge_times              = 1002, -- 充值次数
+    recharge_num                = 1003, -- 充值数量
+    buy_stm_times               = 1004, -- 购买体力次数
+    add_stm_times               = 1005, -- 购买或看视频获得体力
+    buy_shop_item               = 1006, -- 购买商店物品
+
+
+
+
+    watch_ad_times              = 1101, -- 看广告次数
+
+
+    -- advanture
+    simple_battle               = 2001, -- 挑战简单关卡次数
+    sweep_simple_battle         = 2002, -- 扫荡简单关卡次数
+    enter_simple_battle         = 2003, -- 进入简单关卡次数
+    simple_battle_pass          = 2004, -- 通关简单关卡
+
+    elite_battle                = 2101, -- 挑战精英管卡次数
+    elite_battle_pass           = 2102, -- 通关精英关卡
+
+    daily_battle                = 2201, -- 日常副本挑战次数
+
+    relic_battle_num            = 2301, -- 挑战遗迹关卡次数
+
+
+    -- hero
+    hero_upgrade                = 3001, -- 角色升级次数
+    hero_upstart                = 3002, -- 角色突破次数
+    hero_max_level              = 3003, -- 角色最大等级
+    hero_add_num                = 3004, -- 英雄增加数量
+
+
+    skill_card_upgrade          = 3101, -- 卡牌技能升级
+    skill_card_upstart          = 3102, -- 卡牌技能升星
+    skill_card_add_num          = 3103, -- 卡牌激活数量
+
+    
+    --kill monster
+    kill_monster                = 4001, -- 击杀怪物
+    kill_boss                   = 4002, -- 击杀boss
+
+
+    -- equip
+    equip_upgrade               = 5001, -- 装备升级
+    equip_upstart               = 5002, -- 装备突破
+    equip_add_num               = 5003, -- 装备增加数量
+
+    -- telent
+    telent_activate             = 6001, -- 天赋激活次数
+
+
+    -- building
+    building_upgrade            = 7001, -- 建筑升级次数
+    city_skill_upgrade          = 7002, -- 城镇技能升级
+    building_explore_times      = 7003, -- 建筑探索次数
+
+    -- draw
+    draw_times                  = 8001, -- 抽奖次数 
+    draw_hero_times             = 8002, -- 抽取英雄次数
+    draw_equip_times            = 8003, -- 抽取装备次数
+
+    -- currency
+    consume_coin                = 9001, -- 消耗金币数量
+    consume_diamond             = 9002, -- 消耗钻石数量
+    add_coin                    = 9003, -- 获得金币
+    add_diamond                    = 9003, -- 获得金币
+}
+
+local _M = schema.new('stats', {
+    list = {},
+    hero_lv = {},
+})
+
+local stats = {}
+local incident = {}
+
+function stats.parse(character)
+    local d = _M.load(character)
+    d.list = d.list or {}
+    d.hero_lv = d.hero_lv or {}
+    local change = false
+    for _, v in pairs(map) do
+        if not d.list[v] then
+            d.list[v] = 0
+            if not change then
+                change = true
+            end
+        end
+    end
+    if change then
+        _M.persist(character)
+    end
+end
+
+function stats.inform(character, name, ...)
+    local str = "stats.".. name
+    character.dispatch(str,...)
+end
+
+function incident.character(character)
+    local d = _M.assert_get(character)
+    character.monitor("daily_refresh", function()
+        local key = "login"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        _M.persist(character)
+        stats.inform(character, key, 1)
+    end)
+
+    character.monitor("recharge.money",function(_, ...)
+        local key = "recharge_times"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        _M.persist(character)
+        stats.inform(character, key, 1)
+    end)
+
+    character.monitor("recharge.money",function(_, cfid, orderid, moneynum, ...)
+        local key = "recharge_num"
+        local index = map[key]
+        d.list[index] = d.list[index] + moneynum
+        _M.persist(character)
+        stats.inform(character, key, moneynum)
+    end)
+
+    character.monitor("buy_stm", function(_, bfree)
+        if not bfree then
+            local key = "buy_stm_times"
+            local index = map[key]
+            d.list[index] = d.list[index] + 1
+            stats.inform(character, key, 1)
+        end
+
+        local key = "add_stm_times"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+
+    character.monitor("shop_buy", function(_, num)
+        local key = "buy_shop_item"
+        local index = map[key]
+        if num > 0 then
+            d.list[index] = (d.list[index] or 0) + num
+            stats.inform(character, key, num)
+            _M.persist(character)
+        end
+    end)
+
+    character.monitor("watch_ad", function()
+        local key = "watch_ad_times"
+        local index = map[key]
+        d.list[index] = (d.list[index] or 0) + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+end
+
+function incident.advanture(character)
+    local d = _M.assert_get(character)
+    character.monitor("simple_battle", function(_, id)
+        local key = "simple_battle"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+
+        key = "enter_simple_battle"
+        index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+
+        _M.persist(character)
+    end)
+
+    character.monitor("sweep_simple_battle", function(_, id)
+        local key = "sweep_simple_battle"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+
+        key = "enter_simple_battle"
+        index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+
+        _M.persist(character)
+    end)
+
+    character.monitor("simple_battle_pass", function(_, id)
+        local key = "simple_battle_pass"
+        local index = map[key]
+        if id >  d.list[index] then
+            d.list[index] = id
+            stats.inform(character, key, id)
+        end
+        _M.persist(character)
+    end)
+
+    character.monitor("elite_battle", function(_, id)
+        local key = "elite_battle"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+
+    character.monitor("elite_battle_pass", function(_, id)
+        local key = "elite_battle_pass"
+        local index = map[key]
+        if id >  d.list[index] then
+            d.list[index] = id
+            stats.inform(character, key, id)
+        end
+        _M.persist(character)
+    end)
+
+    character.monitor("relic_battle", function()
+        local key = "relic_battle_num"
+        local index = map[key]
+        d.list[index] =  (d.list[index] or 0) + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+
+    -- daily_battle                = 2201, -- 日常副本挑战次数
+    local function join_daily_battle()
+        local key = "daily_battle"
+        local index = map[key]
+        d.list[index] =  (d.list[index] or 0) + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end
+
+    character.monitor("daily_dungeons_pass", function()
+        join_daily_battle()
+    end)
+
+    character.monitor("daily_dungeons_sweep", function()
+        join_daily_battle()
+    end)
+end
+
+function incident.hero(character)
+    local d = _M.assert_get(character)
+    character.monitor("hero_upgrade", function(_, id, pre, cur)
+        if pre >= cur then
+            return 
+        end
+        local add = cur - pre
+        local key = "hero_upgrade"
+        local index = map[key]
+        d.list[index] = d.list[index] + add
+        stats.inform(character, key, add)
+
+        -- 10级以下不处理,
+        for i = math.min(10, pre+1), cur do
+            d.hero_lv[i] = (d.hero_lv[i] or 0) + 1
+            stats.inform(character, "hero_lv", d.hero_lv[i], i)
+        end
+
+        key = "hero_max_level"
+        index = map[key]
+        if d.list[index] < cur then
+            d.list[index] = cur
+            stats.inform(character, key, cur)
+        end
+        _M.persist(character)
+    end)
+
+    character.monitor("hero_upstart", function(_, id, pre, cur)
+        local key = "hero_upstart"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1 
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+
+
+    character.monitor("hero_upstart_times", function(_, num, quality)
+        local key = "hero_upstart"
+        local index = map[key]
+        d.list[index] = d.list[index] + num 
+        stats.inform(character, key, num)
+        _M.persist(character)
+    end)
+
+    character.monitor("hero_add_num", function(_, id, num)
+        local key = "hero_add_num"
+        local index = map[key]
+        d.list[index] = d.list[index] + num
+        stats.inform(character, key, num)
+        _M.persist(character)
+    end)
+
+    character.monitor("skill_card_upgrade", function(_, id, pre, cur)
+        if pre >= cur then
+            return 
+        end
+        local add = cur - pre
+        local key = "skill_card_upgrade"
+        local index = map[key]
+        d.list[index] = d.list[index] + add
+        stats.inform(character, key, add)
+        _M.persist(character)
+    end)
+
+    character.monitor("skill_card_upstart", function(_, id, pre, cur)
+        if pre >= cur then
+            return 
+        end
+        local add = cur - pre
+        local key = "skill_card_upstart"
+        local index = map[key]
+        d.list[index] = d.list[index] + add
+        stats.inform(character, key, add)
+        _M.persist(character)
+    end)
+
+    character.monitor("skill_card_add_num", function(_, id)
+        local key = "skill_card_add_num"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+end
+
+function incident.monster(character)
+    local d = _M.assert_get(character)
+    character.monitor("kill_monster", function(_, num)
+        local key = "kill_monster"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+
+    character.monitor("kill_boss", function(_, num)
+        local key = "kill_boss"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+end
+
+function incident.equip(character)
+    local d = _M.assert_get(character)
+    character.monitor("equip_upgrade", function(_, id, pre, cur)
+        if pre >= cur then
+            return 
+        end
+        local add = cur - pre
+        local key = "equip_upgrade"
+        local index = map[key]
+        d.list[index] = d.list[index] + add
+        stats.inform(character, key, add)
+        _M.persist(character)
+    end)
+
+    character.monitor("equip_upstart", function(_, id, pre, cur)
+        local key = "equip_upstart"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+
+    character.monitor("equip_upstart_times", function(_, num, quality)
+        local key = "equip_upstart"
+        local index = map[key]
+        d.list[index] = d.list[index] + num 
+        stats.inform(character, key, num)
+        _M.persist(character)
+    end)
+
+    character.monitor("equip_add_num", function(_, id, num)
+        local key = "equip_add_num"
+        local index = map[key]
+        d.list[index] = d.list[index] + num
+        stats.inform(character, key, num)
+        _M.persist(character)
+    end)
+end
+
+function incident.telent(character)
+    local d = _M.assert_get(character)
+    character.monitor("telent_activate", function(_)
+        local key = "telent_activate"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+end
+
+function incident.building(character)
+    local d = _M.assert_get(character)
+    character.monitor("building_upgrade", function(_)
+        local key = "building_upgrade"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+
+    character.monitor("city_skill_upgrade", function(_)
+        local key = "city_skill_upgrade"
+        local index = map[key]
+        d.list[index] = d.list[index] + 1
+        stats.inform(character, key, 1)
+        _M.persist(character)
+    end)
+
+    character.monitor("building_explore", function(_, num)
+        local key = "building_explore_times"
+        local index = map[key]
+        d.list[index] = d.list[index] + num
+        stats.inform(character, key, num)
+        _M.persist(character)
+    end)
+
+end
+
+function incident.draw(character)
+    local d = _M.assert_get(character)
+    character.monitor("draw_times", function(_, num, type)
+        local key = "draw_times"
+        local index = map[key]
+        d.list[index] = d.list[index] + num
+        stats.inform(character, key, num)
+
+        if type == 1 or type == 2 then
+            local key = "draw_hero_times"
+            local index = map[key]
+            d.list[index] = d.list[index] + num
+            stats.inform(character, key, num)
+        end
+
+        if type == 3 then
+            local key = "draw_equip_times"
+            local index = map[key]
+            d.list[index] = d.list[index] + num
+            stats.inform(character, key, num)
+        end
+        _M.persist(character)
+    end)
+end
+
+function incident.currency(character)
+    local d = _M.assert_get(character)
+    character.monitor("pay_money", function(_, id, num)
+        if id == CURRENCY_ID_COINS then
+            local key = "consume_coin"
+            local index = map[key]
+            d.list[index] = d.list[index] + num
+            stats.inform(character, key, num)
+            _M.persist(character)
+        elseif id == CURRENCY_ID_DIA then
+            local key = "consume_diamond"
+            local index = map[key]
+            d.list[index] = d.list[index] + num
+            stats.inform(character, key, num)
+            _M.persist(character)
+        end
+    end)
+
+    character.monitor("currency_add", function(_, id, num)
+        if id == CURRENCY_ID_COINS then
+            local key = "add_coin"
+            local index = map[key]
+            d.list[index] = d.list[index] + num
+            stats.inform(character, key, num)
+            _M.persist(character)
+        elseif id == CURRENCY_ID_DIA then
+            local key = "add_diamond"
+            local index = map[key]
+            d.list[index] = d.list[index] + num
+            stats.inform(character, key, num)
+            _M.persist(character)
+        end
+    end)
+
+end
+
+function stats.monitor(character)
+    if incident then
+        for _, v in pairs(incident) do
+            if v then
+                local f = v
+                f(character)
+            end
+        end
+    end
+end
+
+function stats.launch(character)
+end
+
+function stats.ready(character)
+  local d = _M.assert_get(character)
+end
+
+function stats.saybye(character)
+end
+
+function stats.fetch(character, key)
+    local d = _M.assert_get(character)
+    local index = map[key]
+    if not index then
+        return 0
+    end
+    return d.list[index] or 0
+end
+
+function stats.hero_lv(character, lv)
+    local d = _M.assert_get(character)
+    return d.hero_lv[lv] or 0
+end
+
+return stats

+ 181 - 0
project/model/talent.lua

@@ -0,0 +1,181 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local stringify = require "stringify"
+local asset = require "model.asset"
+local payment = require "model.payment"
+
+local module_name = "talent"
+local _M = schema.new(module_name, {
+    list = {
+        -- [type] = {type = 1, id = id}
+    }
+})
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local function func_ret(fname, character, args)
+    local f = THIS[fname]
+    if not f then
+        logger.error("func_ret not fname:%s !!!", fname)
+        return {errno = STD_ERR.COMMON_SYS_ERR}
+    end
+    local errno, ret = f(character, args)
+    if errno ~= 0 then
+        return {errno = errno}
+    end
+    ret = ret or {}
+    ret.errno = 0
+    return ret
+end
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据 在这里把数据初始化好
+function MODULE.parse(character)
+    local d = _M.load(character)
+    if not d.list then
+        d.list = {}
+    end
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    local d = _M.assert_get(character)
+    local u = _M.assert_runtime(character)
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+    local d = _M.assert_get(character)
+    logger.test("%s:ready, %s", module_name, stringify(d or {}))
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+
+end
+
+function MODULE.talent_get_data(character)
+    local d = _M.assert_get(character)
+    local ret = {}
+    for _, v in pairs(d.list or {}) do
+        if v.type == 1 then
+            ret.id1 = v.id
+        elseif v.type == 2 then
+            ret.id2 = v.id
+        end
+    end
+    return ret
+end
+
+
+function THIS.talent_get_data(character, args)
+    local ret = MODULE.talent_get_data(character)
+    return 0, ret
+end
+
+
+
+function THIS.talent_activate(character, args)
+    local id = args.id
+    if not id then
+        return STD_ERR.PLYAER_PARM_LIMIT -- 参数异常
+    end
+
+    local d = _M.assert_get(character)
+    local conf_list = assert(asset.TalentConfig_proto, "TalentConfig_proto")
+    local conf = conf_list[id]
+    if not conf then
+        return STD_ERR.PLYAER_PARM_LIMIT -- 参数异常
+    end
+
+    if character.level < conf.lv then
+        return STD_ERR.PLYAER_PARM_LIMIT -- 参数异常
+    end
+
+    local data = d.list[conf.talentType] or {type = conf.talentType, id = 0}
+    if data.id ~= conf.condition then
+        return STD_ERR.PLYAER_PARM_LIMIT -- 参数异常
+    end
+
+    local pay_list = {}
+    for i = 1, #conf.goods, 2 do
+        local item_id = conf.goods[i]
+        local item_num = conf.goods[i+1]
+        if item_id and item_num and item_id > 0 and item_num > 0 then
+            table.insert(pay_list, {id = item_id, num = item_num})
+        end
+    end
+
+    if next(pay_list) then
+        local receipt = {
+            module="talent",
+            brief="天赋激活",
+            context="天赋激活:"..id,
+            notify={
+                flags="talent_activate"
+            },
+            detail=pay_list
+        }
+        local errno = payment(character, receipt)
+        if errno ~= 0 then 
+            return errno
+        end
+    end
+
+    data.id = id
+    d.list[conf.talentType] = data
+    _M.persist(character)
+    character.dispatch("telent_activate")
+    local ctx = _M.assert_runtime(character)
+    ctx.special_list = nil
+    return 0, {id = args.id}
+end
+
+-- 天赋数据
+function REQUEST.talent_get_data(character, args)
+    return func_ret("talent_get_data", character, args)
+end
+
+function REQUEST.talent_activate(character, args)
+    return func_ret("talent_activate", character, args)
+end
+
+function MODULE.get_special_attr(character)
+    local ctx = _M.assert_runtime(character)
+    if ctx.special_list then
+        return ctx.special_list
+    end
+    local list = {}
+    local d = _M.assert_get(character) or {}
+    for _, talent_conf in pairs(asset.TalentConfig_proto)do
+        local data = d.list[talent_conf.talentType]
+        if data and data.id >= talent_conf.ID then
+            local conf = asset.EntryConfig_proto[talent_conf.entryID]
+            if conf and conf.type and conf.type >= 100 then
+                if conf.rateArr[1] and conf.rateArr[1] > 0 then
+                    list[conf.type] = (list[conf.type] or 0) + conf.rateArr[1]
+                end
+            end
+        end  
+    end
+    ctx.special_list = list
+    return ctx.special_list
+end
+
+function MODULE.get_talent_data(character)
+    local d = _M.assert_get(character)
+    return d.list
+end
+
+
+return MODULE

+ 228 - 0
project/model/time_box.lua

@@ -0,0 +1,228 @@
+local schema = require "model.schema"
+local logger = require "logger"
+local asset = require "model.asset"
+local common_fun = require "model.common_fun"
+local payment = require "model.payment"
+local reward = require "model.reward"
+local stringify = require "stringify"
+
+
+local MODULE_NAME = "time_box"
+local MAX_CHANGE = 20000000000
+
+local _M = schema.new(MODULE_NAME, {
+    data = {
+    }
+})
+
+
+local REQUEST = {}
+local CMD = {}
+local MODULE = {}
+local THIS = {}
+
+local ret_func = common_fun.get_ret_func(THIS)
+
+function MODULE.list_request_interests() return REQUEST end
+function MODULE.list_command_interests() return CMD end
+
+-- TODO: 解析/升级模块数据
+function MODULE.parse(character)
+    local d = _M.load(character)
+end
+
+-- TODO: 侦听事件
+function MODULE.monitor(character)
+    local d = _M.assert_get(character)
+end
+
+-- TODO: 类似泰利的 prepare 接口
+function MODULE.launch(character)
+    local d = _M.assert_get(character)
+end
+
+-- TODO: 与客户端同步数据
+function MODULE.ready(character)
+  local d = _M.assert_get(character)
+  logger.test("%s: ready, d:%s", MODULE_NAME, stringify(d))
+end
+
+-- TODO: 玩家下线时的处理
+function MODULE.saybye(character)
+    local d = _M.assert_get(character)
+end
+
+local function get_max_pos()
+    return 4
+end
+
+local function get_box_data_one(pos, data)
+    local temp = common_fun.scopy(data)
+    temp.pos = pos
+    return temp
+end
+
+function MODULE.get_client_data(character)
+    local d = _M.assert_get(character) or {}
+    local max_pos = get_max_pos()
+    local ret = {}
+    for i = 1, max_pos do
+        if d.data[i] then
+            table.insert(ret, get_box_data_one(i, d.data[i]))
+        end
+    end
+    return ret
+end
+
+function MODULE.get_box_surplus(character)
+    local d = _M.assert_get(character) or {}
+    local max_pos = get_max_pos()
+    local surplus_num = max_pos
+    for i = 1, max_pos do
+        if d.data[i] then
+            surplus_num = surplus_num - 1
+        end
+    end
+    return surplus_num
+end
+
+function MODULE.add_box(character, id, num, collect, dispose, addition)
+    local d = _M.assert_get(character) or {}
+    local conf_list = assert(asset.BoxConfig_proto, "BoxConfig_proto")
+    local conf = conf_list[id]
+    if not conf and dispose then
+        dispose(nil, id, num)
+        return 
+    end
+
+    local add_num = 0
+    local max_pos = get_max_pos()
+    if num >= 1 and num < MAX_CHANGE then
+        for i = 1, max_pos do
+            if add_num >= num then
+                break
+            end
+            if not d.data[i] then
+                d.data[i] = {
+                    id = id,
+                    tm = 0,
+                }
+                collect(GOODS_BOX, get_box_data_one(i, d.data[i]))
+                add_num = add_num + 1
+            end
+        end
+
+        logger.trace(stringify(d.data or {}))
+
+        addition(GOODS_BOX, id, add_num)
+        _M.persist(character)
+        return true
+    else
+        if dispose then
+            dispose(nil, id, num)
+        end
+    end
+end
+
+function THIS.time_box_unlock(character, args)
+    local d = _M.assert_get(character) or {}
+    local pos = args.pos
+    if not pos or pos <= 0 then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    
+    local data = d.data[pos]
+
+    if not data or not data.tm or data.tm > 0 then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    
+    local conf_list = assert(asset.BoxConfig_proto, "BoxConfig_proto")
+    local conf = conf_list[data.id]
+    data.tm = os.time()+ conf.cd*MIN_SEC
+    _M.persist(character)
+    return 0, {data = get_box_data_one(pos, data)}
+end
+
+function THIS.time_box_get_award(character, args)
+    local d = _M.assert_get(character) or {}
+    local pos = args.pos
+    if not pos or pos <= 0 then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+    
+    local data = d.data[pos]
+    if not data or not data.tm or data.tm <= 0 then
+        return STD_ERR.COMMON_PARM_ERR -- 参数异常
+    end
+
+    local conf_list = assert(asset.BoxConfig_proto, "BoxConfig_proto")
+    local conf = conf_list[data.id]
+    if not conf then
+        return STD_ERR.COMMON_CONF_ERR -- 配置异常
+    end
+
+    local now = os.time()
+    local pay_list = {}
+    local errno, award_list = common_fun.get_award(conf.award)
+    if errno ~= 0 then
+        return errno
+    end
+    if now < data.tm then
+        for i = 1, #conf.consume, 2 do
+            local item_id = conf.consume[i]
+            local item_num = conf.consume[i+1]
+            if item_id and item_num and item_id > 0 and item_num > 0 then
+                table.insert(pay_list, {id = item_id, num = item_num})
+            end
+        end
+    end
+
+    if next(pay_list) then
+        local receipt = {
+            module="box",
+            brief="开启宝箱",
+            context="开启宝箱:"..data.id,
+            notify={
+                flags="time_box_get_award"
+            },
+            detail=pay_list
+        }
+        local errno = payment(character, receipt)
+        if errno ~= 0 then 
+            return errno
+        end
+    end
+
+    local desc = {
+        module="box",
+        brief="开启宝箱",
+        context="开启宝箱:"..data.id,
+        mailgen={subject=2, body=""},
+        notify={
+            flags="time_box_get_award"
+        },
+        detail=award_list
+    }
+    reward(character, desc)
+    d.data[pos] = nil
+    _M.persist(character)
+    return 0, {pos = pos}
+end
+
+function THIS.time_box_data(character, args)
+    return 0, {list = MODULE.get_client_data(character)}
+end
+
+function REQUEST.time_box_unlock(character, args)
+    return ret_func("time_box_unlock", character, args)
+end
+
+function REQUEST.time_box_get_award(character, args)
+    return ret_func("time_box_get_award", character, args)
+end
+
+function REQUEST.time_box_data(character, args)
+    return ret_func("time_box_data", character, args)
+end
+return MODULE

+ 116 - 0
project/proto/errno.lua

@@ -0,0 +1,116 @@
+--[[
+    统一错误码
+    格式:[num] = 错误原因          -- 详细说明
+    num: 每个模块最少保证有100个错误码
+]]
+local logger = require "logger"
+local errno =  {
+    [100000] = "COMMON_PARM_ERR",                                   -- 通用 参数异常
+    [100001] = "COMMON_CONF_ERR",                                   -- 通用 配置异常
+    [100002] = "COMMON_SYS_ERR",                                    -- 通用 系统异常(服务器数据或结构异常)
+    [100003] = "COMMON_UNKNOWN_CURRENCY",                           -- 通用 未知的货币
+    [100004] = "COMMON_UNKNOWN_HERO",                               -- 通用 未知的角色
+    [100005] = "COMMON_UNKNOWN_CARD",                               -- 通用 未知的技能卡牌
+    [100006] = "COMMON_UNKNOWN_EQUIP",                              -- 通用 未知的装备
+    [100007] = "COMMON_UNKNOWN_DEBRIS",                             -- 通用 未知的碎片
+    [100008] = "COMMON_UNKNOWN_GOODS",                              -- 通用 未知的物品
+    [100009] = "COMMON_AWARD_FORMAT_ERR",                           -- 通用 奖励格式异常
+    [100010] = "COMMON_AWARD_RECEIVED",                             -- 通用 奖励已领取
+    [100011] = "COMMON_CLICK_TOO_FAST",                             -- 通用 点击过快
+    [100012] = "COMMON_BAG_MAIL_FULL",                              -- 通用 背包和邮件满了
+    [100013] = "COMMON_ACT_NOT_OPEN",                               -- 通用 活动未开启
+    [100014] = "COMMON_AWARD_NOT_UNLOCK",                           -- 通用 奖励未解锁
+    [100015] = "COMMON_HAVE_GIFT",                                  -- 通用 礼包已购买
+    [100016] = "COMMON_NO_MORE_GIFT",                               -- 通用 没有更多的礼包数量
+    [100017] = "COMMON_GIFT_TIME_LIMIT",                            -- 通用 礼包已过期
+    [100018] = "COMMON_NOT_AWARD",                                  -- 通用 没有奖励
+    [100019] = "COMMON_NOT_AWARD_CONDITION",                        -- 通用 奖励条件不满足
+    [100020] = "COMMON_NOT_ROLE_LEVEL_LIMIT",                       -- 通用 玩家等级不足
+    [100021] = "COMMON_BUY_LIMIT",                                  -- 通用 达到购买上限
+
+    [101000] = "ROLE_NOT_AWARD",                                    -- 建筑 没有奖励 
+    
+    [101100] = "ADVENTURE_NOT_BATTLE",                              -- 冒险 没有进行中的冒险 
+    [101101] = "ADVENTURE_NOT_PASS",                                -- 冒险 未通关 
+    [101102] = "ADVENTURE_IN_BATTLE",                               -- 冒险 有冒险在进行 
+
+    [101200] = "PAYMENT_LIST_ERR",                                  -- 支付 格式错误
+    [101201] = "PAYMENT_NUM_ERR",                                   -- 支付 道具数量异常
+    [101202] = "PAYMENT_UNKNOWN_ITEM",                              -- 支付 未知的道具
+    [101203] = "PAYMENT_NOT_ENOUGH",                                -- 支付 道具不足
+
+    [101300] = "HERO_START_MAX",                                    -- 角色 达到最大突破等级
+    [101301] = "HERO_SAME_PAY_ERR",                                 -- 角色 消耗同名卡异常
+    [101302] = "HERO_OTHER_PAY_ERR",                                -- 角色 消耗其他卡异常
+    [101303] = "HERO_MAX_LEVEL",                                    -- 角色 达到最大等级
+    [101304] = "HERO_NOBODY_CAN_UP_STAR",                           -- 角色 没有角色可以升星
+    [101305] = "HERO_IN_BATTLE",                                    -- 角色 英雄上阵中
+    [101306] = "HERO_SAME_SID",                                     -- 角色 相同的角色被使用
+    [101307] = "HERO_FULL",                                         -- 角色 英雄背包已满
+
+    [101400] = "EMBATTLE_NOT_TARGET",                               -- 阵容 未找到目标
+    [101401] = "EMBATTLE_SAME_NAME_HERO",                           -- 阵容 上阵同名英雄
+
+    [101500] = "SKILL_CARD_MAX_LEVEL",                              -- 技能 达到最大等级
+    [101510] = "SKILL_CARD_START_MAX",                              -- 技能 达到最大突破等级
+
+
+    [101600] = "EQUIP_START_MAX",                                   -- 装备 达到最大突破等级
+    [101601] = "EQUIP_SAME_PAY_ERR",                                -- 装备 消耗同名卡异常
+    [101602] = "EQUIP_OTHER_PAY_ERR",                               -- 装备 消耗其他卡异常
+    [101603] = "EQUIP_MAX_LEVEL",                                   -- 装备 达到最大等级
+    [101604] = "EQUIP_NOBODY_CAN_UP_STAR",                          -- 装备 没有装备可以升星
+    [101605] = "EQUIP_IN_BATTLE",                                   -- 装备 英雄上阵中
+    [101606] = "EQUIP_SAME_SID",                                    -- 装备 相同的角色被使用
+    [101607] = "EQUIP_FULL",                                        -- 装备 装备背包已满
+    [101608] = "EQUIP_POS_ERR",                                     -- 装备 装备位置异常
+    [101609] = "EQUIP_BE_USED",                                     -- 装备 已被穿戴
+    [101610] = "EQUIP_PROFESSION_ERR",                              -- 装备 职业不匹配
+
+    [101700] = "QUEST_NOT_COMPLATE",                                -- 任务 任务未完成
+
+    [101800] = "DRAW_DAILY_TIMES_LIMIT",                            -- 抽奖 每日次数限制
+    [101801] = "DRAW_FREE_TIMES_LIMIT",                             -- 抽奖 免费次数不足
+
+    [101900] = "DAILY_DUNGEONS_CANT_PASS",                          -- 日常副本 未通关
+    [101901] = "DAILY_DUNGEONS_BATTLE_TIMES_LIMTE",                 -- 日常副本 挑战次数不足
+    [101903] = "DAILY_DUNGEONS_NOT_BATTLE",                         -- 日常副本 没有进行中的战斗
+
+    [102000] = "CITY_SKILL_UNLOKC",                                 -- 主城 技能未解锁
+    [102001] = "CITY_SKILL_LEVEL_MAX",                              -- 主城 等级上限
+
+    [103001] = "RELIC_CANT_SELECT_BUF",                             -- 遗迹 无法选择buff
+    [103002] = "RELIC_NOT_EVENT",                                   -- 遗迹 没有事件
+    [103003] = "RELIC_ERR_DIFF",                                    -- 遗迹 无法挑战更高难度
+    [103004] = "RELIC_CANT_RESET",                                  -- 遗迹 未到重置时间
+    [103005] = "RELIC_UNRESOLVED_EVENTS",                           -- 遗迹 有未处理的事件
+    [103006] = "RELIC_CANT_ENTER_NEXT_POS",                         -- 遗迹 无法通行
+
+    [163001] = "PLYAER_DUPLICATION_NAME",                           -- 玩家 名字重复
+    [163002] = "PLYAER_NO_PLAYER",                                  -- 玩家 不存在的玩家
+    [163003] = "PLYAER_LLLEGAL_NAME",                               -- 玩家 非法角色名
+    [163004] = "PLYAER_LLLEGAL_OPERATION",                          -- 玩家 非法操作
+    [163005] = "PLYAER_PARM_LIMIT",                                 -- 玩家 参数异常
+    [163006] = "PLYAER_ERR_SERVERID",                               -- 玩家 不支持的服务器ID
+    [163007] = "PLYAER_OTHER_LOGIN",                                -- 玩家 其它设备正在登录
+    [163008] = "PLYAER_MAINTAIN",                                   -- 玩家 正在维护
+    [163009] = "PLYAER_FORBID",                                     -- 玩家 被禁用的帐号
+    [163010] = "PLYAER_NO_PLAYER",                                  -- 玩家 未创建角色
+    [163011] = "PLYAER_ERR_SIGN",                                   -- 玩家 身份验证失败
+
+    [170001] = "SERVER_START_MAINTENANCE",                          -- 服务器 服务器开始维护
+    [170002] = "ACCOUNT_LOGGING_ANOTHER_DEVICE"                     -- 服务器 帐号正在其它设备登录
+}
+
+local ret = {}
+for k, v in pairs(errno) do
+    ret[v] = k
+end
+setmetatable(ret, {
+    __index = function(tbl, tag)
+        logger.warn(" _________________未定义错误码: %s", tag)
+        logger.warn(" _________________错误来源: %s", debug.traceback())
+        return 2000000                                -- 未定义的错误码
+    end
+})
+return ret

File diff suppressed because it is too large
+ 1303 - 0
project/proto/game.pb


+ 5 - 0
project/proto/mkpb.sh

@@ -0,0 +1,5 @@
+rm -rf game.pb
+cd proto
+protoc --descriptor_set_out=game.pb *.proto
+mv game.pb ../
+

+ 1 - 0
project/proto/proto

@@ -0,0 +1 @@
+Subproject commit 368f52962a7b3aabccc1b3ffc8b66d0d77e517d4

+ 0 - 0
project/ser_list.sql


+ 393 - 0
project/service/act_rankinglist.lua

@@ -0,0 +1,393 @@
+--节日排行榜服务
+local skynet = require "skynet"
+require "skynet.manager"
+local util = require "util"
+local logger = require "logger"
+local common_fun = require "model.common_fun"
+local stringify = require "stringify"
+local pipeline = require "pipeline"
+local redisdriver = require "skynet.db.redis"
+local asset = require "model.asset"
+local redis
+local usercenter 
+local activitytime
+local mailbox
+
+
+local KEY_STR = "act_ranking:list:%d"
+local KEY_ACT_INFO = "act_ranking:act_info"
+
+local THIS_OPEN = 1     -- 开启
+local THIS_SYN = 2      -- 最后一次同步数据
+local THIS_AWARD = 3    -- 发奖
+local THIS_CLOSE = 4    -- 结束
+
+
+local cjson     = require "cjson"
+local cjson_decode   = cjson.decode   -- 解码
+local cjson_encode   = cjson.encode   -- 编码
+
+local MAX_RANK = 300
+local SHOW_RANK = 100
+
+local compare = function(a, b)
+    if a.num > b.num then
+        return true
+    end
+    if a.num == b.num and a.num2 and b.num2 then
+        return a.num2 > b.num2
+    end
+    return false
+end
+
+local function inserting(t, n, f)
+    assert(type(t) == "table")
+    local f = f or compare
+	local size = #t
+	local n = n or size + 1
+	for i = 2, size do
+		local num = math.min(i, n)
+		if i > n and f(t[i], t[num]) then
+			t[i], t[num] = t[num], t[i]
+		end
+		local temp = t[num]
+		local j = num -1 
+		while j >= 1 and f(temp, t[j]) do
+			t[j+1] = t[j]
+			j = j - 1
+		end
+		t[j+1] = temp
+	end
+	-- if n < size then
+	-- 	t[n+1] = nil
+	-- end
+    for i = n + 1, size do
+        t[i] = nil
+    end
+	return t
+end
+
+local CMD = {}
+local ranklist = {
+
+}
+
+local showlist = {
+
+}
+
+-- 活动状态表
+local act_list = {
+    --[[
+        [id] = {
+            state = 开启, 发奖, 结束
+            list = { -- 参与人员
+                [uid] = 1
+            }
+        }
+    ]]
+}
+
+local function find_uid(list, uid)
+    for k, info in ipairs(list or {})do
+        if info.role.uid == uid then
+            return k, info
+        end
+    end
+end
+
+local function init_act_info(id, state)
+    return {
+        id = id,
+        state = state or THIS_OPEN,
+        list = {}
+    }
+end
+
+local function get_act_conf(id)
+    return asset.ActivityConfig_proto[id]
+end
+
+local function save_act_info(id)
+    if act_list[id] then
+        redis:hset(KEY_ACT_INFO, id, cjson_encode(act_list[id]))
+    else
+        redis:hdel(KEY_ACT_INFO, id)
+    end
+end
+
+local function send_award(id)
+    local conf = assert(get_act_conf(id), id)
+    local act_type = conf.type
+    mailbox = mailbox or skynet.localname(".mailbox")
+
+    local send_list = {-- 已发送奖励的玩家
+        --[[
+            [uid] = 1
+        ]]
+    } 
+    local join_list = (act_list[id] or {}).list or {}
+    local rank_list = showlist[id] or {}
+
+    local mail_type_1_100
+    local mail_type_100_n
+    local source = {
+        module = "act_ranking",
+        brief = "排行活动奖励",
+    }
+    if act_type == MODULE_ID_HERO_RANK then
+        source.brief = "英雄排行奖励"
+        mail_type_1_100 = 11
+        mail_type_100_n = 15
+    elseif act_type == MODULE_ID_SIMPLE_BATTLE_RANK then
+        source.brief = "闯关排行奖励"
+        mail_type_1_100 = 12
+        mail_type_100_n = 16
+    elseif act_type == MODULE_ID_POWER_RANK then
+        source.brief = "战力排行奖励"
+        mail_type_1_100 = 13
+        mail_type_100_n = 17
+    elseif act_type == MODULE_ID_ELITE_BATTLE_RANK then
+        source.brief = "精英关卡排行奖励"
+        mail_type_1_100 = 14
+        mail_type_100_n = 18
+    else
+        assert(false, act_type)
+    end
+
+    for i=1, #conf.reward do
+        local r1 = assert(conf.parameter1[i*2-1], i)
+        local r2 = assert(conf.parameter1[i*2], i)
+        local award_id = assert(conf.reward[i], i)
+        local _, award_list = common_fun.get_award(award_id, "NewawardConfig_proto")
+        for rank = r1, r2 do
+            local rankinfo = rank_list[rank]
+            if not rankinfo then
+                break
+            end
+            if rank <= SHOW_RANK then
+                if rankinfo.role and rankinfo.role.uid then 
+                    local uid = rankinfo.role.uid
+                    source.context = string.format("第%s名奖励", rank)
+                    send_list[uid] = 1
+                    skynet.call(mailbox, "lua", "off_line_mail", {uid}, mail_type_1_100, rank, award_list, source)
+                else
+                    logger.error("错误的玩家数据,%s,%s",id, rank)
+                end
+            else
+                if rank_list[SHOW_RANK] then
+                    local uids = {}
+                    source.context = string.format("第%s名奖励", rank)
+                    for uid in pairs(join_list) do
+                    if not send_list[uid] then
+                        table.insert(uids, uid)
+                    end
+                    end
+                    if next(uids) then
+                        skynet.call(mailbox, "lua", "off_line_mail", uids, mail_type_100_n, "", award_list, source)
+                    end
+                end
+                break
+            end
+        end
+    end
+end
+
+local function update()
+    for act_id, list in pairs(ranklist) do
+        inserting(list, MAX_RANK)
+        showlist[act_id] = {}
+        local key = string.format(KEY_STR, act_id)
+        redis:del(key)
+        local batch = pipeline(100)
+        for rank, data in ipairs(list) do
+            batch.add("hset", key, data.role.uid, cjson_encode(data))
+            showlist[act_id][rank] = common_fun.scopy(data)
+        end
+        batch.execute(redis,{})
+    end
+end
+
+local function check_over()
+    local now = os.time()
+    for id, data in pairs(act_list) do
+        if data.state ~= THIS_CLOSE then
+            activitytime = activitytime or skynet.localname(".activitytime")
+            usercenter = usercenter or skynet.localname(".usercenter")
+            local info = skynet.call(activitytime, "lua", "get_sid_info", id)
+            local conf = get_act_conf(id)
+            if info and conf then
+                if data.state == THIS_OPEN then
+                    if now > info.endtime - conf.parameter3[1]*HOUR_SEC then
+                        data.state = THIS_SYN
+                        -- 通知在线玩家同步
+                        skynet.call(usercenter, "lua", "command", "syn_act_rank", conf.type)
+                        save_act_info(id)
+                    end
+                elseif data.state == THIS_SYN then
+                    if now > info.endtime - conf.parameter3[1]*HOUR_SEC + 10 * MIN_SEC then
+                        -- 发奖
+                        local ok, msg = xpcall(send_award, debug.traceback, id)
+                        data.state = THIS_AWARD
+                        save_act_info(id)
+                        if not ok then
+                            logger.errno(msg)
+                        end
+                    end
+                elseif data.state == THIS_AWARD then
+                    if now > info.endtime then
+                        data.state = THIS_CLOSE
+                        -- 清空数据
+                        save_act_info(id)
+                    end
+                end
+            else
+                logger.error("活动排行,错误的数据info:%s, conf%s, id:%d", type(info), type(conf), id)
+            end
+        end
+    end
+end
+
+local function init()
+    local conf = assert(option.redis)
+    redis = redisdriver.connect(conf)
+    redis:select(12)
+    local retnuma = redis:hgetall(KEY_ACT_INFO)
+    for k = 1, #retnuma, 2 do
+        local key = tonumber(retnuma[k])
+        local data = cjson_decode(retnuma[k+1])
+        if key and data then
+            act_list[key] = data
+        end
+    end
+    for act_id, info in pairs(act_list) do
+        if info.state ~= THIS_CLOSE then
+            ranklist[act_id] = {}
+            local key = string.format(KEY_STR, act_id)
+            local retnuma = redis:hgetall(key)
+            for k = 1, #retnuma, 2 do
+                local data = cjson_decode(retnuma[k+1])
+                ranklist[act_id][#ranklist[act_id]+1] = data
+            end
+            
+        end
+    end
+end
+
+local function watchtime()
+    skynet.fork(function()
+        local interval = 60*2 -- 更新间隔为2分钟
+        local up_time = os.time()
+        while(true) do
+            local now = os.time()
+            if now > up_time then
+                up_time = now + interval
+                update()
+                check_over()
+            end
+            skynet.sleep(300)
+        end
+    end)
+end
+
+function CMD.player_rename(uid, name)
+    for _, list in pairs(ranklist) do
+        local _, info = find_uid(list, uid)
+        if info then
+            info.role.nickname = name
+        end
+    end
+end
+
+function CMD.upload(act_id, data)
+    if not act_list[act_id] then
+        activitytime = activitytime or skynet.localname(".activitytime")
+        local info = skynet.call(activitytime, "lua", "get_sid_info", act_id)
+        if not info then
+            logger.error("活动排行 未找到活动数据:%s", act_id)
+            return 
+        end
+        if info.state == STATE_OPEN then
+            act_list[act_id] = init_act_info(act_id)
+        else
+            act_list[act_id] = init_act_info(act_id, THIS_CLOSE)
+        end
+        save_act_info(act_id)
+    end
+
+    if act_list[act_id].state ~= THIS_OPEN and act_list[act_id].state ~= THIS_SYN then
+        return
+    end
+    act_list[act_id].list = act_list[act_id].list or {}
+    if not act_list[act_id].list[data.role.uid] then
+        act_list[act_id].list[data.role.uid] = 1
+        save_act_info(act_id)
+    end
+    local key = string.format(KEY_STR, act_id)
+    ranklist[act_id] = ranklist[act_id] or {}
+    local list = ranklist[act_id]
+
+    local k = find_uid(list, data.role.uid)
+    if k then
+        list[k] = data
+        redis:hset(key, data.role.uid, cjson_encode(data))
+        return
+    end
+
+    local show = showlist[act_id] or {}
+    if show[MAX_RANK] and show[MAX_RANK].num > data.num then
+        return
+    end
+    list[#list+1] = data
+    redis:hset(key, data.role.uid, cjson_encode(data))
+end
+
+function CMD.get_ranking(act_id)
+    logger.trace("showlist"..stringify(showlist))
+    return showlist[act_id] or {}
+end
+
+-- 从排行榜中删除玩家
+function CMD.del(uid)
+    for act_id, list in pairs(showlist) do
+        local act_info = act_list[act_id]
+        if not act_info or (act_info.state == THIS_AWARD or act_info.state == THIS_SYN) then
+            local k = find_uid(list, uid)
+            if k then
+                table.remove(list, k)
+            end
+        end
+    end
+
+    for act_id, list in pairs(ranklist) do
+        local act_info = act_list[act_id]
+        if not act_info or (act_info.state == THIS_AWARD or act_info.state == THIS_SYN) then
+            local key = string.format(KEY_STR, act_id)
+            local k = find_uid(list, uid)
+            if k then
+                table.remove(list, k)
+                redis:hdel(key, uid)
+            end
+        end
+    end
+end
+
+function CMD.start()
+    init()
+    watchtime()
+end
+
+skynet.init(function()
+  skynet.register(".act_rankinglist")
+end)
+
+skynet.start(function()
+  skynet.dispatch("lua", function(session, _, cmd, ...)
+    local f = assert(CMD[cmd])
+    if 0==session then
+      f(...)
+    else
+      skynet.retpack(f(...))
+    end
+  end)
+end)

+ 375 - 0
project/service/activitytime.lua

@@ -0,0 +1,375 @@
+--活动时间服务
+local skynet = require "skynet"
+require "skynet.manager"
+local redisdriver = require "skynet.db.redis"
+local logger = require "logger"
+local common_fun = require "model.common_fun"
+local util = require "util"
+local asset = require "model.asset"
+local cjson    = require "cjson"
+local stringify = require "stringify"
+local queue       = require "skynet.queue"
+
+local trace = logger.trace
+--local trace = function(...) end
+local string_format = string.format
+local skynet_retpack = skynet.retpack
+local synchronized   = queue()
+cjson.encode_sparse_array(true, 1)
+
+local STATE_OPEN = 0    -- 活动开启
+local STATE_CLOSE = 1   -- 活动结束
+
+local CMD = {}
+local activity = {}
+local THIS = {}
+local finishtime = {    --记录已经开启活动,或者即将开启的活动
+--[[
+  [activ_tyep] = {
+    -- 活动信息
+  }
+]]
+}
+local recent_finishtime = { -- 开启/待开启/结束 (先筛选开启中 - 筛选待开启 - 筛选以结束)
+}
+
+local all_activity_time = {}    -- 所有活动的数据 [sid] = {}
+
+
+local usercenter        --玩家服务
+local redis             --数据库
+local KEY_FORMAT = "activity:%s"
+local globaltime        --开服时间
+local TEN_YEAR          -- 开服十年后的时间节点
+
+local cache_acti = {      -- 内存活动数据 配置表转发而来
+--[[
+  [id] = {
+    活动信息
+  }
+]]
+}
+
+local IMM_SEND = false    -- 立即推送活动数据
+
+local calculate = {}
+-- TODO: 活动时间类型 1
+--[[
+  opentype = 1 永久活动 直接开启十年
+]]
+calculate[1] = function(aconfig, t_type, act_info)
+    act_info[1] = STATE_OPEN
+    act_info[2] = globaltime
+    act_info[3] = TEN_YEAR
+end
+
+-- TODO: 活动类型 2
+--[[
+  opentype = 2 限时活动
+]]
+calculate[2] = function(aconfig, t_type, act_info, now)
+    local opentime = common_fun.split(aconfig.starttime)
+    local endtime = common_fun.split(aconfig.endtime)
+    if now >= opentime and now < endtime then
+        act_info[1] = STATE_OPEN
+    else
+        act_info[1] = STATE_CLOSE
+    end
+    act_info[2] = opentime                      -- 活动开启时间
+    act_info[3] = endtime                       -- 活动结束时间
+end
+
+
+function THIS.split_unknown(str)
+    local ret = {}
+    for t in string.gmatch(str, "(%d+)%/?") do
+      table.insert(ret, 1, tonumber(t))
+    end
+    return ret
+end
+
+local function math_type6(tab)
+    return tab[1]+ (tab[2] or 0)*MIN_SEC + (tab[3] or 0) * HOUR_SEC + ((tab[4] or 0)-1) *DAY_SEC
+end
+
+-- TODO: 活动类型 3
+--[[
+  opentype = 3 开服活动
+]]
+calculate[3] = function(aconfig, t_type, act_info, now)
+    local list1 = THIS.split_unknown(aconfig.starttime)      -- 活动开启配置
+    local list2 = THIS.split_unknown(aconfig.endtime)          -- 活动结束配置
+    local opentime = globaltime + math_type6(list1)
+    local endtime = globaltime + math_type6(list2)
+    if now >= opentime and now < endtime then
+        act_info[1] = STATE_OPEN
+    else
+        act_info[1] = STATE_CLOSE
+    end
+    act_info[2] = opentime                      -- 活动开启时间
+    act_info[3] = endtime                       -- 活动结束时间
+end
+
+
+-- TODO: 计算活动开启时间, 及活动现在状态
+local function calculate_time(aconfig)
+    local t_type = aconfig.opentype              -- 活动开启类型
+    -- globaltime                                 -- 开服时间
+    local now = os.time()
+    local act_info = {
+        [1] = STATE_CLOSE,          -- 活动状态
+        [2] = 0,                    -- 活动开启时间
+        [3] = 0,                    -- 活动结束时间
+    }
+    calculate[t_type](aconfig, t_type, act_info, now)
+    logger.trace(" id = %s, type = %s, opentype = %s, state = %s, open = %s, end = %s",
+        aconfig.ID, aconfig.type, t_type, act_info[1], os.date("%Y-%m-%d %H:%M:%S",tonumber(act_info[2])),
+        os.date("%Y-%m-%d %H:%M:%S",tonumber(act_info[3]))
+    )
+    return table.unpack(act_info)
+end
+
+
+-- TODO: 活动状态发生变化,推送活动数据
+function activity.send_active_info()
+    --通知在线的玩家有活动的状态发生了变更
+    skynet.send(usercenter, "lua", "command", "update", finishtime)--向服务器推送
+    trace("限时活动时间new_finishtime:%s",stringify(finishtime))
+end
+
+-- TODO: 热更接口
+function CMD.hotfix(list)
+    if list.ActivityConfig_proto then
+        synchronized(function()
+            logger.trace(" ========== 热更活动配置表")
+            -- 读取配置,活动时间数据
+            local temp = activity.load_conf()
+            cache_acti = temp
+            IMM_SEND = true
+        end)
+    end
+end
+
+-- TODO: 检查有活动状态, 并返回已经开启的活动 及 是否有活动状态变化
+function THIS.check_active_state()
+    local open_act = {}         -- 开启中得活动
+    local finsi_act = {}        -- 结束了得活动
+    local recent_act = {}       -- 待开启得活动
+
+    local now = os.time()
+    local nstate
+    local change = false
+    for sid, ainfo in pairs(cache_acti) do
+        if ainfo.opentime <= now and ainfo.endtime > now then
+            nstate = STATE_OPEN
+            if open_act[ainfo.activity_type] then
+                if ainfo.sid < open_act[ainfo.activity_type].sid then
+                    open_act[ainfo.activity_type] = ainfo
+                    change = true
+                end
+            else
+                open_act[ainfo.activity_type] = ainfo
+            end
+        else
+            nstate = STATE_CLOSE
+        end
+
+        if now < ainfo.opentime then        -- 待开启活动
+            if recent_act[ainfo.activity_type] then
+                if recent_act[ainfo.activity_type].opentime > ainfo.opentime then  -- 取时间小得
+                    recent_act[ainfo.activity_type] = ainfo
+                end
+            else
+                recent_act[ainfo.activity_type] = ainfo
+            end
+        end
+
+        if now >= ainfo.endtime then        -- 已经结束得活动
+            if finsi_act[ainfo.activity_type] then
+                if finsi_act[ainfo.activity_type].endtime < ainfo.endtime then    -- 取时间大得
+                    finsi_act[ainfo.activity_type] = ainfo
+                end
+            else
+                finsi_act[ainfo.activity_type] = ainfo
+            end
+        end
+
+        -- 检查状态是否变化
+        if nstate ~= ainfo.state then
+            logger.trace(" -- 活动状态改变 sid = %s, ainfo = %s", sid, stringify(ainfo))
+            change = true
+            ainfo.state = nstate
+        end
+    end
+    return open_act, finsi_act, recent_act, change
+end
+
+--创建检查活动配置时间的协程
+function activity.detection()
+    skynet.fork(function()
+        local change, finsi_act, recent_act, recent_temp
+
+        -- 处理 最近活动数据
+        local dis_recent = function(tt, act)
+            for sid, v in pairs(act) do
+                if not tt[sid] then
+                tt[sid] = v
+                end
+            end
+        end
+
+        while true do
+            synchronized(function()
+                finishtime, finsi_act, recent_act, change = THIS.check_active_state()
+                -- 有活动发生了变化
+                -- logger.trace(" ===============finishtime = %s", stringify(finishtime))
+                recent_temp = {}
+                dis_recent(recent_temp, finishtime)
+                dis_recent(recent_temp, finsi_act)
+                dis_recent(recent_temp, recent_act)
+                recent_finishtime = recent_temp
+
+                if change or IMM_SEND then
+                    activity.send_active_info()
+                    IMM_SEND = false
+                end
+                redis:set("activity:activity_time", cjson.encode(finishtime))
+            end)
+            skynet.sleep(100)
+        end
+    end)
+end
+
+--获得限时活动指定的时间
+function CMD.getassigntime(name)
+    return synchronized(function()
+        return finishtime[name]--state0是关闭1是开启
+    end)
+end
+
+--玩家上线的时候获取一次所有限时活动的时间
+function CMD.getactivitytime()
+    return synchronized(function()
+        return finishtime
+    end)
+end
+
+-- 获取对接活动数据
+function CMD.get_recent_activitytime()
+    return synchronized(function()
+        return recent_finishtime
+    end)
+end
+
+-- 获取对接活动数据
+function CMD.get_recent_assigntime(name)
+    return synchronized(function()
+        return recent_finishtime[name]
+    end)
+end
+
+function CMD.get_sid_info(sid)
+    return all_activity_time[sid]
+end
+
+-- TODO: 准备-加载数据库数据
+function activity.prepare()
+    local key = string_format(KEY_FORMAT, "global")
+    local res = redis:hget(key, "starttime")
+    if res then
+        globaltime = util.today(tonumber(res)+HOUR_SEC) -- 防止冬夏令时导致的时间异常
+    else
+        res = util.today()
+        globaltime = res
+        redis:hmset(key, "starttime", res)
+    end
+end
+
+function CMD.query()
+    return globaltime
+end
+
+function CMD.open_day()
+    return math.max(1, (util.today() - globaltime)/DAY_SEC + 1)
+end
+
+
+-----------------------------------------------------------------------
+-- TODO: 筛选本服得活动
+function activity.filtrate_activity()
+    local ret = {}
+    local act_conf = asset.ActivityConfig_proto
+    for sid, info in pairs(act_conf) do
+        ret[sid] = common_fun.scopy(info)
+    end
+    return ret
+end
+
+-- TODO: 加载活动配置数据,并生成时间节点 及活动状态
+function activity.load_conf()
+    local act_conf = activity.filtrate_activity()
+    local temp_act = {}                         -- 配置表转发活动数据结构, 临时存储
+
+    for sid, aconfig in pairs(act_conf) do
+        local astate, aopne_tm, aend_tm = calculate_time(aconfig)
+        temp_act[sid] = {
+            sid = sid,                                          -- 活动sid
+            state = astate,                                     -- 活动状态
+            switch = aconfig.switch,                            -- 临时关闭标记
+            activity_type = aconfig.type,                       -- 活动类型
+            name = aconfig.name,                                -- 活动名字
+            opentime = aopne_tm,
+            endtime = aend_tm,
+            sort = aconfig.sort,
+            icon = aconfig.icon                                 -- 活动icon
+        }
+        all_activity_time[sid] = temp_act[sid]
+    end
+    return temp_act
+end
+
+function CMD.start()
+  usercenter = usercenter or skynet.localname(".usercenter")
+
+  local conf = assert(option.redis)
+  redis = redisdriver.connect(conf)
+  redis:select(1)--切换到数据库db1
+  activity.prepare()
+
+  TEN_YEAR = globaltime + DAY_SEC*365*10
+
+  -- 读取配置,活动时间数据
+  cache_acti = activity.load_conf()
+
+  IMM_SEND = true
+  activity.detection()--检查时间
+
+  -- 冲关
+
+  -- skynet.fork(function()
+    -- skynet.sleep(300)
+    -- CMD.hotfix()
+  -- end)
+end
+
+skynet.info_func(function()
+  return {
+    open_activie = finishtime,      -- 已经开启的活动关
+    cache_acti   = cache_acti,      -- 所有活动情况
+  }
+end)
+
+skynet.init(function()
+  skynet.register(".activitytime")
+end)
+
+skynet.start(function()
+  skynet.dispatch("lua", function(session, _, cmd, ...)
+    local f = assert(CMD[cmd])
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+  end)
+end)

+ 249 - 0
project/service/agent.lua

@@ -0,0 +1,249 @@
+local skynet = require "skynet"
+-- local socket = require "skynet.socket"
+-- local sprotoloader = require "sprotoloader"
+local logger = require "logger"
+local timer = require "timer"
+local player = require "model.player"
+local cjson = require "cjson"
+-- local JsSProto = require "JsSProto"
+local websocket = require "http.websocket"
+
+
+local traceback = debug.traceback
+local skynet_retpack = skynet.retpack
+local skynet_call = skynet.call
+local skynet_send = skynet.send
+local string_pack = string.pack
+local string_format = string.format
+local websocket_write = websocket.write
+local trace = logger.trace
+local warn = logger.warn
+
+local handle_request = player.request
+local handle_response = player.response
+local handle_command = player.command
+local handle_disconnect = player.disconnect
+local pbSproto = require "pbSproto"
+
+local cjson_decode   = cjson.decode   -- 解码
+local cjson_encode   = cjson.encode   -- 编码
+
+-- local mydispatch = JsSProto.decode
+-- local mypack = JsSProto.encode
+local mydispatch = pbSproto.decode
+local mypack = pbSproto.encode
+
+local fd
+-- local host = sprotoloader.load(1):host( "package")
+-- local pack = host:attach(sprotoloader.load(2))
+local abandon
+local watchdog
+local protocol
+local window = 0
+local tracker = {}
+local function write(data)
+    return websocket_write(fd, data, "binary")
+end
+
+local function send(pname, args, callback)
+    return write(mypack(pname, args))
+end
+
+local function kick(reason, ...)
+  if abandon then return end
+  if reason then
+    logger.error(reason, ...)
+  end
+  abandon = true
+  skynet_call(watchdog, "lua", "close", fd)
+end
+
+local kill_timer
+local function throw_exception(type, brief, linger)
+  assert(type)
+  brief = brief or "unknown"
+  if abandon then
+    return
+  end
+
+--   send('exception', { type=type, brief=brief })
+  send('exception_nty', { errno=brief })
+  linger = linger or 500
+  kill_timer = timer(linger, function()
+    kick("exception: { type=%s, brief=%s }", type, brief)
+  end)
+end
+
+local function dispatch_request(pname, args, response)
+  if abandon then
+    trace("drop a request message: %s", pname)
+    return
+  end
+  local r = handle_request(pname, args)
+  if response and r then
+    write(response(r))
+  end
+end
+
+local function dispatch_response(session, args)
+  local ctx = assert(tracker[session])
+  local pname = assert(ctx.pname)
+  local callback = assert(ctx.callback)
+  tracker[session] = nil
+  if abandon then
+    trace("drop a response message: %s", pname)
+    return
+  end
+  handle_response(pname, args, callback)
+end
+
+skynet.register_protocol {
+	name = "client",
+	id = skynet.PTYPE_CLIENT,
+	unpack = function (msg, sz)
+		return mydispatch(msg, sz)
+	end,
+	dispatch = function(_, _, type, pname, ...)
+        skynet.ignoreret()
+        local f = (type == "REQUEST") and dispatch_request
+        if f then
+            local ok, msg = xpcall(f, traceback, pname, ...)
+            if not ok then
+                kick("pname: %s, err: %s", pname, msg)
+            end
+        end
+    end
+}
+
+local CMD = {}
+function CMD.start(ctx)
+  local args = assert(ctx.args)
+  local gate = assert(ctx.gate)
+  local ipaddr = assert(ctx.ipaddr)
+  fd = assert(ctx.fd)
+  watchdog = assert(ctx.watchdog)
+  protocol = ctx.protocol
+  websocket.forward(fd, protocol, ipaddr)
+  local resp = player.start({
+    args = args,
+    ipaddr = ipaddr,
+    send = send,
+    kick = kick,
+    throw_exception = throw_exception,
+    pbSproto = pbSproto,
+  })
+
+  skynet_call(gate, "lua", "forward", fd)
+
+  local skynet_sleep = skynet.sleep
+  skynet.fork(function()
+    collectgarbage "collect"
+    while true do
+      skynet_sleep(60*100)
+      local kb, bytes = collectgarbage "count"
+      if kb >= 4096 then
+        logger.trace("内存超过4M,将启动垃圾回收")
+        collectgarbage "collect"
+      end
+    end
+  end)
+  return resp
+end
+
+local suspend = nil
+local dispose = nil
+local function coroutine_yield()
+  local co = coroutine.running()
+  suspend = suspend or {}
+  table.insert(suspend, co)
+  skynet.wait(co)
+  if dispose then
+    dispose()
+  end
+end
+
+local function coroutine_resume()
+  suspend = suspend or {}
+
+  local ref = 0
+  for _, co in ipairs(suspend) do
+    ref = ref + 1
+    skynet.wakeup(co)
+  end
+
+  if ref > 0 then
+    local co = coroutine.running()
+    dispose = function()
+      ref = ref - 1
+      if ref == 0 then
+        skynet.wakeup(co)
+      end
+    end
+    skynet.wait(co)
+  end
+end
+
+function CMD.disconnect()
+  -- todo: do something before exit
+    -- skynet.sleep(1000)
+  if kill_timer then
+    kill_timer()
+  end
+  handle_disconnect()
+  websocket.clear_pool(fd)
+  coroutine_resume()
+  skynet.exit()
+end
+
+-- Reference: watchdog.command.hotfix
+function CMD.exit()
+  skynet.exit()
+end
+
+-- Reference: usercenter.command.maintain
+function CMD.maintain(...)
+  local args=select(1, ...)
+  local key
+  if select("#",...)==0 then
+    key=STD_ERR.SERVER_START_MAINTENANCE --"服务器开始维护"
+  else
+    key=args
+  end
+  throw_exception("maintain",key, 300)
+  coroutine_yield()
+end
+
+-- Reference: usercenter.command.login
+function CMD.multilogin()
+  throw_exception("multilogin", STD_ERR.ACCOUNT_LOGGING_ANOTHER_DEVICE, 100) --"帐号正在其它设备登录"
+  coroutine_yield()
+end
+
+function CMD.broad_cast_msg(pname, args)
+  assert(pname)
+  trace("CMD.broad_cast_msg: %s", pname)
+  send(pname, args or {})
+end
+
+function CMD.kick(reason, ...)
+--   send('exception',{ type="kick", brief=reason })
+  kick(reason, ...)
+end
+
+skynet.start(function()
+  logger.label("<Agent>")
+	skynet.dispatch("lua", function(session,_, cmd, ...)
+    trace("handle command: '%s'", cmd)
+    local f = CMD[cmd]
+    if f then
+      if session == 0 then
+        f(...)
+      else
+        skynet_retpack(f(...))
+      end
+    else
+      handle_command(session,cmd, ...)
+    end
+	end)
+end)
+

+ 110 - 0
project/service/assetcenter.lua

@@ -0,0 +1,110 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local sharedata = require "skynet.sharedata"
+local logger = require "logger"
+local cache = require "skynet.codecache"
+cache.mode "OFF"	-- turn off codecache, because CMD.new may load data file
+
+local index = ...
+local SET_FILES
+
+local path = skynet.getenv "asset"
+local skynet_retpack = skynet.retpack
+local FILES = {
+  -- TODO: 在下面定义需要引入的配置文件
+    DataConfig_proto = "DataConfig_proto",              -- 离散表
+    GoodsConfig_proto = "GoodsConfig_proto",            -- 货币表
+    StageConfig_proto = "StageConfig_proto",            -- 关卡表
+    StageInfoConfig_proto = "StageInfoConfig_proto",    -- 关卡信息表
+    RoleConfig_proto = "RoleConfig_proto",              -- 角色表
+    RoleLevelConfig_proto = "RoleLevelConfig_proto",    -- 角色升级表
+    RanksLevelConfig_proto = "RanksLevelConfig_proto",  -- 队伍升级表
+    ArmorConfig_proto = "ArmorConfig_proto",            -- 装备表
+    EquipmentQualityConfig_proto = "EquipmentQualityConfig_proto", -- 装备升阶表
+    EquipmentLevelConfig_proto = "EquipmentLevelConfig_proto",  -- 装备升级表
+    AwardConfig_proto   = "AwardConfig_proto",          -- 奖励库id
+    TalentConfig_proto = "TalentConfig_proto",          -- 天赋配置表
+    SigninConfig_proto = "SigninConfig_proto",          -- 签到配置
+    GiftConfig_proto = "GiftConfig_proto",              -- 计费点礼包配置
+    RechargeConfig_proto = "RechargeConfig_proto",      -- 充值计费点
+    MonsterConfig_proto = "MonsterConfig_proto",        -- 怪物表
+    NewawardConfig_proto = "NewawardConfig_proto",      -- 奖励库活动
+    EntryConfig_proto = "EntryConfig_proto",            -- 词条表
+    BoxConfig_proto = "BoxConfig_proto",                -- 宝箱配置
+}
+
+
+-- TODO: 集群使用的配置表
+local SPAN_FILES = {
+    
+}
+
+local function encode(filename)
+  return string.format("@%s%s.lua", path, filename)
+end
+
+local function reload(k, filename)
+  local ok = pcall(sharedata.update, k, encode(filename))
+  if ok then
+    logger.warn("hotfix: '%s.lua'", k)
+  else
+    return filename
+  end
+end
+
+local function hotfix(...)
+  local args = { ... }
+  if #args == 0 then
+    for k, v in pairs(SET_FILES) do
+      local error = reload(k, v)
+      if error then
+        return error
+      end
+    end
+  else
+    for _, k in ipairs(args) do
+      local v = SET_FILES[k]
+      if v then
+        local error = reload(k, v)
+        if error then
+          return error
+        end
+      end
+    end
+  end
+end
+
+local CMD = {}
+function CMD.start()
+  logger.info("start")
+
+  if index == "master" then
+    SET_FILES = SPAN_FILES
+  else
+    SET_FILES = FILES
+  end
+  for k, filename in pairs(SET_FILES) do
+    sharedata.new(k, encode(filename))
+  end
+end
+
+function CMD.hotfix(...)
+  local error = hotfix(...)
+  collectgarbage "collect"
+  return error
+end
+
+skynet.memlimit(128 * 1024 * 1024)
+skynet.init(function() skynet.register(".assetcenter") end)
+skynet.start(function()
+  logger.label("<Assetcenter>")
+  skynet.dispatch("lua", function(session, _, cmd, ...)
+    local f = assert(CMD[cmd])
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+  end)
+end)
+

+ 57 - 0
project/service/gift.lua

@@ -0,0 +1,57 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local asset = require "model.asset"
+local stringify = require "stringify"
+
+local skynet_retpack = skynet.retpack
+
+local list = {
+    --[[
+        [shop_type] = 
+        {
+            {
+                id = id
+                num = num
+            }
+        }
+    ]]
+
+}
+
+local CMD = {}
+
+local function optimize()
+    local temp = {}
+    for _, conf in pairs(asset.GiftConfig_proto) do
+        temp[conf.type] = temp[conf.type] or {}
+        table.insert(temp[conf.type], conf)
+    end
+    list = temp
+    -- logger.trace("gift:"..stringify(list))
+end
+
+function CMD.start()
+    logger.info("start")
+    optimize()
+end
+
+function CMD.get_all_gift(model_id)
+    return list[model_id] or {}
+end
+
+skynet.init(function()
+    skynet.register(".gift")
+end)
+  
+skynet.start(function()
+    logger.label("<Gift>,")
+    skynet.dispatch("lua", function(session,_, cmd, ...)
+        local f = assert(CMD[cmd])
+        if session == 0 then
+            f(...)
+        else
+            skynet_retpack(f(...))
+        end
+    end)
+end)

+ 151 - 0
project/service/itemavg.lua

@@ -0,0 +1,151 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local common_fun = require "model.common_fun"
+local stringify = require "stringify"
+local pipeline = require "pipeline"
+local redisdriver = require "skynet.db.redis"
+local KEY_STR = "itemavg"
+local util = require "util"
+local redis
+
+local cjson     = require "cjson"
+cjson.encode_sparse_array(true, 1)
+
+local cjson_decode   = cjson.decode   -- 解码
+local cjson_encode   = cjson.encode   -- 编码
+
+local change = false
+
+
+local CMD = {}
+
+local function toFixed(a, b)
+    local num = 10 ^ b
+    local ret = math.floor(a * num + 0.5)/num
+    return ret
+end
+
+local clear_time = 0
+
+local avglist = {
+    --[[
+        [id] = {
+            list = {
+                [lv] = {
+                    num = 人数,
+                    avg = 平均,
+                }
+            }
+            all = {num, avg}
+        }
+    ]]
+}
+
+local function init()
+    local conf = assert(option.redis)
+    redis = redisdriver.connect(conf)
+    redis:select(12)
+    local retnuma = redis:hgetall(KEY_STR)
+    local time = redis:get(KEY_STR..":clear_time")
+    if time then
+        clear_time = tonumber(time) or 0
+    end
+    for k = 1, #retnuma, 2 do
+        local key = tostring(retnuma[k])
+        if key then
+            avglist[key] = cjson_decode(retnuma[k+1])
+        end
+    end
+end
+
+local function save()
+    if change then
+        local batch = pipeline(124)
+        for id, data in pairs(avglist) do
+            batch.add("hset", KEY_STR, id, cjson_encode(data))
+        end
+        batch.execute(redis)
+        change = false
+    end
+end
+
+function CMD.updata(data)
+    if not data then
+        return 
+    end
+    local lv = tostring(data.level)
+    for _, v in ipairs(data.list or {})do
+        local key = tostring(v.id)
+        local num = v.num
+        avglist[key] = avglist[key] or {
+            list = {},
+            all = {num = 0, avg = 0}
+        }
+        avglist[key].list[lv] = avglist[key].list[lv] or {
+            num = 0,
+            avg = 0,
+        }
+        local all = avglist[key].all
+        local cur = avglist[key].list[lv]
+        all.num = all.num + 1
+        all.avg = all.avg + toFixed((num - all.avg)/all.num, 2)
+        cur.num = cur.num + 1
+        cur.avg = cur.avg + toFixed((num - cur.avg)/cur.num, 2)
+    end
+    change = true
+end
+
+function CMD.show(id)
+    if not id then
+        return stringify(avglist)
+    end
+    return stringify(avglist[id] or {})
+end
+
+local function check_clear(time)
+    if time > clear_time then
+        clear_time = util.today()+DAY_SEC
+        redis:set(KEY_STR..":clear_time", clear_time)
+        avglist = {}
+        redis:del(KEY_STR)
+        change = false
+    end
+end
+
+local function watchtime()
+    skynet.fork(function()
+        local interval =  1-- 更新间隔为2分钟
+        local up_time = os.time()
+        while(true) do
+            local now = os.time()
+            if now > up_time then
+                up_time = up_time + interval
+                check_clear(now)
+                save()
+            end
+            skynet.sleep(100)
+        end
+    end)
+end
+
+function CMD.start()
+    init()
+    watchtime()
+end
+
+skynet.init(function()
+    skynet.register(".itemavg")
+end)
+  
+skynet.start(function()
+    skynet.dispatch("lua", function(session, _, cmd, ...)
+        local f = assert(CMD[cmd])
+        if 0==session then
+            f(...)
+        else
+            skynet.retpack(f(...))
+        end
+    end)
+end)
+  

+ 64 - 0
project/service/log/init.lua

@@ -0,0 +1,64 @@
+-- 日志服务
+local skynet = require "skynet"
+require "skynet.manager"
+local cluster = require "skynet.cluster"
+local logger = require "logger"
+local util = require "util"
+local cjson = require "cjson"
+
+local cjson_encode = cjson.encode
+cjson.encode_sparse_array(true, 1)
+
+local skynet_retpack = skynet.retpack
+local skynet_pack = skynet.pack
+local skynet_redirect = skynet.redirect
+local sid   -- server id
+local logserver
+
+local CMD = {}
+function CMD.start()
+    sid = assert(option.sid)
+    if option.cluster and option.cluster.log then
+        logserver = cluster.query("log", "logserver")
+    end
+    logger.info("start")
+end
+
+function CMD.record(uid, content)
+    assert(content)
+    assert(type(content) == "string")
+    local length = #content
+    if length > 2048 then
+        logger.warn("日志内容超过 2048 字节")
+    end
+    if logserver then
+        cluster.send("log", logserver, "record", sid, uid, content)
+    else
+        logger.warn("\n%s", content)
+    end
+end
+
+function CMD.http_record(uid, content)
+    if logserver then
+        cluster.send("log", logserver, "http_record", sid, uid, content)
+    else
+        logger.trace(" http_record 日志发送 %s", cjson_encode(content))
+    end
+end
+
+skynet.init(function()
+    skynet.register(".log")
+end)
+
+skynet.memlimit(128 * 1024 * 1024)
+skynet.start(function()
+    logger.label("<Log>,")
+    skynet.dispatch("lua", function(session, source, cmd, ...)
+        local f = assert(CMD[cmd])
+        if 0==session then
+            f(...)
+        else
+            skynet_retpack(f(...))
+        end
+    end)
+end)

File diff suppressed because it is too large
+ 100 - 0
project/service/loginserver/apple.lua


+ 90 - 0
project/service/loginserver/google.lua

@@ -0,0 +1,90 @@
+--facebook
+local skynet = require "skynet"
+local httpc = require "http.httpc"
+local logger = require "logger"
+local cjson = require "cjson"
+local stringify = require "stringify"
+local md5 = require "md5"
+require "skynet.manager"
+
+local trace = logger.trace
+local cjson_decode = cjson.decode
+
+local cdkconf = {
+  host = "https://oauth2.googleapis.com",
+  url = "/tokeninfo?id_token=%s",
+  ["gameloft_infinite"] = { -- 智乐互娱 海外包
+    forbidden = false,          -- 是否开放
+    ["com.xjl3.www"] = {},        -- google包
+    ["com.xjl3ios.www"] ={},     -- ios
+    ['com.xjl3ios.appname'] = {}, -- 智乐互娱 IOS A2
+    ['com.fantasticios.appname'] = {}, -- 智乐互娱 IOS A3
+    ['com.xjl3.appname'] = {}, -- 智乐互娱 google A5
+  },
+  ["wydh_ft"] = {               -- 微赢互动 繁体包
+    forbidden = false,          -- 是否开放
+    ["com.wyyx.fzxls"] = {},    -- google
+    ["com.lzl.fzxlsft"] = {},   -- IOS
+    ["com.cxjjwk.pkq"] = {},    -- google
+  },
+  ["wyhd_ft_bt"] = {          -- ft bt
+    forbidden = false,
+    ['com.rain.mhlc'] = {},
+  },
+}
+
+local google = {}
+
+function google.login_player(args)
+    local channel = args.channel
+    if not cdkconf[channel] then
+      logger.trace("### google 不存在的渠道 %s", channel)
+      return
+    end
+    if cdkconf[channel].forbidden then
+      logger.trace("### google 渠道登陆关闭 %s",channel)
+      return
+    end
+    local channel_info = cdkconf[channel][args.appid]
+    logger.error("google 登陆请求 appid:%s,channel:%s",args.appid,channel)
+    -- if not channel_info or not channel_info.appid or not channel_info.appkey then
+    if not channel_info  then
+      logger.trace("google登录失败 错误的渠道信息")
+        return
+    end
+
+    local info = string.format(cdkconf.url, args.token)
+    logger.trace("google login start %s",info)
+    local ok, ret, body = pcall(httpc.get,cdkconf.host,info)
+    logger.trace("google login end %s",args.channel)
+    if ok then
+      logger.trace("google回馈:%s",body)
+      local resp = cjson_decode(body) or {}
+      if resp.iss and resp.exp then
+        return 0
+      else
+        logger.trace("google登录验证失败body:%s", body)
+        return
+      end
+    else
+      logger.trace("google登录验证 ret:%s", ret)
+      return
+    end
+    logger.trace("google登录验证 info2:%s", info)
+    return
+end
+
+function google.login_test()
+  local ret = google.login_player({
+    channel = "gameloft_infinite_and",      -- 渠道名
+    appid = "com.xjl3.www",                 -- 包名
+    token = "ajfdalsjf",                   -- SDK 返回密钥
+  })
+  if ret then
+    logger.trace(" ### 登陆成功")
+  else
+    logger.trace(" ### 登陆失败")
+  end
+end
+
+return google

+ 63 - 0
project/service/loginserver/init.lua

@@ -0,0 +1,63 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local cjson = require "cjson"
+local logger = require "logger"
+local stringify = require "stringify"
+local sha2 = require "sha2"
+local sha256 = sha2.sha256
+
+local trace = logger.trace
+local warn = logger.warn
+local shengtian = require "service.loginserver.shengtian"
+
+local SDK = {}
+SDK.shengtian = shengtian.login_player
+
+local CMD = {}
+function CMD.start(conf)
+  logger.info("start")
+
+--   skynet.fork(function()
+--     skynet.sleep(500)
+--     logger.trace(" #### 登陆测试 #### ")
+--     shengtian.login_test()
+--   end)
+end
+
+function CMD.login(args)
+    if args.channel == "" then
+        return 0
+    end
+    trace("login request:\n%s", stringify(args))
+
+    local k = args.channel or ""
+    if string.len(k) == 0 then
+        warn("invalid channel identifier")
+        return
+    end
+
+    local auth = SDK[k]
+    if auth == nil then
+        warn("channel [%s]: unrealized", k)
+        return
+    end
+    return auth(args)
+end
+
+skynet.init(function()
+  skynet.register(".loginserver")
+end)
+
+skynet.start(function()
+  local skynet_retpack = skynet.retpack
+  logger.label("<Login server>,")
+
+	skynet.dispatch("lua", function(session, source, cmd, ...)
+    local f = assert(CMD[cmd], cmd)
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+	end)
+end)

+ 196 - 0
project/service/loginserver/shengtian.lua

@@ -0,0 +1,196 @@
+--圣天sdk
+local httpc = require "http.httpc"
+local logger = require "logger"
+local cjson = require "cjson"
+local stringify = require "stringify"
+local md5 = require "md5"
+
+local trace = logger.trace
+local warn = logger.warn
+local cjson_decode = cjson.decode
+local cjson_encode = cjson.encode
+
+local MODULE = {}
+
+local cdkconf = {
+  host = "https://yfy-api.t1degames.com",
+  loginUrl = "/login/s/gameAuthentication",
+  uploadUrl = "/actor/report/",
+  forbidden = false,
+  key = "fkybt_519d7c1a09",
+  secret = "226f965616a3daac306f9cfb2aa72f58",
+  gameCode = "FKYBT",
+}
+
+local function sign_str(data)
+    -- 提取键到一个数组中
+    local keys = {}
+    for key, _ in pairs(data) do
+        table.insert(keys, key)
+    end
+    table.sort(keys)
+
+    local str = ""
+    for i, key in ipairs(keys) do
+        if key ~= "sign" and (key ~= "extension" or data[key] ~= "") then
+            if str == "" then
+                str = string.format("%s=%s", key, data[key])
+            else
+                str = string.format("%s&%s=%s", str, key, data[key])
+            end
+        end
+    end
+    str = str..cdkconf.secret
+    -- trace(str)
+    return str
+end
+
+
+function MODULE.login_player(args)
+    -- Check input arguments
+    local account = args.account
+    if account == nil then
+        warn("invalid account")
+        return
+    end
+
+    local token = args.token
+    if token == nil then
+        warn("invalid token")
+        return
+    end
+
+    local token = args.token
+    local host = cdkconf.host
+    local url = cdkconf.loginUrl
+
+    local content = {
+        app_key = cdkconf.key,
+        extension = "",
+        fuse_token = token,
+        timestamp = os.time(),
+        sign = false
+    }
+    
+    local str = string.format("app_key=%s&fuse_token=%s&timestamp=%s%s", 
+        content.app_key,
+        content.fuse_token,
+        content.timestamp,
+        cdkconf.secret
+    )
+    
+    content.sign = string.lower(md5.sumhexa(str))
+    local error_msg = ""
+    -- trace("content:%s", str)
+    -- trace("content.sign:%s", content.sign)
+    -- if true then
+    --     return true
+    -- end
+    trace("shengtian: login start")
+    local ok, statuscode, body = pcall(httpc.post, host, url, content)
+    trace("shengtian: login end")
+    if ok then
+        local ret = cjson_decode(body)
+        -- trace("shengtian: login body"..stringify(ret or {}))
+        if ret.code == "200" then
+            return account == tostring(ret.data.uid)
+        else
+            error_msg = ret.msg
+        end
+    else
+        error_msg = string.format("Failed to call %s%s, param=%s",
+            host,
+            url,
+            cjson_encode(content))
+    end
+    warn(error_msg)
+end
+
+function MODULE.recharge_sign(data)
+    local contnet = {
+        game_app_key = cdkconf.key,
+        game_code = cdkconf.gameCode,
+        fuse_uid = data.fuse_uid,
+        order_id = data.order_id,
+        product_id = data.product_id,
+        product_name = data.product_name,
+        price = data.price,
+        role_id = data.role_id,
+        role_name = data.role_name,
+        role_level = data.role_level,
+        server_code = data.server_code,
+        server_name = data.server_name,
+        extension = data.extension,
+        timestamp = data.time,
+    }
+    local str = sign_str(contnet)
+    return string.lower(md5.sumhexa(str))
+end
+
+function MODULE.upload(uptype, data)
+    local host = cdkconf.host
+    local url = cdkconf.uploadUrl..cdkconf.gameCode
+
+    local content = {
+        time = os.time(),
+        uid = data.account or "",
+        type = uptype,
+        player_role_id = data.uid or "",
+        player_role_name = data.name or "",
+        player_server_id = data.server or "",
+        player_server_name = data.server or  "",
+        player_grade = data.level or "",
+        player_rein = "0",
+        player_power = data.power or "",
+        player_sex = "0",
+        player_vip_level = data.vip or "0"
+    }
+    local str = string.format("game_code=%s&time=%s&uid=%s%s", cdkconf.gameCode, content.time, content.uid, cdkconf.secret)
+    content.sign = md5.sumhexa(str)
+    -- trace("shengtian: upload start")
+    local ok, statuscode, body = pcall(httpc.post, host, url, content)
+    -- trace("shengtian: upload end")
+    local error_msg
+    if ok then
+        local ret = cjson_decode(body)
+        -- trace("shengtian: upload body"..stringify(ret or {}))
+        if ret.code == "200" then
+            return 
+        else
+            error_msg = ret.msg or ""
+        end
+    end
+    error_msg = error_msg or string.format("Failed upload %s%s, param=%s", host, url, cjson_encode(content))
+    warn(error_msg)
+end
+
+-- function MODULE.login_test()
+--   local args = {
+--     account = "27733215",
+--     token = "3ab72984f6ebbf085eaa65c6f0c53534bdce21f2",
+--   }
+--   MODULE.login_player(args)
+-- end
+
+function MODULE.sign_test()
+    local data = {
+        game_app_key = "fkybt_519d7c1a09",
+        game_code = "FKYBT",
+        fuse_uid = "27733215",
+        order_id = "1f309a30-a171-11ef-b457-eb63430875e1",
+        product_id = "20003",
+        product_name = "t7002",
+        price = "99",
+        role_id = "00-7262039605090717696",
+        role_name = "role#640",
+        role_level = "6",
+        server_code = "1",
+        server_name = "1",
+        extension = "{currency:TWD}",
+        timestamp = "1731469269"
+    }
+    trace(string.lower(md5.sumhexa(sign_str(data)))) 
+    trace("9bb6f6cbe7d5a3811808544b8efdedd9") 
+end
+
+return MODULE

+ 37 - 0
project/service/loginserver/tourist.lua

@@ -0,0 +1,37 @@
+local skynet = require "skynet"
+local httpc = require "http.httpc"
+local logger = require "logger"
+local cjson = require "cjson"
+local stringify = require "stringify"
+local md5 = require "md5"
+require "skynet.manager"
+
+local trace = logger.trace
+local cjson_decode = cjson.decode
+
+local conf = {
+  ["gameloft_infinite"] = { appkey = "h0DKUEW9GFHvUy4q"},    -- 智乐互娱-安卓/IOS
+  ["xiaohei"] = {}
+}
+
+local tourist = {}
+function tourist.login_player(args)
+    local channel = args.channel
+    local tconf = conf[channel]
+    if tconf then
+      local account = args.account
+      local appkey = tconf.appkey
+      local token = args.token
+      local mytoken = md5.sumhexa(account..appkey)
+      local ok = mytoken == token
+
+      if ok then
+          return 0
+      else
+          logger.trace("游客登录验证失败 account:%s, appkey:%s, token:%s, mytoken:%s",account, appkey, token, mytoken)
+          return
+      end
+    end
+end
+
+return tourist

+ 50 - 0
project/service/logserver.lua

@@ -0,0 +1,50 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local cluster = require "skynet.cluster"
+local util = require "util"
+
+local skynet_pack = skynet.pack
+local skynet_redirect = skynet.redirect
+
+skynet.start(function()
+  logger.info("Logserver start")
+  local daemon = skynet.getenv("daemon")
+  if not daemon then
+    skynet.newservice("console")
+  end
+  skynet.newservice("debug_console",
+                    "0.0.0.0",
+                    config.debug_console)
+
+  assert(config.slave)
+  local slave = {}
+  for i=1, #config.slave do
+    local addr = skynet.newservice("logslave", i)
+    skynet.call(addr, "lua", "start", config.slave[i])
+    table.insert(slave, addr)
+  end
+
+  assert(#slave > 0)
+  local thread = #slave
+  local os_date = os.date
+  local os_time = os.time
+  local string_format = string.format
+  local hashcode = util.hashcode
+
+  skynet.dispatch("lua", function(session, source, cmd, sid, uid, ...)
+    local ti = os_date("%Y-%m-%d", os_time())
+    local key = string_format("%s:%s:%s", sid, ti, uid)
+    local msg, sz = skynet_pack(cmd, key, ...)
+    local balance = hashcode(sid) % thread
+    if balance == 0 then
+      balance = thread
+    end
+    skynet_redirect(slave[balance], source, "lua", session, msg, sz)
+  end)
+
+  cluster.reload(config.cluster)
+  cluster.register("logserver", skynet.self())
+  cluster.open "log"
+end)
+

+ 159 - 0
project/service/logslave.lua

@@ -0,0 +1,159 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local cluster = require "skynet.cluster"
+local redisdriver = require "skynet.db.redis"
+local logger = require "logger"
+local stringify = require "stringify"
+local dns = require "skynet.dns"
+local httpc = require "http.httpc"	
+local cjson = require "cjson"	
+
+cjson.encode_sparse_array(true, 1)	
+
+local table_insert = table.insert
+local table_remove = table.remove
+local skynet_retpack = skynet.retpack
+local skynet_wakeup = skynet.wakeup
+local skynet_wait = skynet.wait
+local index = ...
+
+local MAX_PAGE_SIZE = 256
+local preview = false
+local threading = 0
+local processed = 0
+local received = 0
+local page = {}
+local cache = {}
+local wakeup_queue = {}
+local function wakeup(num)
+  if num <= 0 then
+    return
+  end
+
+  for co, _ in pairs(wakeup_queue) do
+    skynet_wakeup(co)
+    num = num - 1
+    if num == 0 then
+      break
+    end
+  end
+end
+
+local function push_back(...)
+  table_insert(page, {... })
+  if #page == MAX_PAGE_SIZE then
+    table_insert(cache, page)
+    page = {}
+    wakeup(#cache)
+  end
+end
+
+local function co_create(conf)
+  local self = redisdriver.connect(conf)
+  local function pipelining(ops)
+    local succ = pcall(self.pipeline, self, ops)
+    if not succ then
+      while true do
+        logger.warn("Reconnect to redis")
+        local ok, red = pcall(redisdriver.connect, conf)
+        if ok then
+          logger.info("Reconnect success")
+          self = red
+          break
+        end
+        skynet.sleep(500)
+      end
+    end
+    processed = processed + #ops
+  end
+
+  skynet.fork(function()
+    local co = coroutine.running()
+    while true do
+      if #cache > 0 then
+        local ops = table_remove(cache, 1)
+        pipelining(ops)
+      elseif #page > 0 then
+        local ops = page
+        page = {}
+        pipelining(ops)
+      else
+        wakeup_queue[co] = true
+        skynet_wait()
+        wakeup_queue[co] = nil
+      end
+    end
+  end)
+end
+
+local CMD = {}
+function CMD.start(conf)
+  assert(conf)
+  threading = assert(conf.thread)
+  preview = conf.preview or false
+  assert(threading > 0)
+  for i=1, threading do
+    co_create(conf)
+  end
+
+  local co = coroutine.running()
+  skynet.fork(function()
+    skynet_wakeup(co)
+    local skynet_sleep = skynet.sleep
+    while true do
+      local len = #cache
+      if #page > 0 then
+        len = len + 1
+      end
+      wakeup(len)
+      skynet_sleep(100)
+    end
+  end)
+  skynet_wait()
+  logger.info("%s:%s", conf.host, conf.port)
+end
+
+function CMD.record(key, content)
+  push_back('rpush', key, content)
+  received = received + 1
+  if preview then
+    logger.trace("\n%s", content)
+  end
+end
+
+function CMD.logon()
+  preview = true
+end
+function CMD.logoff()
+  preview = false
+end
+
+skynet.info_func(function()
+  local sleeping = 0
+  for _, _ in pairs(wakeup_queue) do
+    sleeping = sleeping + 1
+  end
+  local working = threading - sleeping
+  local pending = #cache * MAX_PAGE_SIZE + #page
+  return stringify({
+    preview = preview and "true" or "false",
+    processed = processed,
+    received = received,
+    sleeping = sleeping,
+    working = working,
+    pending = pending
+  })
+end)
+
+skynet.memlimit(256 * 1024 * 1024)
+skynet.start(function()
+  logger.label(string.format("<Log slave %d>,", index))
+  skynet.dispatch("lua", function(session, _, cmd, ...)
+    local f = assert(CMD[cmd])
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+  end)
+end)

+ 625 - 0
project/service/mailbox.lua

@@ -0,0 +1,625 @@
+local skynet      = require "skynet"
+local queue       = require "skynet.queue"
+local redisdriver = require "skynet.db.redis"
+local logger   = require "logger"
+local cjson    = require "cjson"
+local keygen   = require "model.keygen"
+local pipeline =  require "pipeline"
+local util = require "util"
+require "skynet.manager"
+local stringify = require "stringify"
+local traceback = debug.traceback
+
+
+local trace          = logger.trace
+local skynet_retpack = skynet.retpack
+local skynet_send    = skynet.send
+local string_format  = string.format
+local table_insert   = table.insert
+local cjson_decode   = cjson.decode   -- 解码
+local cjson_encode   = cjson.encode   -- 编码
+local os_time        = os.time
+local hashcode       = util.hashcode
+local start_sign     = false            -- 服务器启动标记
+local synchronized   = queue()
+
+local retention_tm = 30*24*3600         -- 30天
+local MAX_MAIL = 1000                   -- 最大邮件数量
+local redis_thread = 4                  -- redis 链接的数量
+local redis								-- BUGPK3-20 全局变量修复
+
+cjson.encode_sparse_array(true, 1)
+
+local MAIL_CONTENT = "mailbox:content:%s"   -- 邮件内容     mail_id
+local MAIL_PRIVATE = "mailbox:private:%s"   -- 玩家邮件列表 uid
+
+local MAIL_RUBBISH = "mailbox:rubbish:%s:%s"-- 邮件垃圾篓 uid/时间
+
+local online_user = {}
+local global_mail = {
+    --[[
+        [id] = {
+            date = ,    -- 邮件时间
+            content = , -- 邮件内容
+            portion = 1,    -- 部分人拥有的邮件标记
+        },
+    ]]
+}
+
+local user_mail_num = {
+    --[[
+        [uid] = 0,      -- 邮件数量
+    ]]
+}
+
+local mail_pool_one = {
+    sign = false,
+    name = 'one',
+    mail = {}
+}
+
+local mail_pool_two = {
+    sign = false,
+    name = 'two',
+    mail = {}
+}
+
+-- redis的链接池
+local redis_list = {
+    -- [1] = {batch = pipeline(124), redis = nil}
+}
+
+local CMD = {}
+local mail_pool ={}
+local redis_pool = {}
+
+---------------------------------- local ---------------------------------
+
+local function get_mail_num(uid)
+    local key = string_format(MAIL_CONTENT, uid)
+    local redis = redis_pool.get_redis(redis_pool.hashcode(uid))
+    local num = redis:zcard(key)
+    user_mail_num[uid] = num
+    return num
+end
+
+-- 从内存中取出玩家邮件的数量
+local function get_memory_mail_num(uid)
+    local num = user_mail_num[uid]
+    if num then
+        return num
+    else
+        return get_mail_num(uid)
+    end
+end
+
+-- 修改内存中玩家邮件的数量
+local function set_memory_mail_num(uid, num)
+    if not user_mail_num[uid] then
+        get_mail_num(uid)
+    end
+    user_mail_num[uid] = user_mail_num[uid] + num
+end
+
+-- 服务器启动时,加载邮件
+local function preload_global_mail()
+  	local rets = redis:lrange("mailbox:global", 0, -1)
+    local now_tm = os_time()
+    local num = 0
+  	for _, content in ipairs(rets) do
+    	local obj = cjson_decode(content)
+        -- 时间检查, 检查邮件是否过期
+        if retention_tm and (retention_tm + obj.date >= now_tm) then
+            global_mail[obj.id] = { date=obj.date, content=content, id = obj.id }
+        else
+            num = num + 1
+        end
+  	end
+    -- 维护数据库
+    local batch = pipeline(124)
+    for i = 1, num do
+        batch.add("lpop", "mailbox:global")
+    end
+    batch.execute(redis)
+end
+
+--[[
+    双缓存的邮件池,
+    mail_pool_one = {
+        sign = true,
+        mail = {},
+    }
+    mail_pool_two = {
+        sign = true,
+        mail = {},
+    }
+]]
+---------------------------------------- 邮件池 ---------------------------------
+
+-- 邮件垃圾篓
+local function mail_rubbish(uid, content)
+    local ok, info = xpcall(function()
+        local hash = redis_pool.hashcode(uid)
+        redis_pool.push(hash, "setex", string_format(MAIL_RUBBISH, uid, os.date("%Y-%m-%d:%H-%M-%S",os.time())), retention_tm,content)
+    end, traceback)
+    if not ok then logger.trace(" 邮件垃圾报错 %s", info) end
+end
+
+-- 取出可用的缓存池
+local function get_usable_pool(type)
+    return synchronized(function ( ... )
+        if not mail_pool_one.sign then
+            mail_pool_one.sign = true
+            return mail_pool_one
+        elseif not mail_pool_two.sign then
+            mail_pool_two.sign = true
+            return mail_pool_two
+        else        -- 缓存池 死循环
+            -- 处理缓存死循环
+            logger.trace("----- 在取可用邮件池,两个池都被占用了, type = ", type)
+        end
+    end)
+end
+
+-- 邮件池
+function mail_pool:add_mail_pool(uids, content)     -- 多个id,相同的内容, content 此时为被js编码
+    local mail_pool = get_usable_pool('w')
+    local id
+    if mail_pool then
+        for _, uid in pairs(uids) do
+            id = "M" .. keygen()
+            content.id = id
+            table_insert(mail_pool.mail, {uid = uid, id = id, content = cjson_encode(content)})
+        end
+    else
+        skynet.sleep(100)
+        return mail_pool:add_mail_pool(uids, content)
+    end
+    -- 释放池子
+    mail_pool.sign = false
+end
+
+-- 轮流返回两个邮件的池
+local have_num = 0
+local function get_have_mail_pool()
+    have_num = have_num + 1
+    -- math.maxinteger
+    return synchronized(function ( ... )
+        local ret = math.fmod(have_num, 2)
+        if ret == 0 then
+            if not mail_pool_one.sign then
+                mail_pool_one.sign = true
+                return mail_pool_one, 1
+            end
+        else
+            if not mail_pool_two.sign then
+                mail_pool_two.sign = true
+                return mail_pool_two, 2
+            end
+        end
+    end)
+end
+
+function mail_pool:send_mail_pool()
+    skynet.fork(function()
+        local mail_pool = {}
+        local batch = pipeline(1024)
+        local count = 0
+        local z_count = 0
+        local hash = 0
+        while true do
+            skynet.sleep(100)
+            mail_pool = get_have_mail_pool('r')
+            local sign = false
+            z_count = 0
+            if mail_pool then
+                --  开始发送邮件
+                local kaishi = 0
+                local keys = {}
+                local key
+                -- 读取非在线玩家的邮件数量到缓存中
+                for _, v in pairs(mail_pool.mail) do
+                    if not user_mail_num[v.uid] then
+                        key = string_format(MAIL_CONTENT, v.uid)
+                        table.insert(keys, v.uid)
+                        batch.add("zcard", key)
+                    end
+                end
+                if key then
+                    local resp = batch.execute(redis, {})
+                    for k, uid in pairs(keys) do
+                        user_mail_num[uid] = resp[k].out
+                    end
+                end
+                -- 将邮件写入到玩家数据库中
+                for _, v in pairs(mail_pool.mail) do
+                    sign = true
+                    z_count = z_count + 1
+                    if get_memory_mail_num(v.uid) < MAX_MAIL then
+                       set_memory_mail_num(v.uid, 1)
+                        hash = redis_pool.hashcode(v.uid)
+
+                        -- redis_pool.push(hash, "setex", string_format(MAIL_PRIVATE, v.id), retention_tm,v.content)
+                        redis_pool.push(hash, "set", string_format(MAIL_PRIVATE, v.id), v.content)
+                        redis_pool.push(hash, "zadd", string_format(MAIL_CONTENT, v.uid), os_time(), v.id)
+                        redis_pool.execute(hash)
+                        -- 检查玩家是否已经上线, 在线,通知玩家
+                        if online_user[v.uid] then
+                            pcall(skynet_send, online_user[v.uid].agent, "lua", "newmail", v.content, v.id)
+                        end
+                        count = count + 1
+                        if count >= 200 then
+                            skynet.sleep(100)
+                            count = 0
+                        end
+                    else
+                        mail_rubbish(v.uid, v.content)
+                    end
+                end
+
+                -- 邮件发送结束
+                count = 0
+                mail_pool.mail = {}
+                if sign then
+                    redis_pool.time(z_count)
+                    sign = false
+                end
+                redis_pool.execute()
+            end
+            -- 释放池子
+            mail_pool.sign = false
+        end
+    end)
+end
+
+-- 取得对于的 redis
+function redis_pool.get_redis(hash)
+    return redis_list[hash].redis
+end
+
+function redis_pool.get_batch(hash)
+    return redis_list[hash].batch
+end
+
+function redis_pool.push(hash, ...)
+    redis_list[hash].batch.add(...)
+end
+
+function redis_pool.execute(hash)
+    skynet.fork(function()
+        coroutine.running()
+        local kaishi = util.gettime()
+        if hash then
+            local v = redis_list[hash]
+            v.batch.execute(v.redis)
+            v.time = (v.time or 0) + (util.gettime() - kaishi)
+            v.num = 0
+        else
+            for _, v in pairs(redis_list) do
+                if v.num > 0 then
+                    local kaishi = util.gettime()
+                    v.batch.execute(v.redis)
+                    v.time = (v.time or 0) + (util.gettime() - kaishi)
+                    v.num = 0
+                end
+            end
+            -- redis_pool.time(count)
+        end
+    end)
+end
+
+function redis_pool.time(count)
+    local tm = 0
+    for _, v in pairs(redis_list) do
+        tm = tm + (v.time or 0)
+        v.time = 0
+    end
+    logger.trace("本次发送邮件: %s, 花费的时间 %s", count, tm)
+end
+
+function redis_pool.hashcode(uid)
+    local hash = hashcode(uid) % redis_thread
+    if hash == 0 then
+        hash = hash + 1
+    end
+    redis_list[hash].num = redis_list[hash].num + 1
+    return hash
+end
+
+--------------------------------------------------------------------------------
+
+function CMD.start()
+  	logger.info("start")
+
+  	local conf = assert(option.redis)
+    -- 邮件数据库
+  	redis = redisdriver.connect(conf)
+  	redis:select(13)
+
+    local redis_1
+    local batch
+    for i = 1, redis_thread do
+        redis_1 = redisdriver.connect(conf)
+        redis_1:select(13)
+        batch = pipeline(124)
+        table_insert(redis_list, {redis = redis_1, batch = batch, num = 0})
+    end
+
+  	preload_global_mail()
+    logger.info("%s:%s", conf.host, conf.port)
+    start_sign = true
+end
+
+-- 群发送离线邮件给玩家,如果在线,就通知玩家,
+-- 提示: 必须 send 该模块
+--[[
+    table                   uids: 接收邮件玩家的id
+    number                  subject: 邮件的主题
+    string                  body: 邮件的内容
+    table                   attachment: 邮件的附件,格式按照物品添加格式处理
+    table                   source: 邮件的来源{
+                                        module,         -- 发送模块
+                                        brief,          -- 说明
+                                        context,        -- 上下文
+                                    }
+]]
+function CMD.off_line_mail(uids, subject, body, attachment, source)
+    if type(uids) ~= 'table' then
+        uids = {[1] = uids}
+    end
+    local id
+    local now = os_time()
+    local content = {}
+    local js_content
+    -- 区分玩家是否在线
+    local off_ids = {}
+    local count = 0
+    content = {
+        date = now,
+        subject = subject,
+        body = body,
+        attachment = attachment,
+        source = source,
+    }
+    for _, uid in pairs(uids) do
+        if online_user[uid] then
+            if get_memory_mail_num(uid) < MAX_MAIL then
+                set_memory_mail_num(uid, 1)
+                id = "M" .. keygen()
+                content.id = id
+                js_content = cjson_encode(content)
+
+                local hash = redis_pool.hashcode(uid)
+                -- redis_pool.push(hash, "setex", string_format(MAIL_PRIVATE, id), retention_tm, js_content)
+                redis_pool.push(hash, "set", string_format(MAIL_PRIVATE, id), js_content)
+                redis_pool.push(hash, "zadd", string_format(MAIL_CONTENT, uid), os_time(), id)
+                redis_pool.execute(hash)
+                pcall(skynet_send, online_user[uid].agent, "lua", "newmail", js_content, id)
+                count = count + 1
+                if count >= 100 then
+                    count = 0
+                    skynet.sleep(10)
+                end
+            else
+                mail_rubbish(uid, cjson_encode(content))
+            end
+        else
+            table_insert(off_ids, uid)
+        end
+    end
+    mail_pool:add_mail_pool(off_ids, content)
+end
+
+-- 用于集群邮件发放
+function CMD.master_mail(...)
+    local ok, info = pcall(CMD.off_line_mail, ...)
+    if not ok then
+        logger.trace(" ### 集群邮件发放失败 %s", info)
+    end
+end
+
+-- 广播邮件(全局邮件)
+function CMD.broadcast(subject, body, attachment, source)
+  	--assert(type(content) == "string")
+  	local id = "M" .. keygen()
+  	local now = os_time()
+  	local content = cjson_encode({
+  	  	id = id,
+  	  	date = now,
+  	  	subject = subject,
+  	  	body = body,
+  	  	attachment = attachment,
+        source = source,
+  	})
+  	redis:rpush("mailbox:global",content)
+  	global_mail[id] = { date=now, content=content, id = id}
+    local content = cjson_decode(content)
+  	for uid, user in pairs(online_user) do
+        if get_memory_mail_num(uid) < MAX_MAIL then
+            set_memory_mail_num(uid, 1)
+            id = "M" .. keygen()
+            content.id = id
+            local ok = pcall(skynet_send, user.agent, "lua", "newmail", cjson_encode(content), id)
+            assert(ok)
+            local hash = redis_pool.hashcode(uid)
+            redis_pool.push(hash, "zadd", string_format(MAIL_CONTENT, uid), os_time(), id)
+            -- redis_pool.push(hash, "setex", string_format(MAIL_PRIVATE, id), retention_tm, content)
+            redis_pool.push(hash, "set", string_format(MAIL_PRIVATE, id), cjson_encode(content))
+        else
+            mail_rubbish(uid, cjson_encode(content))
+        end
+  	end
+    redis_pool.execute()
+end
+
+-- 保存一封邮件, 邮件的内容已经是被 js 处理过的
+function CMD.save_mail(id, content, uid, flag)
+    if flag or get_memory_mail_num(uid) < MAX_MAIL then
+        set_memory_mail_num(uid, 1)
+        -- 添加邮件, 到私人邮件公用信箱
+        local hash = redis_pool.hashcode(uid)
+        -- redis_pool.push(hash, "setex", string_format(MAIL_PRIVATE, id), retention_tm, content)
+        redis_pool.push(hash, "set", string_format(MAIL_PRIVATE, id), content)
+        -- 维护邮件的id
+        redis_pool.push(hash, "zadd", string_format(MAIL_CONTENT, uid), os_time(), id)
+        redis_pool.execute(hash)
+    else
+        mail_rubbish(uid, content)
+    end
+end
+
+-- 取出玩家邮件
+local function get_mail(uid, ids)
+    local temp = {}
+    local temp_ids = {}
+    for _, id in pairs(ids) do
+        table_insert(temp_ids, string_format(MAIL_PRIVATE, id))
+    end
+    local redis = redis_pool.get_redis(redis_pool.hashcode(uid))
+    if #temp_ids > 0 then
+        temp = redis:mget(table.unpack(temp_ids)) or {}
+    end
+    return 0, temp
+end
+
+-- 取出玩家邮件
+function CMD.get_all_mail(tm, uid)
+    local key = string_format(MAIL_CONTENT, uid)
+    local ids = {}
+    -- ids = redis:lrange(key, 0, -1)
+    local redis = redis_pool.get_redis(redis_pool.hashcode(uid))
+    local del_ids = redis:ZRANGEBYSCORE (key, 0, os.time() - retention_tm)
+    if next(del_ids or {}) then
+        CMD.del_mail(uid, del_ids)
+    end
+    ids = redis:zrevrange(key, 0, -1)
+    return get_mail(uid, ids)
+end
+
+-- 玩家上线的时候,检测全局邮件
+local function check_mail(uid, tm)
+    local mail_num = get_memory_mail_num(uid)
+    local temp_num = 0
+    temp_num = MAX_MAIL - mail_num
+    local hash = redis_pool.hashcode(uid)
+    local m_u  = string_format(MAIL_CONTENT, uid)
+    for _, v in pairs(global_mail) do
+        if v.date > tm then
+            if temp_num <= 0 then
+                break
+            end
+            -- redis_pool.push(hash, "zadd", string_format(MAIL_CONTENT, uid), os_time(), v.id)
+            -- redis_pool.push(hash, "setex", string_format(MAIL_PRIVATE, v.id), retention_tm, v.content)
+            -- 解码
+            local content = cjson_decode(v.content)
+            local id = "M" .. keygen()
+            -- 更换id
+            content.id = id
+            redis_pool.push(hash, "set", string_format(MAIL_PRIVATE, id), cjson_encode(content))
+            -- 维护邮件的id
+            redis_pool.push(hash, "zadd", m_u, tm, id)
+            set_memory_mail_num(uid, 1)
+            temp_num = temp_num - 1
+        end
+    end
+    redis_pool.execute(hash)
+    return
+end
+
+-- 玩家上线
+function CMD.online(agent, uid, tm)
+    online_user[uid] = {
+        agent = agent,
+    }
+    local key = string_format(MAIL_CONTENT, uid)
+    local del_ids = redis:ZRANGEBYSCORE (key, 0, os.time() - retention_tm)
+    if next(del_ids or {}) then
+        CMD.del_mail(uid, del_ids)
+    end
+    -- 收取玩家的全局邮件
+    check_mail(uid, tm)
+end
+
+-- 玩家下线
+function CMD.offline(agent, uid)
+    -- 删除服务器邮件数据
+    if online_user[uid].agent == agent then
+        online_user[uid] = nil
+        user_mail_num[uid] = nil
+    end
+end
+
+
+function CMD.mail_num(uid)
+    return get_memory_mail_num(uid)
+end
+
+function CMD.surplus_mail_num(uid)
+    return MAX_MAIL - get_memory_mail_num(uid)
+end
+
+-- 删除邮件
+function CMD.del_mail(uid, list)
+    local mail_s = string_format(MAIL_CONTENT, uid)
+    local mail_s_s
+    local del_num = 0
+    local hash = redis_pool.hashcode(uid)
+    for _, v in pairs(list) do
+        mail_s_s = string_format(MAIL_PRIVATE, v)
+        redis_pool.push(hash, "zrem", mail_s, v)
+        redis_pool.push(hash, "del", mail_s_s)
+        del_num = del_num - 1
+    end
+    set_memory_mail_num(uid, del_num)
+    redis_pool.execute(hash)
+    return 0
+end
+
+-- 提取邮件附件
+function CMD.get_goods(uid, ids)
+    local err, list = get_mail(uid, ids)
+    return err, list
+end
+
+local function update_global_mail()
+    local now_tm = os_time()
+    local temp = {}
+    local batch = pipeline(124)
+    for _, v in pairs(global_mail) do
+        -- 时间检查, 检查邮件是否过期
+        if retention_tm and (retention_tm + v.date < now_tm) then
+            batch.add("lpop", "mailbox:global")
+        else
+            temp[v.id] = v
+        end
+  	end
+    batch.execute(redis)
+    global_mail = temp
+end
+
+skynet.init(function()
+    skynet.register(".mailbox")
+    cjson.encode_sparse_array(true, 1)
+    mail_pool:send_mail_pool()
+    skynet.fork(function()
+        while true do
+            skynet.sleep(300)
+            if start_sign then
+                update_global_mail()
+            end
+        end
+    end)
+
+end)
+
+skynet.start(function()
+  logger.label("<Mailbox>")
+  skynet.dispatch("lua", function(session, _, cmd, ...)
+    local f = assert(CMD[cmd])
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+  end)
+end)

+ 81 - 0
project/service/main.lua

@@ -0,0 +1,81 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local cluster = require "skynet.cluster"
+local logger = require "logger"
+
+skynet.start(function()
+  logger.info("game start")
+
+  local daemon = skynet.getenv("daemon")
+  if not daemon then
+    skynet.newservice("console")
+  end
+  skynet.newservice("debug_console",
+                    "127.0.0.1",
+                    assert(option.console_port))
+--   skynet.newservice("protoloader")
+  local snowflake = skynet.newservice("snowflake")
+  skynet.call(snowflake, "lua", "start")
+  skynet.newservice("webserver")
+--   skynet.newservice("https_client") -- 新版本skynet 代https访问
+
+  if option.cluster then
+    cluster.reload(option.cluster)
+    if option.cluster.address then
+      cluster.open("address")
+    end
+    if option.cluster.log then
+      cluster.proxy("log", "logserver")
+    end
+  end
+
+  local loginserver = skynet.newservice("loginserver")
+  local log = skynet.newservice("log")
+  local mq = skynet.newservice("mq")
+  local assetcenter = skynet.newservice("assetcenter")
+  local namecenter = skynet.newservice("namecenter")
+  local usercenter = skynet.newservice("usercenter")
+  local mailbox = skynet.newservice("mailbox")
+  local recharge = skynet.newservice("recharge")
+  local ws_watchdog = skynet.newservice("ws_watchdog")
+--   local quest = skynet.newservice("quest")
+--   local gift = skynet.newservice("gift")
+--   local rankinglist = skynet.newservice("rankinglist")
+--   local act_rankinglist = skynet.newservice("act_rankinglist")
+--   local relic_manual = skynet.newservice("relic_manual")
+--   local activitytime = skynet.newservice("activitytime")
+--   local itemavg = skynet.newservice("itemavg")
+--   local statistics = skynet.newservice("statistics")
+
+    --   local watchdog = skynet.newservice("watchdog")
+  skynet.call(loginserver, "lua", "start")
+  skynet.call(log, "lua", "start")
+  skynet.call(mq, "lua", "start")
+  skynet.call(assetcenter, "lua", "start")
+  skynet.call(namecenter, "lua", "start")
+  skynet.call(usercenter, "lua", "start")
+  skynet.call(mailbox, "lua", "start")
+  skynet.call(recharge, "lua", "start")
+--   skynet.call(quest, "lua", "start")
+--   skynet.call(gift, "lua", "start")
+--   skynet.call(rankinglist, "lua", "start")
+--   skynet.call(act_rankinglist, "lua", "start")
+--   skynet.call(activitytime, "lua", "start")
+--   skynet.call(itemavg, "lua", "start")
+--   skynet.call(statistics, "lua", "start")
+--   skynet.call(relic_manual, "lua", "start")
+  --   skynet.call(watchdog, "lua", "start", {
+--     port = option.game_port,
+--     maxclient = option.max_client,
+--     nodelay = true,
+--   })
+  skynet.call(ws_watchdog, "lua", "start", {
+    port = option.game_port,
+    maxclient = option.max_client,
+    nodelay = true,
+    protocol = "ws",
+  })
+  skynet.exit()
+end)
+
+

+ 103 - 0
project/service/mq.lua

@@ -0,0 +1,103 @@
+-- 消息队列服务
+local skynet = require "skynet"
+require "skynet.manager"
+-- require "skynet.queue"
+local redisdriver = require "skynet.db.redis"
+local logger = require "logger"
+local cjson = require "cjson"
+-- local util = require "util"
+
+local skynet_call = skynet.call
+local skynet_send = skynet.send
+local string_format = string.format
+local table_unpack = table.unpack
+local cjson_encode = cjson.encode
+local cjson_decode = cjson.decode
+local logger_trace = logger.trace
+local logger_warn = logger.warn
+-- local hashcode = util.hashcode
+local forwarding = function(agent, ...)
+  return pcall(skynet_call, agent, "lua", "publish", ...)
+end
+local keygen = function(k)
+  return string_format("mq:%s", k)
+end
+
+local redis
+local lpop = function(k) return redis:lpop(k) end
+local lpush = function(k, v) return redis:lpush(k, v) end
+local rpush = function(k, v) return redis:rpush(k, v) end
+local dispatch_message = function(agent, uid)
+  local key = keygen(uid)
+  while true do
+    local val = lpop(key)
+    if val == nil then
+      break
+    end
+
+    if forwarding(agent, table_unpack(cjson_decode(val))) then
+      logger_trace("玩家 %s 处理了离线消息 %s", uid, val)
+    else
+      lpush(key, val)
+      logger_warn("玩家 %s 处理离线消息 %s 失败", uid, val)
+      break
+    end
+  end
+end
+
+local online_user = {}
+local CMD = {}
+function CMD.subscribe(agent, uid)
+  dispatch_message(agent, uid)
+  online_user[uid] = agent
+end
+
+function CMD.unsubscribe(agent, uid)
+  if online_user[uid] == agent then
+    online_user[uid] = nil
+  end
+end
+
+function CMD.publish(_, uid, ...)
+  local agent = online_user[uid]
+  if agent then
+    if forwarding(agent, ...) then
+      return
+    end
+  end
+
+  local key = keygen(uid)
+  local value = cjson_encode({...})
+  rpush(key, value)
+  logger_trace("玩家 %s 的离线消息 %s 被存盘", uid, value)
+
+  agent = online_user[uid]
+  if agent then
+    dispatch_message(agent, uid)
+  end
+end
+
+function CMD.start()
+  local conf = assert(option.redis)
+  redis = redisdriver.connect(conf)
+  redis:select(6)
+
+  logger.info("%s:%s", conf.host, conf.port)
+end
+
+skynet.init(function()
+  cjson.encode_sparse_array(true, 1)
+  skynet.register(".mq")
+end)
+
+skynet.start(function()
+  logger.label("<MQ>,")
+  skynet.dispatch("lua", function(session, source, cmd, ...)
+    local f = assert(CMD[cmd])
+    if 0==session then
+      f(source, ...)
+    else
+      skynet.retpack(f(source, ...))      
+    end
+  end)
+end)

+ 177 - 0
project/service/namecenter.lua

@@ -0,0 +1,177 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local redisdriver = require "skynet.db.redis"
+local pipeline = require "pipeline"
+local logger = require "logger"
+local asset = require "model.asset"
+local queue  = require "skynet.queue"
+local redis
+local NAMELIST = "namelist"
+
+local stringify = require "stringify"
+
+local DEFAULT_NAME = "role"
+
+local skynet_retpack = skynet.retpack
+local synchronized = queue()
+
+local population = 0          -- 服务器人口数量
+
+local nickname = {}
+local duplicate_name = {}
+local function load_registered_name()
+    local conf = assert(option.redis)
+    local character_redis = redisdriver.connect(conf)
+    character_redis:select(0)
+
+    -- 载入角色名字和帐号
+    local block = pipeline(1024)
+    local keys = character_redis:keys("character:*")
+    if #keys > 0 then
+        for _, k in ipairs(keys) do
+            block.add("hmget", k, "nickname", "uid")
+        end
+        population = population + #keys 
+    end
+
+    local resp = block.execute(character_redis, {})
+    nickname[DEFAULT_NAME] = ""
+    for i, v in ipairs(resp) do
+        assert(v.ok)
+        local k = v.out[1]
+        if k and k ~= DEFAULT_NAME then
+            nickname[k] = v.out[2]
+        end
+    end
+    character_redis:disconnect()
+end
+
+local function init()
+    local conf = assert(option.redis)
+    redis = redisdriver.connect(conf)
+    redis:select(14)
+    local retnuma = redis:hgetall(NAMELIST)
+    for k = 1, #retnuma, 2 do
+        duplicate_name[retnuma[k]] = tonumber(retnuma[k+1])
+    end
+end
+
+local function get_name(name)
+    local realname = name
+    for i = 1, 100 do
+        if nickname[realname] then
+            duplicate_name[name] = (duplicate_name[name] or 0) + 1
+            realname = name .. "#" .. (duplicate_name[name]+100)
+            break
+        end    
+    end
+
+    if duplicate_name[name] > 0 then
+        redis:hset(NAMELIST, name, duplicate_name[name])
+    end
+
+    if not nickname[realname] then
+        return realname
+    end
+end
+
+local CMD = {}
+function CMD.start()
+    init()
+    load_registered_name()
+end
+
+function CMD.register_nickname(name, uid)
+    local name = name 
+    if not name or name == "" then
+        name = DEFAULT_NAME
+    end
+
+    if nickname[name] then 
+        name = get_name(name) 
+    end
+
+    if not name then
+        return 
+    end
+    nickname[name] = uid
+    population = population + 1
+    logger.trace(" #### 服务器人口数量增加 %s",population)
+    return true, name
+end
+
+--@TODO 服务器人口数量
+function CMD.get_population()
+  return population
+end
+
+-- 通过名字查找玩家的基础信息
+--   uid:    角色id
+--   account: 帐号id
+function CMD.findby(name)
+    if name == DEFAULT_NAME then
+        return 
+    end
+    local uid = nickname[name]
+    if uid and type(uid) == "string" then
+        return uid
+    end
+end
+
+-- 玩家改名
+-- old_name:以前的名
+-- new_name: 新的名字
+-- uid :玩家的uid
+function CMD.rename(old_name, new_name, uid)
+    return synchronized(function ()
+        if new_name == DEFAULT_NAME then
+            return STD_ERR.PLYAER_DUPLICATION_NAME    -- 名字已经被使用
+        end
+      -- 没有找到改玩家
+        if old_name ~= DEFAULT_NAME then
+            if not nickname[tostring(old_name)] or nickname[tostring(old_name)] ~= uid then
+                return STD_ERR.PLYAER_NO_PLAYER           -- 不存在的玩家
+            end
+        end
+        -- 名字已经被使用
+        if nickname[new_name] then
+            return STD_ERR.PLYAER_DUPLICATION_NAME    -- 名字已经被使用
+        end
+        nickname[new_name] = uid
+        return 0
+    end)
+end
+
+-- 撤销改名
+function CMD.revocation_rename(new_name, uid)
+    return synchronized(function ()
+    --   nickname[old_name] = uid
+      nickname[new_name] = nil
+      return 0
+    end)
+end
+
+-- 取消对name的锁定
+function CMD.unlock_name(name)
+    return synchronized(function ()
+      nickname[name] = nil
+      return 0
+    end)
+end
+
+skynet.init(function()
+  skynet.register(".namecenter")
+end)
+
+skynet.start(function()
+  logger.label("<Namecenter>")
+  skynet.dispatch("lua", function(session, _, cmd, ...)
+    local f = assert(CMD[cmd])
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+  end)
+end)
+

+ 106 - 0
project/service/pbuf.lua

@@ -0,0 +1,106 @@
+-- local protobuf = require "protobuf" --引入文件protobuf.lua
+-- --注册protobuffer文件
+-- protobuf.register_file(protofile)
+-- ​
+-- --根据注册的protofile中的类定义进行序列化,返回得到一个stringbuffer
+-- protobuf.encode("package.message", { ... })
+-- ​
+-- --根据注册的protofile中的类定义进行反序列化
+-- protobuf.decode("package.message", stringbuffer)
+
+local skynet = require "skynet"
+local protobuf = require "protobuf" --引入文件protobuf.lua
+local stringify = require "stringify"
+
+protobuf.register_file "./proto/game.pb"
+local packname = "game." -- 包名
+
+local Sproto = {}
+local tmap = {}
+
+Sproto.enum_id = function (name)
+    local ret = protobuf.enum_id("game.msg_cmd", "cmd_"..name)
+    if not ret then
+        assert(false, "not enum:cmd_"..name)
+    end
+    return ret
+end
+
+Sproto.decode = function(msg)
+    local e_id = string.unpack("I4", msg)
+    if not e_id then
+        skynet.error("Sproto.decode not e_id")
+        return 
+    end
+    local name = tmap[e_id]
+    if not name then
+        skynet.error("Sproto.decode not name, e_id:"..e_id)
+        return nil, nil 
+    end
+    local str = string.sub(msg, 5, #msg)
+    local ret = protobuf.decode(packname..name, str)
+    local function fun(args)
+        return Sproto.encode(name.."_rsp", args)
+    end
+    return "REQUEST", name, ret, fun
+end
+
+Sproto.encode = function (name, args, ...)
+    local e_id = Sproto.enum_id(name)
+    local ret = protobuf.encode(packname..name, args)
+    if ret and e_id then
+        return string.pack("I4", e_id)..ret
+    end
+    return ret
+end
+
+Sproto.register_msg = function(name)
+    local e_id = Sproto.enum_id(name)
+    if e_id then
+        tmap[e_id] = name
+    end
+end
+
+
+-- skynet.start(function()
+--     skynet.error("protobuf game.pb")
+
+--     --编码
+--     local stringbuffer = protobuf.encode("game.login", --对应person.proto协议的包名与类名
+--     {
+--         sid = 1,
+--         account = 3,
+--     })
+--     stringbuffer = string.pack("I4", 65536)..stringbuffer
+--     local msg = string.unpack("I4", stringbuffer)
+
+--     local str = string.sub(stringbuffer, 5, #stringbuffer)
+--     -- 解码
+--     local data = protobuf.decode("game.login",str)
+--     for k, v in pairs(data or {}) do
+--         skynet.error(k, v)
+--     end
+-- end)
+
+skynet.start(function()
+    skynet.error("protobuf game.pb")
+    Sproto.register_msg("login")
+    Sproto.register_msg("login_rsp")
+    --编码
+    local code = Sproto.encode("login", {
+        sid = 1,
+        account = 3,
+    })
+
+    local req, name , args, func= Sproto.decode(code)
+    skynet.error(req, name)
+    skynet.error(stringify(args or {}))
+    local code2 = func({
+        errno = 3;
+    })
+
+    req, name , args, func= Sproto.decode(code2)
+    skynet.error(req, name)
+    skynet.error(stringify(args or {}))
+end)
+

+ 10 - 0
project/service/protoloader.lua

@@ -0,0 +1,10 @@
+local skynet = require "skynet"
+local sprotoparser = require "sprotoparser"
+local sprotoloader = require "sprotoloader"
+local proto = require "proto"
+
+skynet.start(function()
+	sprotoloader.save(proto.c2s, 1)
+	sprotoloader.save(proto.s2c, 2)
+	-- don't call skynet.exit() , because sproto.core may unload and the global slot become invalid
+end)

+ 296 - 0
project/service/quest.lua

@@ -0,0 +1,296 @@
+-- 任务
+local skynet = require "skynet"
+require "skynet.manager"
+local socket = require "skynet.socket"
+local logger = require "logger"
+local asset = require "model.asset"
+local util = require "util"
+local stringify = require "stringify"
+
+local skynet_retpack = skynet.retpack
+
+-- 日常任务
+local daily = {
+    --[[
+        [等级] = {{
+                    id = id, 
+                    type = type,
+                    required = required
+                    required2 = required2
+                }}
+    ]]
+}
+
+-- 周常任务
+local weekly = {
+    --[[
+        [等级] = {{
+                    id = id, 
+                    type = type,
+                    required = required
+                    required2 = required2
+                }}
+    ]]
+}
+
+-- 成就任务
+local achi = {
+    --[[
+        [类型] = {{
+                    id = id, 
+                    type = type,
+                    required = required
+                    required2 = required2
+                }}
+    ]]
+}
+
+-- 主线任务
+local main_quest = {
+    first = nil,
+    list = {
+        --[[
+            [id] = {
+                next = next_index
+            }
+        ]]
+    }
+
+}
+
+local function new_quest(desc, reset)
+    local ret =  {
+        id = desc.ID,
+        type = desc.touch,              -- 触发器
+        model = desc.type,
+        required = desc.parameter,      -- 数值要求
+        required2 = desc.data,    -- 品质要求
+        reset = reset or desc.timely == 1,
+    }
+
+    -- 特殊原因需要将21触发器多条件任务的主条件和副条件对调.
+    if ret.type == 22 then
+        ret.required, ret.required2 = ret.required2, ret.required
+    end
+
+    return ret 
+end
+
+
+local function register_level_quest(t, conf)
+    local quest = new_quest(conf, true)
+    if conf.interval[2] then
+        for i = conf.interval[1], conf.interval[2] do
+            t[i] = t[i] or {}
+            t[i][conf.ID] = quest
+        end
+    end
+end
+
+local function register_type_quest(t, conf)
+    local quest = new_quest(conf)
+    t[conf.touch] =  t[conf.touch] or {}
+    table.insert(t[conf.touch], quest)
+end
+
+local function register_main_quest(t, conf)
+    local quest = new_quest(conf)
+    t.list = t.list or {}
+    t.index = t.index or {}
+    assert(not t.list[quest.id], quest.id)
+    t.list[quest.id] = quest
+    table.insert(t.index, quest.id)
+end
+
+local function register_quest(list, config)
+    for _, desc in pairs(config) do
+        if desc.type == DAILY_QUEST then
+            register_level_quest(list.daily, desc)
+        elseif desc.type == WEEKLY_QUEST then
+            register_level_quest(list.weekly, desc)
+        elseif desc.type == ACHI_QUEST then
+            register_type_quest(list.achi, desc)
+        elseif desc.type == MAIN_QUEST then
+            register_main_quest(list.main, desc)
+        end
+    end
+end
+
+
+local function optimize()
+    -- 首先,构建关卡的任务列表
+    local list = {
+        daily = {},
+        weekly = {},
+        achi = {},
+        main = {},
+    }
+    register_quest(list, asset.TaskConfig_proto)
+    for _, v in pairs(list.achi or {}) do
+        table.sort(v, function(a, b)
+            if a.required < b.required then
+                return true
+            end
+            if a.required > b.required then
+                return false
+            end
+            if a.required2 and b.required2 then
+                if a.required2 < b.required2 then
+                    return true
+                end
+            end
+            return false
+        end)
+    end
+    if list.main.index then
+        table.sort(list.main.index)
+        list.main.first = list.main.index[1]
+        for i = 1, #list.main.index do
+            local cur_index = list.main.index[i]
+            local nex_index = list.main.index[i+1]
+            if nex_index then
+                list.main.list[cur_index].next = nex_index
+            end
+        end
+    end
+
+    daily = list.daily
+    weekly = list.weekly
+    achi = list.achi
+    if list.main then
+        main_quest.first = list.main.first
+        main_quest.list = list.main.list
+    end
+    logger.trace("main_quest:"..stringify(main_quest or {}))
+end
+
+local CMD = {}
+function CMD.start()
+  optimize()
+end
+
+function CMD.daily_qlst(lv)
+    return daily[lv] or {}
+end
+
+function CMD.weekly_qlst(lv)
+    return weekly[lv] or {}
+end
+
+function CMD.achi_qlst(list)
+    local ret = {}
+    local temp ={}
+    for _, id in ipairs(list or {}) do
+        local conf = asset.TaskConfig_proto[id]
+        if conf and conf.touch and conf.type == ACHI_QUEST then
+            temp[conf.touch] = id
+        end
+    end
+
+    for type, v in pairs(achi) do
+        local find = false
+        if temp[type] then
+            for _, quest in ipairs(v) do
+                if quest.id == temp[type] then
+                    ret[quest.id] = quest
+                    find = true
+                    break
+                end
+            end
+        end
+        if not find then
+            local quest = v[1]
+            if quest then
+                ret[quest.id] = quest
+            end
+        end
+    end
+    return ret
+end
+
+function CMD.newx_achi(id)
+    local conf = asset.TaskConfig_proto[id]
+    local list = achi[conf.touch]
+    if not list then
+        return
+    end
+
+    for k, quest in ipairs(list) do
+        if quest.id == id then
+            return list[k+1]
+        end
+    end
+end
+
+function CMD.quest(id)
+    local conf = asset.TaskConfig_proto[id]
+    if conf then
+        return new_quest(conf)
+    end
+    return nil
+end
+
+function CMD.get_main(id)
+    if not id or not main_quest.list[id] then
+        return CMD.next_main(id)
+    else
+        return main_quest.list[id]
+    end
+end
+
+function CMD.next_main(id)
+    if not main_quest.first then
+        return 
+    end
+    if not id then
+        return main_quest.list[main_quest.first]
+    else
+        if main_quest.list[id] then
+            if main_quest.list[id].next then
+                return main_quest.list[main_quest.list[id].next]
+            end
+            return
+        else
+            local index = main_quest.first
+            while(main_quest.list[index]) do
+                if index > id then
+                    return main_quest.list[index]
+                else
+                    local next_index = main_quest.list[index].next
+                    if next_index and next_index ~=  index then
+                        index = next_index
+                    else
+                        return  
+                    end
+                end
+            end 
+        end
+    end
+end
+
+function CMD.hotfix(list)
+    if list.TaskConfig_proto then
+        logger.info("===============  quest hotfix ===================")
+        local ok, msg = pcall(optimize)
+        if not ok then
+            logger.warn("===============  quest hotfix ERR===================\n, msg:"..msg)
+        end
+    end
+end
+
+
+skynet.init(function()
+  skynet.register(".quest")
+end)
+
+skynet.start(function()
+  logger.label("<Quest>,")
+	skynet.dispatch("lua", function(session,_, cmd, ...)
+    local f = assert(CMD[cmd])
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+	end)
+end)

+ 188 - 0
project/service/rankinglist.lua

@@ -0,0 +1,188 @@
+--排行榜服务
+local skynet = require "skynet"
+require "skynet.manager"
+local util = require "util"
+local logger = require "logger"
+local common_fun = require "model.common_fun"
+local stringify = require "stringify"
+local pipeline = require "pipeline"
+local redisdriver = require "skynet.db.redis"
+local redis
+local KEY_STR = "ranking:%d:list"
+
+local cjson     = require "cjson"
+local cjson_decode   = cjson.decode   -- 解码
+local cjson_encode   = cjson.encode   -- 编码
+
+local MAX_RANK = 300
+
+local compare = function(a, b)
+    if a.num > b.num then
+        return true
+    end
+    if a.num == b.num and a.num2 and b.num2 then
+        return a.num2 > b.num2
+    end
+    return false
+end
+
+local function inserting(t, n, f)
+    assert(type(t) == "table")
+    local f = f or compare
+	local size = #t
+	local n = n or size + 1
+	for i = 2, size do
+		local num = math.min(i, n)
+		if i > n and f(t[i], t[num]) then
+			t[i], t[num] = t[num], t[i]
+		end
+		local temp = t[num]
+		local j = num -1 
+		while j >= 1 and f(temp, t[j]) do
+			t[j+1] = t[j]
+			j = j - 1
+		end
+		t[j+1] = temp
+	end
+	-- if n < size then
+	-- 	t[n+1] = nil
+	-- end
+    for i = n + 1, size do
+        t[i] = nil
+    end
+	return t
+end
+
+local CMD = {}
+local ranklist = {
+
+}
+
+local showlist = {
+
+}
+
+local function update()
+    for rank_type, list in pairs(ranklist) do
+        inserting(list, MAX_RANK)
+        showlist[rank_type] = {}
+        local key = string.format(KEY_STR, rank_type)
+        redis:del(key)
+        local batch = pipeline(100)
+        for rank, data in ipairs(list) do
+            batch.add("hset", key, data.role.uid, cjson_encode(data))
+            showlist[rank_type][rank] = common_fun.scopy(data)
+        end
+        batch.execute(redis,{})
+    end
+end
+
+local function init()
+    local conf = assert(option.redis)
+    redis = redisdriver.connect(conf)
+    redis:select(12)
+    for rank_type= RANKING_ADV, RANKING_POWER do
+        ranklist[rank_type] = {}
+        local key = string.format(KEY_STR, rank_type)
+        local retnuma = redis:hgetall(key)
+        for k = 1, #retnuma, 2 do
+            local data = cjson_decode(retnuma[k+1])
+            ranklist[rank_type][#ranklist[rank_type]+1] = data
+        end
+    end
+end
+
+local function watchtime()
+    skynet.fork(function()
+        local interval = 60*2 -- 更新间隔为2分钟
+        local up_time = os.time()
+        while(true) do
+            local now = os.time()
+            if now > up_time then
+                up_time = now + interval
+                update()
+            end
+            skynet.sleep(300)
+        end
+    end)
+end
+
+local function find_uid(list, uid)
+    for k, info in ipairs(list or {})do
+        if info.role.uid == uid then
+            return k, info
+        end
+    end
+end
+
+function CMD.player_rename(uid, name)
+    for _, list in pairs(ranklist) do
+        local _, info = find_uid(list, uid)
+        if info then
+            info.role.nickname = name
+        end
+    end
+end
+
+function CMD.update(rank_type, data)
+    local key = string.format(KEY_STR, rank_type)
+    ranklist[rank_type] = ranklist[rank_type] or {}
+    local list = ranklist[rank_type]
+
+    local k = find_uid(list, data.role.uid)
+    if k then
+        list[k] = data
+        redis:hset(key, data.role.uid, cjson_encode(data))
+        return
+    end
+
+    local show = showlist[rank_type] or {}
+    if show[MAX_RANK] and show[MAX_RANK].num > data.num then
+        return
+    end
+    list[#list+1] = data
+    redis:hset(key, data.role.uid, cjson_encode(data))
+end
+
+function CMD.get_ranking(rank_type)
+    return showlist[rank_type] or {}
+end
+
+-- 从排行榜中删除玩家
+function CMD.del(uid)
+    for _, list in pairs(showlist) do
+        local k = find_uid(list, uid)
+        if k then
+            table.remove(list, k)
+        end
+    end
+
+    for rank_type, list in pairs(ranklist) do
+        local key = string.format(KEY_STR, rank_type)
+        local k = find_uid(list, uid)
+        if k then
+            table.remove(list, k)
+            redis:hdel(key, uid)
+        end
+    end
+end
+
+function CMD.start()
+    init()
+    watchtime()
+end
+
+skynet.init(function()
+  skynet.register(".rankinglist")
+end)
+
+skynet.start(function()
+  skynet.dispatch("lua", function(session, _, cmd, ...)
+    local f = assert(CMD[cmd])
+    if 0==session then
+      f(...)
+    else
+      skynet.retpack(f(...))
+    end
+  end)
+end)

+ 162 - 0
project/service/recharge.lua

@@ -0,0 +1,162 @@
+--充值服务
+local skynet = require "skynet"
+require "skynet.manager"
+local redisdriver = require "skynet.db.redis"
+local logger = require "logger"
+local queue = require "skynet.queue"
+local asset = require "model.asset"
+local pipeline = require "pipeline"
+
+local synchronized = queue()
+local skynet_retpack = skynet.retpack
+local trace = logger.trace
+local recharge = {}
+local CMD = {}
+local redis
+
+local INDENT = "indent:"
+local KEYGEN_PAY = "%s:%s"
+local mq
+local REBATE = "rebate:"
+
+function recharge.refresh_data(uid,orderid)
+  local key = INDENT..string.format(KEYGEN_PAY,uid,orderid)
+  local flag = redis:hget(key,"flag")
+  if not flag then
+    key = REBATE..string.format(KEYGEN_PAY,uid,orderid)
+    flag = redis:hget(key,"flag")
+  end
+
+  if flag then
+    flag = tonumber(flag)
+    trace("充值修改flag:%s",flag)
+    if flag==1 then
+      local now = os.time()
+      redis:hmset(key,
+          "overTime",now,
+          "flag",0
+        )
+    end
+  else
+    trace("充值修改flag失败没有orderid:%s",orderid)
+  end
+end
+
+function recharge.record_data(uid, cfid, orderid, appname, giftid)
+    local key = INDENT..string.format(KEYGEN_PAY, uid, orderid)
+    local now = os.time()
+    redis:hmset(key,
+        "uid",uid,
+        "cfid",cfid,
+        "orderid",orderid,
+        "writeTime",now,
+        "flag",1,         --1未完成,0完成
+        "appname", appname,
+        "giftid", giftid
+    )
+    --test recharge
+    local moneycfg = assert(asset.RechargeConfig_proto[cfid],cfid)
+    local moneytype = asset.DataConfig_proto[19].data1
+    local moneynum
+    if moneytype == 1 then
+        moneynum = moneycfg.my
+    elseif moneytype == 2 then
+        moneynum = moneycfg.rmb
+    elseif moneytype == 3 then
+        moneynum = moneycfg.tw
+    end
+    skynet.send(mq, "lua", "publish", uid, "recharge", cfid, orderid, moneynum, appname, giftid)  -- 派发事件
+end
+
+function CMD.orderid_refresh(uid,orderid)
+  synchronized(function()
+    recharge.refresh_data(uid,orderid)--在玩家发货成功
+  end)
+end
+
+function CMD.orderid_record(uid, cfid, orderid, appname, giftid)
+    synchronized(function()
+        recharge.record_data(uid, cfid, orderid, appname, giftid)--写入玩家成功支付的订单
+    end)
+end
+
+--------------------------sy185的返利接口------------------------
+function recharge.rebate_data(uid, money, orderid, vip, gift)--写入玩家返利的订单
+  local key = REBATE..string.format(KEYGEN_PAY,uid,orderid)
+  local now = os.time()
+  redis:hmset(key,
+    "uid",uid,
+    "money",money,
+    "orderid",orderid,
+    "writeTime",now,
+    "flag",1,         --1未完成,0完成
+    "vip",vip,        --是否加vip经验,0-不加1-加
+    "gift",gift       --是否amount>=5000时,额外赠送50%,0-不赠送1-赠送(满50 元额外赠送50%)
+  )
+  skynet.send(mq, "lua", "publish", uid, "recharge.rebate_money", money, orderid, vip, gift)  -- 派发事件
+end
+
+function CMD.orderid_rebate(uid, cfid, orderid,vip,gift)
+  synchronized(function()
+    recharge.rebate_data(uid, cfid, orderid,vip,gift)--写入玩家返利的订单
+  end)
+end
+--------------------------------------------------------------------
+
+--获取玩家充值数量
+local all_recharge = {}
+local stringify = require "stringify"
+function recharge.get_all_recharge()
+  local keys = redis:keys("indent:*")
+  local block = pipeline(1024)
+  if next(keys) then
+    for _, k in pairs(keys) do
+      block.add("hmget", k, "uid","cfid")
+    end
+  end
+  local resp = block.execute(redis, {})
+  local num = 0
+
+  for i, v in ipairs(resp) do
+    assert(v.ok)
+    local rs = v.out
+    if rs[1] then
+      local uid = rs[1]
+      local cfid = rs[2]
+      all_recharge[uid] = all_recharge[uid] or {}
+      table.insert(all_recharge[uid],{cfid = cfid,uid = uid})
+      num = num + 1
+    end
+  end
+  -- trace("___________________________________获取玩家充值数量num:%s,all_recharge:%s",num,stringify(all_recharge))
+end
+
+function CMD.get_uid_recharge(uid)
+  if all_recharge and all_recharge[uid] then
+    return all_recharge[uid]
+  end
+end
+
+function CMD.start()
+  local conf = assert(option.redis)
+  redis = redisdriver.connect(conf)
+  redis:select(3)--切换到数据库db1
+  mq = mq or skynet.localname(".mq")
+
+  recharge.get_all_recharge()
+end
+
+skynet.init(function()
+  skynet.register(".recharge")
+end)
+
+skynet.start(function()
+  skynet.dispatch("lua", function(session, _, cmd, ...)
+    local f = assert(CMD[cmd])
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+  end)
+end)

+ 131 - 0
project/service/relic_manual.lua

@@ -0,0 +1,131 @@
+-- 遗迹战令
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local skynet_retpack = skynet.retpack
+local asset = require "model.asset"
+local redisdriver = require "skynet.db.redis"
+local util = require "util"
+local common_fun = require "model.common_fun"
+local redis
+
+local cjson     = require "cjson"
+local cjson_decode   = cjson.decode   -- 解码
+local cjson_encode   = cjson.encode   -- 编码
+
+local INIT_FLAG = false
+
+local data_list = {
+    -- round = 1,   -- 版本
+    -- start_time = 0,      -- 开始时间
+    -- end_time = 0,        -- 结束时间
+}
+
+
+local CMD = {}
+local function save_data() 
+    local temp = {}
+    temp.round = data_list.round
+    temp.start_time = common_fun.time_str(data_list.start_time)
+    redis:hset(OTHER_REDIS_KEY, "relic_manual", cjson_encode(temp))
+end
+
+local function init_data()
+    local activitytime = skynet.localname(".activitytime")
+    local open_time = util.today(skynet.call(activitytime, "lua", "query"))
+    local data_conf = asset.DataConfig_proto[22]
+    local today = util.today()
+
+    if open_time > today then
+        return 
+    end
+    local round_time = DAY_SEC*data_conf.data2
+    local round = math.floor((today - open_time)/(round_time))+1
+    local start_time = open_time + (round-1)*round_time
+    local end_time = start_time + round_time
+
+    data_list.round = round
+    data_list.start_time = start_time
+    data_list.end_time = end_time
+    save_data()
+    INIT_FLAG = true
+end
+
+local function check_data()
+    local now = os.time()
+    -- 提前2s,与玩家时间做差异处理
+    if now > data_list.end_time-2 then
+        local data_conf = asset.DataConfig_proto[22]
+        local round_time = DAY_SEC*data_conf.data2
+        data_list.round = data_list.round + 1
+        data_list.start_time = data_list.end_time
+        data_list.end_time = data_list.start_time + round_time
+        save_data()
+    end
+end
+
+local function optimize()
+    local res = redis:hget(OTHER_REDIS_KEY, "relic_manual")
+    if res then
+        local temp = cjson_decode(res)
+        if not temp then
+            return 
+        end
+        data_list.round = temp.round
+        data_list.start_time = common_fun.split(temp.start_time)
+        local data_conf = asset.DataConfig_proto[22]
+        local round_time = DAY_SEC*data_conf.data2
+        data_list.end_time = data_list.start_time + round_time
+        if data_list.round and data_list.start_time then
+            INIT_FLAG = true
+        end
+    end
+end
+
+local function watchtime()
+    skynet.fork(function()
+        while true do
+            if not INIT_FLAG then
+                init_data()
+            else
+                check_data()
+            end
+            skynet.sleep(50)
+        end
+    end)
+end
+
+function CMD.start()
+    local conf = assert(option.redis)
+    redis = redisdriver.connect(conf)
+    redis:select(15)
+    optimize()
+    watchtime()
+end
+
+function CMD.relic_manual_data()
+    if not INIT_FLAG then
+        return 
+    else
+        return {
+            round = data_list.round,
+            start_time = data_list.start_time + 12*3600 -- //防止冬夏令时的问题
+        }
+    end
+end
+
+skynet.init(function()
+    skynet.register(".relic_manual")
+end)
+  
+skynet.start(function()
+    logger.label("<Relic_manual>,")
+    skynet.dispatch("lua", function(session,_, cmd, ...)
+        local f = assert(CMD[cmd])
+        if session == 0 then
+            f(...)
+        else
+            skynet_retpack(f(...))
+        end
+    end)
+end)

+ 38 - 0
project/service/snowflake.lua

@@ -0,0 +1,38 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local c = require "snowflake.core"
+local skynet_retpack = skynet.retpack
+
+local prefix = nil
+local MAX_WORKID_VAL = 1024
+local CMD = {}
+function CMD.start()
+  local work_id = assert(option.sid)
+  if work_id < 1 or work_id > 65535 then
+    assert(false, "Work id is in range of 1 - 65535.")
+  end
+
+  prefix = string.format("%02d-", work_id>>10)
+  work_id = work_id % MAX_WORKID_VAL
+  c.init(work_id)
+end
+
+function CMD.prefix()
+  return prefix
+end
+
+skynet.init(function()
+  skynet.register(".snowflake")
+end)
+
+skynet.start(function()
+  skynet.dispatch("lua", function(session, _, cmd, ...)
+    local f = assert(CMD[cmd])
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+  end)
+end)
+

+ 271 - 0
project/service/statistics.lua

@@ -0,0 +1,271 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local redisdriver = require "skynet.db.redis"
+local util = require "util"
+local common_fun = require "model.common_fun"
+local pipeline = require "pipeline"
+local logger = require "logger"
+local stringify = require "stringify"
+local httpc = require "http.httpc"
+local cjson     = require "cjson"
+
+
+local db_list 
+local db_character
+
+local KEY_STR = "statistics"
+local math_min = math.min
+local math_max = math.max
+
+local CMD = {}
+local next_collect = 0
+local function time_key(tm)
+    return os.date("%Y%m%d", tm)
+end
+
+local function check_next()
+    if next_collect == 0 then
+        next_collect = util.today()
+    end
+    local key = time_key(next_collect-DAY_SEC)
+    if db_list:hexists(KEY_STR, key) == 1 then
+        next_collect = next_collect + DAY_SEC
+    else
+        next_collect = next_collect
+    end
+end
+
+local function init()
+    local conf = assert(option.redis)
+    db_character = redisdriver.connect(conf)
+    db_character:select(0)
+    db_list = redisdriver.connect(conf)
+    db_list:select(11)
+    check_next()
+end
+
+local function myfloor(num)
+    return math.floor(num*100)/100
+end
+
+local function collect(tm)
+    local time = tm
+    local keys = common_fun.redis_keys_wait(db_character, "character:*")
+    local block = pipeline(1024)
+    if next(keys) then
+      for _, k in pairs(keys) do
+        block.add("hmget", k, "nickname", "uid", "level", "lastlogin", "lastlogout", "createtime")
+      end
+    end
+    local list = {
+        create = 0,         -- 激活用户
+        dau = 0,            -- 日活
+        wau = 0,            -- 周活
+        mau = 0,            -- 月活
+        lost = 0,           -- 流失
+        login = 0,          -- 再次登录的玩家
+        keep = 0,           -- 次日留存
+        keep3 = 0,          -- 3日留存
+        keep7 = 0,          -- 7日留存
+        keep30 = 0,         -- 30日留存
+        all = 0,            -- 总人数
+        rcg_player = 0,     -- 充值人数(每日)
+        rcg_money = 0,      -- 充值金额(每日)
+        arpu = 0,           -- 付费玩家价值(每日)
+        rcg_money30 = 0,    -- 充值金额(每月)
+        ltv = 0,            -- 价值
+    }
+
+    local resp = block.execute(db_character, {})    
+    for _, v in ipairs(resp) do
+        assert(v.ok)
+        local rs = v.out
+        if rs[2] then
+            local name = rs[1]
+            local uid = rs[2]
+            local level = tonumber(rs[3])
+            local lastlogin = tonumber(rs[4]) or 0
+            local lastlogout = tonumber(rs[5]) or 0
+            local createtime = tonumber(rs[6]) or 0
+            -- 当日激活
+            if createtime >= time and createtime < time + DAY_SEC then
+                list.create = list.create + 1
+            end
+
+            if lastlogin - createtime > 10 then
+                list.login = list.login + 1
+            end
+
+            if createtime < time + DAY_SEC then
+                local temp = math_min(time - lastlogout, time - lastlogin)
+                if temp < 0 then
+                    list.dau = list.dau + 1
+                end
+
+                if temp < 6 * DAY_SEC then
+                    list.wau = list.wau + 1
+                end
+                
+                if temp < 29 * DAY_SEC then
+                    list.mau = list.mau + 1
+                end
+
+                if temp > 30 * DAY_SEC then
+                    list.lost = list.lost + 1
+                end
+
+                local temp2 = math_max(math_min(lastlogin, time + DAY_SEC), math_min(lastlogout, time + DAY_SEC)) - createtime
+                if temp2 > DAY_SEC then
+                    list.keep = list.keep + 1
+                end
+
+                if temp2 > 2 * DAY_SEC then
+                    list.keep3 = list.keep3 + 1
+                end
+
+                if temp2 > 6 * DAY_SEC then
+                    list.keep7 = list.keep7 + 1
+                end
+
+                if temp2 > 29 * DAY_SEC then
+                    list.keep30 = list.keep30 + 1
+                end
+                list.all = list.all + 1
+            end
+        end
+    end
+    local ok,herrno,info = pcall(httpc.post, RECHARGE_SERVICE, '/recharge_info', {
+        serverid = option.sid,
+        starttime = time,
+    })
+
+    if herrno ~= 200 then
+        logger.trace("查询充值失败.errno %s", herrno)
+        return
+    end
+
+    -- info 解码
+    local order = cjson.decode(info)
+    if order.erron ~= 0 then
+        logger.trace("查询充值失败 %s", stringify(order))
+        return
+    end
+
+    list.rcg_player = order.data.day_player or 0
+    list.rcg_money = order.data.day_recharge or 0
+    list.rcg_money30 = order.data.month_recharge or 0
+    if list.create > 0 then
+        list.keep = myfloor(list.keep/list.create)
+        list.keep3 = myfloor(list.keep3/list.create)
+        list.keep7 = myfloor(list.keep7/list.create)
+        list.keep30 = myfloor(list.keep30/list.create)
+    else
+        list.keep = "NaN"
+        list.keep3 = "NaN"
+        list.keep7 = "NaN"
+        list.keep30 = "NaN"
+    end
+
+    if list.rcg_player > 0 then
+        list.arpu = list.rcg_money/list.rcg_player
+    else
+        list.arpu = "NaN"
+    end
+
+    db_list:hset(KEY_STR, time_key(tm), cjson.encode(list))
+    return list
+end
+
+
+local function watchtime()
+    skynet.fork(function()
+        while(true) do
+            if os.time() >= next_collect then
+                collect(next_collect-DAY_SEC)
+                check_next()
+            end
+            skynet.sleep(6000)
+        end
+    end)
+end
+
+local key_index = {
+    "create",
+    "dau",
+    "wau",
+    "mau",
+    "lost",
+    "login",
+    "keep",
+    "keep3",
+    "keep7",
+    "keep30",
+    "all",
+    "rcg_player",
+    "rcg_money",
+    "rcg_money30",
+    "arpu",
+    "ltv",
+}
+function CMD.get_statistics_data(time)
+    local activitytime = skynet.localname(".activitytime")
+    local open_time = skynet.call(activitytime, "lua", "query")
+
+    local now = util.today()
+    if not time then 
+        time = now
+    end
+
+    local start_time = util.month_one(time)
+    local end_time = util.month_one(start_time+31*DAY_SEC)-DAY_SEC
+    if start_time < open_time then
+        start_time = util.today(open_time)
+    end
+    if end_time >= now then
+        end_time = now - DAY_SEC
+    end
+
+    local ret = {} 
+    ret[1] = common_fun.scopy(key_index)
+    table.insert(ret[1], 1, "date")
+    for i = start_time, end_time, DAY_SEC do
+        local key = time_key(i)
+        local data
+        if db_list:hexists(KEY_STR, key) == 1 then
+            data = cjson.decode(db_list:hget(KEY_STR, key))
+        else
+            data = collect(i)
+        end
+        local temp = {key}
+        for _, v in pairs(key_index) do
+            local value = data and tostring(data[v])
+            if not value or value == 'nil' then
+                value = 'NaN'
+            end
+            table.insert(temp, value)
+        end
+        table.insert(ret, temp)                
+    end
+    return ret
+end
+
+function CMD.start()
+    init()
+    watchtime()
+end
+
+skynet.init(function()
+    skynet.register(".statistics")
+end)
+  
+skynet.start(function()
+    skynet.dispatch("lua", function(session, _, cmd, ...)
+        local f = assert(CMD[cmd])
+        if 0==session then
+            f(...)
+        else
+            skynet.retpack(f(...))
+        end
+    end)
+end)
+  

+ 279 - 0
project/service/usercenter.lua

@@ -0,0 +1,279 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local redisdriver = require "skynet.db.redis"
+local logger = require "logger"
+-- local keygen = require "model.keygen"
+local stringify = require "stringify"
+local util = require "util"
+
+local os_time = os.time
+local string_format = string.format
+local skynet_retpack = skynet.retpack
+local skynet_call = skynet.call
+local skynet_send = skynet.send
+local table_insert = table.insert
+local table_remove = table.remove
+local table_length = util.length
+local trace = logger.trace
+
+local login_user = {}
+local online_user = {}
+local allowed_server_id = {}
+local redis
+local uid_num
+
+local function keygen()
+    local rand_list = {1,2,1,1,2,1}
+    assert(uid_num and uid_num < 999999, uid_num)
+    uid_num = uid_num + rand_list[(uid_num%(#rand_list))+1]
+    redis:set("uidnum", uid_num)
+    return string.format("%02d-%06d", option.sid, 100000+uid_num)
+end
+
+local CMD = {}
+function CMD.start()
+  logger.info("start")
+  local conf = assert(option.redis)
+  redis = redisdriver.connect(conf)
+  redis:select(0)
+  uid_num = tonumber(redis:get("uidnum") or 0)
+  local res = redis:smembers("sharepoint")
+  for _, sid in ipairs(res) do
+    allowed_server_id[math.tointeger(sid)] = true
+  end
+
+  local sid = assert(option.sid)
+  allowed_server_id[sid] = true
+  logger.info("%s:%s\n%s", conf.host, conf.port, stringify(allowed_server_id))
+end
+
+function CMD.service_list()
+  return allowed_server_id
+end
+
+function CMD.batch_save(pkg)
+  assert(#pkg > 1)
+  pkg[1] = string_format("character:%s", pkg[1])
+  return redis:hmset(pkg)
+end
+
+function CMD.load(uid)
+  local k = string_format("character:%s", uid)
+  local rets = redis:hgetall(k)
+  if next(rets) then
+    return rets
+  end
+end
+
+local facebook = nil -- 云账号登陆废弃
+local function retrieve_facebook_account(account, sid, channel)
+  facebook = facebook or skynet.localname(".facebook")
+
+  local errno, fb = skynet.call(facebook, "lua", "retrieve", account, channel)
+  local uid = nil
+  if errno == 200 then
+    uid = redis:get(string_format("account:%s:%s:%s", channel, sid, fb))
+  end
+  return errno, uid
+end
+
+function CMD.bingding(channel, sid, account, cuid) -- CDPK3-005 海外SDK
+  -- 检查账号是否已经存在
+  local key = string_format("account:%s:%s:%s", channel, sid, account)
+  local uid = redis:get(key)
+  if uid then
+    return STD_ERR.PLYAER_BING_ACCOUNT_EXIST    -- 绑定账号已经存在
+  end
+  logger.trace( "##### key %s uid %s", key, cuid)
+  local ok = redis:set(key, cuid)
+  if ok ~= "OK" then
+    return STD_ERR.PLYAER_BING_SERVER_ERROR
+  end
+  return 0
+end
+
+function CMD.login(args, agent)
+  local sid = assert(args.sid)              -- 服务器唯一标识
+  local account = assert(args.account)      -- 帐号唯一标识(渠道分配)
+  -- local appid = assert(args.appid)          -- App唯一标识
+  local channel = assert(args.channel)      -- 渠道唯一标识
+  -- local platform = assert(args.platform)    -- 操作系统(pc,ios,android等)
+  -- local version = assert(args.version)      -- 客户端版本号
+
+  if not allowed_server_id[sid] then return 1 end   -- 防止非法的服务器标识
+  if login_user[account] then return 2 end          -- 防止多设备登录
+  login_user[account] = true
+
+  -- 查找帐号在指定 sid 上创建的角色id
+  local k = string_format("account:%s:%s:%s", channel, sid, account)
+  local uid = redis:get(k)
+  -- 云账号登陆废弃
+  -- if uid == nil then
+  --   local errno, val = retrieve_facebook_account(account, sid, channel)
+  --   if errno == 200 then
+  --     uid = val
+  --   elseif errno ~= 404 then
+  --     login_user[account] = nil
+  --     return 3  -- 暂时不能放用户进来
+  --   end
+  -- end
+
+  if uid then
+    -- 通知当前在线设备下线
+    local old = online_user[uid]
+    online_user[uid] = agent
+    if old then
+      pcall(skynet_call, old, "lua", "multilogin")
+    end
+
+    -- 载入角色数据
+    k = string_format("character:%s", uid)
+    local rets = redis:hgetall(k)
+    if next(rets) then
+      login_user[account] = nil
+      return 0, rets
+    end
+  else
+    -- 为尚未创建角色的帐号关联一个新的角色id
+    uid = keygen()
+    local key = string_format("character:%s", uid)
+    assert(not redis:exists(key))
+    online_user[uid] = agent
+    redis:set(k, uid)
+    k = string_format("character:%s", uid)
+  end
+
+  -- 创建并保存角色的基础信息
+  local rets = {
+    k,
+    'uid', uid,
+    'account', account,
+    -- 'appid', appid,
+    'channel', channel,
+    -- 'platform', platform,
+    'sid', sid,
+    'createtime', os_time(),
+    'tourist', args.tourist or 1 -- nil/1:非游客账号 2: 游客账号 CDPK3-005 海外SDK
+  }
+  redis:hmset(rets)
+  table_remove(rets, 1)
+  login_user[account] = nil
+  return 0, rets
+
+end
+
+function CMD.exsits(args)
+    local sid = assert(args.sid)
+    local account = assert(args.account)      -- 帐号唯一标识(渠道分配)
+    local channel = assert(args.channel)      -- 渠道唯一标识
+    local k = string_format("account:%s:%s:%s", channel, sid, account)
+    return redis:exists(k)
+end
+
+function CMD.logout(uid, agent)
+  if online_user[uid] == agent then
+    online_user[uid] = nil
+    trace("User '%s' logout", uid)
+  end
+end
+
+--向全服在线玩家广播网络消息
+function CMD.broadcast(pname, args)
+  for _, agent in pairs(online_user) do
+    pcall(skynet_send, agent, "lua", "broad_cast_msg", pname, args)
+  end
+end
+
+-- 向全服在线玩家的 agent 发送命令
+function CMD.command(cmd, ...)
+  for _, agent in pairs(online_user) do
+    pcall(skynet_send, agent, "lua", cmd, ...)
+  end
+end
+
+-- 向指定玩家的 agent 发送命令
+function CMD.agent_command(uid, cmd, ...)
+    trace("User '%s' agent_command", uid)
+    local agent = online_user[uid]
+  if agent then
+    trace("User '%s' agent_command", uid)
+    local ok, err = pcall(skynet_send, agent, "lua", cmd, ...)
+    if ok then return err end
+  end
+end
+
+-- 向部分玩家的 agent 发送命令 uids = {[uid] = true}
+function CMD.batch_agent_command(uids, cmd, ...)
+  for uid in pairs(uids) do
+    local agent = online_user[uid]
+    if agent then
+      pcall(skynet_send, agent, "lua", cmd, ...)
+    end
+  end
+end
+
+function CMD.maintain(timeout, ...)
+  -- 向全服在线玩发送维护指令
+  for _, agent in pairs(online_user) do
+    pcall(skynet_send, agent, "lua", "maintain", ...)
+  end
+
+  -- 等待在线玩家执行标准退出流程,超时强制退出
+  timeout = math.max(5, timeout or 15)
+  for i=1, timeout do
+    local number = table_length(online_user) + table_length(login_user)
+    if number == 0 then
+      return true
+    end
+
+    logger.warn("正在等待 %s 个用户下线", number)
+    skynet.sleep(100)
+  end
+end
+
+-- 批量查询玩家是否在线
+function CMD.batch_ping(uids)
+  local ret = {}
+  for _, uid in ipairs(uids) do
+    table_insert(ret, online_user[uid] and true or false)
+  end
+  return ret
+end
+
+-- 向指定的玩家发送gm命令
+function CMD.gm_command(uid, cmd, ...)
+  local agent = online_user[uid]
+  if agent then
+    local ok, errno, msg = pcall(skynet_call, agent, "lua", cmd, ...)
+    if ok then
+        return errno, msg
+    else
+        return 5, "系统异常"
+    end
+  end
+  return 1, "玩家不在线/不存在"
+end
+
+skynet.init(function()
+  skynet.register(".usercenter")
+end)
+
+skynet.info_func(function()
+  return stringify({
+    agent = table_length(online_user),
+    login = table_length(login_user)
+  })
+end)
+
+skynet.start(function()
+  logger.label("<Usercenter>")
+  skynet.dispatch("lua", function(session, _, cmd, ...)
+    local f = assert(CMD[cmd])
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+  end)
+end)
+

+ 109 - 0
project/service/webagent.lua

@@ -0,0 +1,109 @@
+local skynet = require "skynet"
+local socket = require "skynet.socket"
+local cjson = require "cjson"
+local logger = require "logger"
+local httpd = require "http.httpd"
+local sockethelper = require "http.sockethelper"
+local urllib = require "http.url"
+local logger = require "logger"
+require "model.asset"
+require "model.loader"
+local traceback = debug.traceback
+
+local index = ...
+local skynet_retpack = skynet.retpack
+local socket_accept = socket.start
+local socket_close = socket.close
+local urllib_parse = urllib.parse
+local urllib_parse_query = urllib.parse_query
+local cjson_encode = cjson.encode
+
+local headers = {
+    ['Access-Control-Allow-Origin'] = '*', -- 这里写允许访问的域名就可以了,允许所有人访问的话就写*
+    ['Access-Control-Allow-Credentials'] = true,
+ } 
+
+local function response(fd, ...)
+  local args = {...}
+  local ok, err = httpd.write_response(sockethelper.writefunc(fd), args[1], args[2], headers)
+  if not ok then
+    -- if err == sockethelper.socket_error , that means socket closed.
+    logger.error("fd = %d, %s", fd, err)
+  end
+end
+
+local webpage = {}
+local function loadfile(url)
+  local f = webpage[url]
+  if f then return f end
+  if url == "/favicon.ico" then
+    -- discard the <favico.ico> request
+    return nil
+  end
+
+  local ok, ret = pcall(require, "webpage" .. url)
+  --local ok, ret = xpcall(require, traceback, "webpage" .. url)
+  if not ok then
+    logger.trace("webpage: ret"..ret)
+    return nil
+   end
+  webpage[url] = ret
+  return ret
+end
+
+local function dispatch_request(fd, method, url, body, ipaddr, recv_header)
+  local path, query = urllib_parse(url)
+  local f = loadfile(path)
+  if not f then
+    response(fd, 404, "Not Found")
+    return
+  end
+
+  local ok, args = pcall(urllib_parse_query, query)
+  if method == "POST" then
+    query = body
+    args = cjson.decode(body)
+  end
+
+  local resp, header = f(args, ipaddr, recv_header)
+  if resp then
+    if type(resp) == "table" then
+      response(fd, 200, cjson_encode(resp), header)
+    else
+      assert(type(resp) == "string")
+      response(fd, 200, resp, header)
+    end
+  end
+end
+
+local CMD = {}
+function CMD.dispatch(fd, ipaddr)
+  socket_accept(fd)
+  local code, url, method, header, body = httpd.read_request(sockethelper.readfunc(fd), 8192)
+  if code then
+    if code == 200 then
+      dispatch_request(fd, method, url, body, ipaddr, header)
+    else
+      response(fd, code)
+    end
+  else
+    if url == sockethelper.socket_error then
+      logger.warn("socket closed")
+    else
+      logger.error(url)
+    end
+  end
+  socket_close(fd)
+end
+
+skynet.start(function()
+  logger.label(string.format("<Webagent %d>", index))
+	skynet.dispatch("lua", function(session,_, cmd, ...)
+    local f = assert(CMD[cmd])
+    if session == 0 then
+      f(...)
+    else
+      skynet_retpack(f(...))
+    end
+	end)
+end)

+ 45 - 0
project/service/webserver.lua

@@ -0,0 +1,45 @@
+local skynet = require "skynet"
+local socket = require "skynet.socket"
+local logger = require "logger"
+
+local table_concat = table.concat
+local skynet_send = skynet.send
+
+local function ipaddr_parse(addr)
+	-- check for format 1.11.111.111 for IPV4
+	local chunks = {addr:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")}
+	if #chunks == 4 then
+		return table_concat(chunks, ".")
+	end
+
+	-- check for IPV6 format, should be 8 'chunks' of numbers/letters
+	-- without trailing chars
+	local chunks = {addr:match(("([a-fA-F0-9]*):"):rep(8):gsub(":$","$"))}
+	if #chunks == 8 then
+		return table_concat(chunks, ":")
+	end
+	return addr
+end
+
+skynet.start(function()
+  logger.label("<Webserver>")
+
+	local agent = {}
+	for index=1, 3 do
+		table.insert(agent, skynet.newservice("webagent", index))
+	end
+
+  local balance = 1
+  local webport = assert(option.web_port)
+	local id = socket.listen(option.web_host, webport)
+	logger.info(option.web_host ..":%d", webport)
+
+  socket.start(id, function(fd, addr)
+    local ipaddr = ipaddr_parse(addr)
+    skynet_send(agent[balance], "lua", "dispatch", fd, ipaddr)
+    balance = balance + 1
+    if balance > #agent then
+      balance = 1
+    end
+  end)
+end)

+ 163 - 0
project/service/ws_gate.lua

@@ -0,0 +1,163 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local socket = require "skynet.socket"
+local websocket = require "http.websocket"
+local socketdriver = require "skynet.socketdriver"
+local logger = require "logger"
+local traceback = debug.traceback
+local stringify = require "stringify"
+
+local nodelay
+local watchdog
+local connection = {}	-- fd -> connection : { fd , client, agent , ip, mode }
+local forwarding = {}	-- agent -> connection
+
+local client_number = 0
+local maxclient	-- max client
+
+local function unforward(c)
+	if c.agent then
+		forwarding[c.agent] = nil
+		c.agent = nil
+		c.client = nil
+	end
+end
+
+local function close_fd(fd)
+	local c = connection[fd]
+	if c then
+		socketdriver.close(fd)
+		unforward(c)
+		connection[fd] = nil
+		client_number = client_number - 1
+	end
+end
+
+local handle = {}
+
+function handle.connect(fd)
+	-- print("ws connect from: " .. tostring(fd))
+	if client_number >= maxclient then
+		socketdriver.close(fd)
+		return
+	end
+	if nodelay then
+		socketdriver.nodelay(fd)
+	end
+
+	client_number = client_number + 1
+	local addr = websocket.addrinfo(fd)
+    local c = {
+		fd = fd,
+		ip = addr,
+	}
+	connection[fd] = c
+	skynet.send(watchdog, "lua", "socket", "open", fd, addr)
+end
+
+function handle.handshake(fd, header, url)
+    -- local addr = websocket.addrinfo(fd)
+    -- print("ws handshake from: " .. tostring(fd), "url", url, "addr:", addr)
+    -- print("----header-----")
+    -- for k,v in pairs(header) do
+    --     print(k,v)
+    -- end
+    -- print("--------------")
+end
+
+function handle.message(fd, msg)
+	-- print("ws ping from: " .. tostring(fd), msg.."\n")
+	local sz = #msg
+    -- recv a package, forward it
+	local c = connection[fd]
+	local agent = c.agent
+	if agent then
+    -- It's safe to redirect msg directly , gateserver framework will not free msg.
+        skynet.redirect(agent, c.client, "client", fd, msg, sz)
+	else
+		skynet.send(watchdog, "lua", "socket", "data", fd, msg)
+		-- skynet.tostring will copy msg to a string, so we must free msg here.
+		skynet.trash(msg,sz)
+	end
+end
+
+function handle.ping(fd)
+    -- print("ws ping from: " .. tostring(fd) .. "\n")
+end
+
+function handle.pong(fd)
+    -- print("ws pong from: " .. tostring(fd))
+end
+
+function handle.close(fd, code, reason)
+    -- print("ws close from: " .. tostring(fd), code, reason)
+    close_fd(fd)
+	skynet.send(watchdog, "lua", "socket", "close", fd)
+end
+
+function handle.error(fd)
+    -- print("ws error from: " .. tostring(fd))
+    close_fd(fd)
+	skynet.send(watchdog, "lua", "socket", "error", fd)
+end
+
+
+local CMD = {}
+
+function CMD.open(source, conf)
+	watchdog = conf.watchdog or source
+
+	local address = conf.address or "0.0.0.0"
+	local port = assert(conf.port)
+	local protocol = conf.protocol or "ws"
+	maxclient = conf.maxclient or 1024
+	nodelay = conf.nodelay
+    local fd = socket.listen(address, port)
+    logger.trace("Listen websocket port:%s protocol:%s", port, protocol)
+    socket.start(fd, function(fd, addr)
+        logger.trace(string.format("accept client socket_fd: %s addr:%s", fd, addr))
+        websocket.accept(fd, handle, protocol, addr)
+    end)
+end
+
+function CMD.forward(source, fd, client, address)
+	local c = assert(connection[fd])
+	unforward(c)
+	c.client = client or 0
+	c.agent = address or source
+	forwarding[c.agent] = c
+end
+
+function CMD.accept(source, fd)
+	local c = assert(connection[fd])
+	unforward(c)
+end
+
+function CMD.kick(source, fd)
+	websocket.close(fd)
+end
+
+skynet.register_protocol {
+	name = "client",
+	id = skynet.PTYPE_CLIENT,
+}
+
+skynet.start(function()
+    skynet.dispatch("lua", function(session, source, cmd, ...)
+        local f = CMD[cmd]
+        if not f then
+            skynet.error("simplewebsocket can't dispatch cmd ".. (cmd or nil))
+            skynet.ret(skynet.pack({ok=false}))
+            return
+        end
+        if session == 0 then
+            f(source, ...)
+        else
+            skynet.ret(skynet.pack(f(source, ...)))
+        end
+    end)
+
+    skynet.register(".ws_gate")
+
+    skynet.error("ws_gate booted...")
+end)

+ 589 - 0
project/service/ws_watchdog.lua

@@ -0,0 +1,589 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local socket = require "skynet.socket"
+-- local sprotoloader = require "sprotoloader"
+local logger = require "logger"
+local stringify = require "stringify"
+local util = require "util"
+local cjson    = require "cjson"
+-- local JsSProto = require "JsSProto"
+local pbSproto = require "pbSproto"
+local websocket = require "http.websocket"
+
+
+
+local skynet_call = skynet.call
+local skynet_send = skynet.send
+local skynet_now = skynet.now
+local skynet_newservice = skynet.newservice
+local skynet_sleep = skynet.sleep
+local skynet_fork = skynet.fork
+local skynet_wakeup = skynet.wakeup
+local skynet_wait = skynet.wait
+local skynet_yield = skynet.yield
+local skynet_retpack = skynet.retpack
+local websocket_write = websocket.write
+local string_pack = string.pack
+local traceback = debug.traceback
+local table_concat = table.concat
+local table_insert = table.insert
+local table_remove = table.remove
+local warn = logger.warn
+local trace = logger.trace
+local info = logger.info
+local cjson_decode   = cjson.decode   -- 解码
+local cjson_encode   = cjson.encode   -- 编码
+
+-- local mydispatch = JsSProto.decode
+-- local mypack = JsSProto.encode
+
+local mydispatch = pbSproto.decode
+local mypack = pbSproto.encode
+
+local MAX_ONLINE_NUM = 3000
+local MAX_CONCURRENT_NUM = 32   -- 最大并发数
+
+-- local host = sprotoloader.load(1):host( "package")
+-- local pack = host:attach(sprotoloader.load(2))
+
+local gate
+local watchdog
+local loginserver
+local protocol
+local function ipaddr_parse(addr)
+	-- check for format 1.11.111.111 for IPV4
+	local chunks = {addr:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")}
+	if #chunks == 4 then
+		return table_concat(chunks, ".")
+	end
+
+ -- check for IPV6 format, should be 8 'chunks' of numbers/letters
+	-- without trailing chars
+	local chunks = {addr:match(("([a-fA-F0-9]*):"):rep(8):gsub(":$","$"))}
+	if #chunks == 8 then
+		return table_concat(chunks, ":")
+	end
+	return addr
+end
+
+local function write(fd, data)
+    return pcall(websocket_write, fd, data, "binary")
+    -- return websocket_write(fd, data)
+end
+
+local function send(fd, pname, args)
+    pcall(write, fd, mypack(pname, args))
+--   write(fd, mypack(pname, args))
+end
+
+local sessions = {}               -- 会话字典, fd -> { agent, args, fd, ipaddr... }
+local total_session_number = 0    -- 当前连接数
+local total_player_number = 0     -- 当前在线玩家数
+local total_offline_number = 0
+local function close_session(fd)
+  local session = sessions[fd]
+  sessions[fd] = nil
+  if session then
+    session.deadline = false
+    total_session_number = total_session_number - 1
+    skynet_call(gate, "lua", "kick", fd)
+    trace("关闭套接字 %s", fd)
+    trace("session.agent %s", session.agent)
+    if session.agent then
+      total_player_number = total_player_number - 1
+      total_offline_number = total_offline_number + 1
+
+      -- disconnect never return
+      skynet_send(session.agent, "lua", "disconnect")
+    end
+  end
+end
+
+local function new_session(fd, ipaddr)
+  local self = {
+    agent = false,
+    args = false,
+    next = false,
+    -- deadline = skynet_now()+1000,
+    deadline = skynet_now()+100000,
+    fd = fd,
+    ipaddr = ipaddr,
+  }
+  function self.close()
+    close_session(fd)
+    websocket.clear_pool(fd)
+  end
+  total_session_number = total_session_number + 1
+  return self
+end
+
+local pool = {}
+local pool_size = 8
+local function spawn()
+  local min = math.min
+  while true do
+    local step = min(pool_size - #pool, 16)
+    for i=1, step do
+      table_insert(pool, skynet_newservice("agent"))
+    end
+    skynet_sleep(50)
+  end
+end
+
+local function new_agent()
+  local len = #pool
+  if len > 0 then
+    local address = pool[len]
+    pool[len] = nil
+    return address
+  else
+    return skynet_newservice("agent")
+  end
+end
+
+local offline_avg_number = 1    -- 每分钟的平均下线人数
+local queue = {}                -- 等待队列
+local waiter = {}
+local function collect()
+  local max = math.max
+  local floor = math.floor
+  local sampling = { 0,0,0,0,0,0,0,0,0,0 }
+  while true do
+    skynet_sleep(60*100)
+    table_remove(sampling, 1)
+    table_insert(sampling, total_offline_number)
+    total_offline_number = 0
+    local sum = 0
+    for _, v in ipairs(sampling) do
+      sum = sum + v
+    end
+    offline_avg_number = max(floor(sum / #sampling), 1)
+  end
+end
+
+local function flush_invalid_session()
+  for pos = #queue, 1, -1 do
+    local fd = queue[pos]
+    if sessions[fd] == nil then
+      table_remove(queue, pos)
+      trace("从登录队列中移除失效套接字: %s", fd)
+    end
+  end
+end
+
+local function session_wakeup(session)
+  assert(not session.agent)
+  local response = assert(session.response)
+  local ctx = {
+    args = assert(session.args),
+    fd = assert(session.fd),
+    ipaddr = assert(session.ipaddr),
+    gate = gate,
+    watchdog = watchdog,
+    protocol = protocol
+  }
+  --  启动 agent 的初始化流程
+  local agent = new_agent()
+--   logger.trace("new_agent:%s", agent)
+  local ok, ret = pcall(skynet_call, agent, "lua", "start", ctx)
+  if ok and sessions[session.fd] then
+    trace("Agent 进入游戏, fd=%s", session.fd)
+    session.args = false
+    session.response = false
+    session.agent = agent
+    total_player_number = total_player_number + 1
+    response(ret)
+    websocket.clear_pool(session.fd)
+  else
+    warn("Agent 启动失败, fd=%s", session.fd)
+    assert(session.agent == false)
+    session.close()
+    skynet_send(agent, "lua", "disconnect")
+    logger.trace("Agent 启动失败原因 fd:%s,%s", session.fd, ret or "nil")
+  end
+end
+
+local function dispatch_session_wakeup()
+  local ti = skynet_now()
+  local freenum = math.min(MAX_ONLINE_NUM-total_player_number, MAX_CONCURRENT_NUM)
+  if freenum > 0 and #queue > 0 then
+    local ref = 0
+    local current_thread = coroutine.running()
+    local launch = function(session)
+      session_wakeup(session)
+      ref = ref - 1
+      if ref == 0 then
+        skynet_wakeup(current_thread)
+      end
+    end
+
+    while freenum > 0 and #queue > 0 do
+      local fd = table_remove(queue, 1)
+      local session = sessions[fd]
+      if session then
+        freenum = freenum - 1
+        ref = ref + 1
+        skynet_fork(launch, session)
+      end
+    end
+
+    if ref > 0 then
+      skynet_wait(current_thread)
+      assert(ref == 0)
+    end
+  end
+  return skynet_now()-ti
+end
+
+local function timecost(pos)
+  assert(pos > 0)
+  local freenum = MAX_ONLINE_NUM - total_player_number
+  local ti = pos / MAX_CONCURRENT_NUM + 1
+  if freenum < pos then
+    ti = ti + ((pos-freenum) / offline_avg_number) * 60
+    return math.ceil(ti), true -- seconds
+  else
+    return math.ceil(ti)
+  end
+end
+
+local data = {}
+local function sync_progress(fd, pos)
+  assert(pos > 0)
+  local ti, queued = timecost(pos)
+  if ti > 5 or queued then
+    data.number = pos
+    data.waiting = ti
+    -- send(fd, "queue", data)
+    trace("套接字(%s): 排在第 %s 的位置,预计 %s 秒之后进入游戏", fd, pos, ti)
+  end
+end
+
+local function broadcast()
+  local min = math.min
+  local max = math.max
+  local limit_size = 128
+  local invalid_session = { agent=true }
+  local paging = {}
+  while true do
+    local ti = skynet_now()
+    local freenum = MAX_ONLINE_NUM - total_player_number
+    if freenum < #queue then
+      -- 清除无效 session 对象
+      flush_invalid_session()
+
+      for pos, fd in ipairs(queue) do
+        if pos > limit_size then
+          -- 添加到分页缓存
+          table_insert(paging, fd)
+        else
+          sync_progress(fd, pos)
+        end
+      end
+    end
+
+    -- 向客户端推送排队进度
+    local pos = limit_size
+    while #paging > 0 do
+      --给其它协程一些工作机会
+      skynet_sleep(10)
+
+      -- 向部分客户端推送进度
+      local range = min(#paging, limit_size)
+      for i=1, range do
+        local fd = table_remove(paging, 1)
+        local session = sessions[fd] or invalid_session
+        if not session.agent then
+          pos = pos + 1
+          sync_progress(fd, pos)
+        end
+      end
+    end
+
+    -- 执行每5秒一个同步周期(未过载时)
+    ti = max(500-(skynet_now()-ti), 100)
+    skynet_sleep(ti)
+  end
+end
+
+function waiter.add(fd)
+  table_insert(queue, fd)
+  sync_progress(fd, #queue)
+end
+
+function waiter.start()
+  skynet.fork(broadcast)
+  skynet.fork(collect)
+
+  -- 定期让一些排队玩家进入游戏(有空位的话)
+  skynet.fork(function()
+    while true do
+      local ti = 100 - dispatch_session_wakeup()
+      if ti > 0 then
+        skynet_sleep(ti)
+      else
+        skynet_yield()
+      end
+    end
+  end)
+end
+
+local list = { next=false }
+local tail = list
+local timeout = {}
+local function scan()
+  local now = skynet_now()
+  local nxt = list.next   -- The front node
+  local prev = list
+  while nxt do
+    if nxt.deadline == false then
+      -- remove from timeout queue
+      prev.next = nxt.next
+    elseif nxt.deadline < now then
+      prev.next = nxt.next
+      nxt.close()
+      warn("The client %s login timeout", nxt.fd)
+    else
+      prev = nxt
+    end
+    nxt = nxt.next
+  end
+  tail = prev
+end
+
+function timeout.add(session)
+  tail.next = session
+  tail = session
+end
+
+function timeout.start()
+  skynet.fork(function()
+    while true do
+      scan()
+      skynet_sleep(100)
+    end
+  end)
+end
+
+local SOCKET = {}
+function SOCKET.open(fd, addr)
+  local ipaddr = ipaddr_parse(addr)
+  trace("New client %d from %s", fd, ipaddr)
+
+  -- Create session object
+  local session = new_session(fd, ipaddr)
+  websocket.forward(fd,protocol,ipaddr)
+  sessions[fd] = session
+  timeout.add(session)
+  skynet_call(gate, "lua", "accept", fd)
+end
+
+function SOCKET.close(fd)
+  close_session(fd)
+end
+
+function SOCKET.error(fd, msg)
+  close_session(fd)
+end
+
+function SOCKET.warning(fd, size)
+  -- size K bytes havn't send out in fd
+end
+
+local REQUEST = setmetatable({}, {__newindex = function (t, k, v)
+    pbSproto.register_msg(k)
+    rawset(t, k, v)
+end})
+
+local function dispatch_request(session, pname, args, response)
+  assert(session)
+  local f = REQUEST[pname]
+  if f then
+    f(session, args, response)
+  else
+    warn("Not found request '%s'", pname)
+    session.close()
+  end
+end
+
+local function dispatch_response(session)
+  assert(session)
+  warn("Does not support response")
+  session.close()
+end
+
+local function dispatch_message(session, type, ...)
+--   local f = (type == "REQUEST") and dispatch_request or dispatch_response
+  local f = dispatch_request
+  local ok, msg = xpcall(f, traceback, session, ...)
+  if not ok then
+    warn(msg)
+    session.close()
+  end
+end
+
+function SOCKET.data(fd, msg)
+  local session = sessions[fd]
+  if session then
+    -- dispatch_message(session, host:dispatch(msg))
+    dispatch_message(session, mydispatch(msg))
+  else
+    assert(false, "Does not found session %s", fd)
+  end
+end
+
+local CMD = {}
+function CMD.start(conf)
+  info("start")
+  watchdog = skynet.self()
+  loginserver = skynet.localname(".loginserver")
+  skynet.fork(spawn)
+  timeout.start()
+  waiter.start()
+  protocol = conf.protocol or "ws"
+  skynet_call(gate, "lua", "open", conf)
+end
+
+function CMD.close(fd)
+	close_session(fd)
+end
+
+-- Reference: webpage/shutdown
+local maintain = LAUNCH.maintain
+function CMD.maintain()
+  maintain = true
+  --skynet_call(gate, "lua", "close")
+  skynet_sleep(50)
+end
+
+--服务器维护状态查询
+function CMD.getstatus()
+  return maintain
+end
+
+function CMD.open()
+  maintain = false
+  skynet_sleep(50)
+end
+
+-- Reference: webpage/hotfix
+function CMD.hotfix()
+  local invalid = pool
+  pool = {}
+
+  for _, agent in ipairs(invalid) do
+    skynet_send(agent, "lua", "exit")
+  end
+end
+
+local account_whitelist = {}
+function CMD.addnew_account_whitelist(account)
+  account_whitelist[account] = true
+end
+
+local ipaddr_whitelist = LAUNCH.white_list or {}
+function CMD.addnew_ipaddr_whitelist(ipaddr)
+  ipaddr_whitelist[ipaddr] = true
+end
+
+local s_public = nil    -- 角色创建限制
+function REQUEST.login(session, args, response)
+  assert(args ~= nil)
+  assert(type(response) == "function")
+  local fd = assert(session.fd)
+  if session.agent or session.args then
+    return
+  end
+  args.channel = args.channel or ""
+  session.args = args
+  session.deadline = false        -- remove from timeout queue
+  session.response = function(r)  -- login response
+    write(fd, response(r))
+  end
+
+  if maintain then
+    -- In the maintenance
+    while true do
+      local account = args.account or ""
+      if account_whitelist[account] then
+        break
+      end
+      local ipaddr = session.ipaddr
+      if ipaddr_whitelist[ipaddr] then
+        break
+      end
+
+      logger.info("MAINTAIN: channel=%s, account=%s, ipaddr=%s", args.channel, account, ipaddr)
+      session.response({errno=STD_ERR.PLYAER_MAINTAIN or 9})   -- 正在维护
+      return
+    end
+  end
+
+  -- 检查人口数量限制
+  -- s_public = s_public or skynet.localname(".public")
+  -- if skynet.call(s_public, "lua","role_limit", "rolelimit_isclose",args.account) then
+  --   logger.trace(" ### ipaddr_whitelist:%s,ipaddr:%s", ipaddr_whitelist[session.ipaddr] and "true" or "false", session.ipaddr)
+  --   if not ipaddr_whitelist[session.ipaddr] then
+  --     session.response({errno=STD_ERR.PLYAER_LIMIT_CREATE}) -- 限制角色创建
+  --     send(fd, 'exception', { type='role_limit', brief=STD_ERR.PLYAER_LIMIT_CREATE })
+  --     session.close()
+  --     return
+  --   else
+  --     logger.trace(" 角色创建限制 白名单登陆不限制角色创建")
+  --   end
+  -- end
+
+  -- Verify login token
+  local succ,access_info,account = skynet_call(loginserver, "lua", "login", args)
+  if succ then
+    if access_info then
+      if account then
+        args.account = account
+      end
+      -- send(fd, "get_access_token", {msg = access_info})
+    end
+    waiter.add(fd)
+  else
+    -- Failed to login
+    session.response({errno=STD_ERR.PLYAER_ERR_SIGN or 2}) -- 身份验证失败
+  end
+end
+
+-- function REQUEST.client_data_errno(session, args, response)
+--   args = args or {}
+--   logger.trace(" 客户端异常日志(watchdog) %s", args.errinfo)
+--   if response then
+--     write(session.fd, response({errno = 0}))
+--   end
+-- end
+
+skynet.info_func(function()
+  return {
+    players = total_player_number,
+    connections = total_session_number,
+    offline_avg_number = offline_avg_number
+  }
+end)
+
+skynet.init(function()
+  skynet.register(".ws_watchdog")
+end)
+
+skynet.start(function()
+  logger.label("<ws_watchdog>,")
+	skynet.dispatch("lua", function(session, source, cmd, subcmd, ...)
+    if cmd == "socket" then
+			local f = SOCKET[subcmd]
+			f(...)
+			-- socket api don't need return
+		else
+			local f = assert(CMD[cmd])
+      if session == 0 then
+        f(subcmd, ...)
+      else
+        skynet_retpack(f(subcmd, ...))
+      end
+		end
+	end)
+	gate = skynet.newservice("ws_gate")
+end)

+ 82 - 0
project/webpage/check_role.lua

@@ -0,0 +1,82 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local usercenter = skynet.localname(".usercenter")
+local ws_watchdog = skynet.localname(".ws_watchdog")
+local cjson = require "cjson"
+local md5 = require "md5"
+local redisdriver = require "skynet.db.redis"
+local loader = require "model.loader"
+
+
+local skynet_send = skynet.send
+local skynet_call = skynet.call
+
+local status = 0
+local db_character
+
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+local whitelist = {
+    ["192.168.1.23"] = true,
+    ["192.168.1.127"] = true,
+    ["192.168.1.41"] = true,
+    ["14.29.136.211"] = true,
+    ["222.212.88.4"] = true,
+}
+
+local function initialize()
+    status = 1
+    local conf = assert(option.redis)
+    db_character = redisdriver.connect(conf)
+    db_character:select(0)
+  end
+
+--请求方式:	http://服务器ip地址:端口号/notice?code=xxx&ntype=1&interval=10&content=想要发送的跑马灯内容
+--示例:		http://192.168.1.102:8002/notice?code=xxx&ntype=1&interval=10&content=this%20is%20a%20test
+local function check_server(args, ipaddr, header)
+    if not whitelist[ipaddr] then
+        -- return { "403 - Forbidden" }
+        -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+    end
+    -- 验证gm账号
+    -- local code = string.sub(args.code, 1, 16)
+    -- if code ~= content.sign then
+    --     return cjson.encode({state = 403, msg = "账号或密码错误"})
+    -- end
+    logger.trace("处理来自主机 %s 的获取玩家数据请求", ipaddr)
+
+    if status == 0 then
+      initialize()
+    end
+
+    local sid = args.sid
+    local account = args.account
+    local channel = args.channel or ""
+ -- 渠道唯一标识
+    local k = string.format("account:%s:%s:%s", channel, sid, account)
+
+    local uid = db_character:get(k)
+    if uid then
+        local character = loader(uid)
+        local ret = cjson.encode({
+            state = 0, 
+            create = true,
+            name = character.nickname,
+            level = character.level,
+            uid = character.uid,
+            createtime = os.date("%Y-%m-%d %H:%M:%S", character.createtime)
+        })
+        logger.trace(ret)
+        return ret
+    end
+
+    
+    local ret = cjson.encode({state = 0, create = false})
+    logger.trace(ret)
+    return ret
+end
+
+return check_server

+ 49 - 0
project/webpage/check_server.lua

@@ -0,0 +1,49 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local usercenter = skynet.localname(".usercenter")
+local ws_watchdog = skynet.localname(".ws_watchdog")
+local cjson = require "cjson"
+local md5 = require "md5"
+
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+local whitelist = {
+    ["192.168.1.23"] = true,
+    ["192.168.1.127"] = true,
+    ["192.168.1.41"] = true,
+    ["14.29.136.211"] = true,
+    ["222.212.88.4"] = true,
+}
+
+--请求方式:	http://服务器ip地址:端口号/notice?code=xxx&ntype=1&interval=10&content=想要发送的跑马灯内容
+--示例:		http://192.168.1.102:8002/notice?code=xxx&ntype=1&interval=10&content=this%20is%20a%20test
+local function check_server(args, ipaddr, header)
+    logger.trace("处理来自主机 %s 查询服务器状态", ipaddr)
+    if not whitelist[ipaddr] then
+        -- return { "403 - Forbidden" }
+        -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+    end
+    -- 验证gm账号
+    -- local code = string.sub(args.code, 1, 16)
+    -- if code ~= content.sign then
+    --     return cjson.encode({state = 403, msg = "账号或密码错误"})
+    -- end
+    local sid = args.sid
+    local account = args.account
+    local channel = args.channel or ""
+    local create = skynet.call(usercenter, "lua", "exsits", {
+        sid = sid,
+        account = account,
+        channel = channel,
+    })
+
+    local maintain = skynet.call(ws_watchdog, "lua", "getstatus")
+    local ret = cjson.encode({state = 0, create = create, open = maintain and false or true})
+    logger.trace(ret)
+    return ret
+end
+
+return check_server

+ 540 - 0
project/webpage/delitem.lua

@@ -0,0 +1,540 @@
+--[[
+    扣除玩家的物品
+]]
+
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local config = require "model.asset"
+local queue = require "skynet.queue"
+local cjson = require "cjson"
+local synchronized = queue()
+local namecenter = skynet.localname(".namecenter")
+local temp_config = mt
+local cjson = require "cjson"
+local redisdriver = require "skynet.db.redis"
+local stringify = require "stringify"
+
+local redis
+
+-- local z_trace = require "zlf_log"
+-- local z_trace = z_trace.trace
+
+local authz = {
+    yytx = "lee@YY-Games.520"
+}
+
+local whitelist = {
+  ["127.0.0.1"] = true,
+  ["14.29.136.211"] = true,
+  ["222.212.88.4"] = true,
+}
+
+-- 更具名字添加id
+local function get_id(name)
+    return skynet.call(namecenter,"lua","findby", name)
+end
+
+-- 读取数据
+local function get_data(uid, key)
+    local ret = redis:HMGET(string.format("character:%s", uid), key)
+    if ret[1] then
+        if  key == "coins"                      -- 金币
+            -- or key == "exp"                     -- 角色经验
+            or key == "pkpoint"                 -- 竞技积分
+            or key == "diamonds"                -- 钻石
+            or key == "friendship"              -- 友情点
+            or key == "contribute"              -- 公会贡献
+            or key == "advintegral"             -- 公会冒险积分
+            or key == "recharge_point"          -- 充值积分
+            or key == "research_note"           -- 研究笔记 --PK3-1148
+            or key == "megacoin"                -- mege点
+            or key == "bind_diamonds"           -- 绑定钻石
+            or key == "budo_point"              -- 道馆积分
+            or key == "manual_exp"              -- 训练师手册积分 --PK3-1077
+            or key == "explore"                 -- 探险值
+            or key == "explore_exp"             -- 探险经验
+            or key == "gashapon_num"            -- 扭蛋积分
+            or key == "smeltpoint"              -- 熔炼点
+            or key == "uid"
+            or key == "nickname"
+            or key == "span_elf_king_point"     -- 跨服至尊天王积分 -- CDPK3-86
+            or key == "ruins_point"             -- 古代金币 -- CDPK3-276 宝可梦遗迹
+        then
+            return ret[1]
+        else
+            return skynet.unpack(ret[1])
+        end
+    else
+        return nil
+    end
+end
+
+-- 设置数据
+local function set_data(uid, key, data)
+    if data then
+        if type(data) == "table" then
+            redis:HMSET(string.format("character:%s", uid), key, skynet.packstring(data))
+        else
+            redis:HMSET(string.format("character:%s", uid), key, data)
+        end
+    end
+end
+
+-- 检查玩家是否存在
+local function check_receiver(uid)
+    local ret = get_data(uid, "uid")
+    return ret
+end
+
+local cmd = {}
+local err = {
+  err_info = cjson.encode({state = 403, msg = "没有找到那个字段 3"}),
+  err_than = cjson.encode({state = 403, msg = "扣除的值过大 4"}),
+  err_find = cjson.encode({state = 403, msg = "扣除的sid不存在"}),
+  err_empty = cjson.encode({state = 403,msg = "table里面的数据为空 8"}),
+  err_elf  = cjson.encode({state = 403, msg = "没有找到该精灵 ------ 3"}),
+  err_set  = cjson.encode({state = 403, msg = "精灵使用中"}),
+}
+
+function cmd.currency(key, value, uid)
+    local info = get_data(uid, key)
+    if not info then return err.err_info end
+    info = tonumber(info)
+    logger.trace(" key = %s, value = %s", key, value)
+    if value > info then return err.err_than end
+    info = info - value
+    set_data(uid, key, info)
+end
+
+function cmd.pokemon_exp(key, value, uid)
+    local info = get_data(uid, "elfdata")
+    if not info then return err.err_info end
+
+    if value > info.experience then  return err.err_than end
+    info.experience = info.experience - value
+    set_data(uid, "elfdata", info)
+end
+
+function cmd.item(sid, num, uid)
+  local info = get_data(uid, "bagdata")
+  if not info then return err.err_info end
+
+  if info.item[sid] then
+    local value = (info.item[sid].num or 0) - num
+    if value == 0 then
+        info.item[sid] = nil
+        info.num = info.num - 1       -- 被占用格子的数量
+    elseif value > 0 then
+        info.item[sid].num = value
+    else
+        return err.err_than
+    end
+  else
+    return err.err_find
+  end
+  -- 保存数据
+  set_data(uid, "bagdata", info)
+end
+
+-- TODO: 扣除符文碎片
+function cmd.rune(sid, num, uid)
+  local info = get_data(uid, "elf_rune")
+  if not info then return err.err_info end
+
+  -- 统计数量
+  local del = {}
+  for k, v in pairs(info.rune_bag) do
+    if v.sid == sid then
+      table.insert(del, k)
+    end
+  end
+
+  if #del == num then
+    for _, id in pairs(del) do
+      info.rune_bag[id] = nil
+      info.rune_bagnum = info.rune_bagnum or 1
+      info.rune_bagnum = info.rune_bagnum - 1
+    end
+    set_data(uid, "elf_rune", info)
+  else
+    return err.err_than
+  end
+end
+-- TODO: 扣除携带品--CDPK3-271
+function cmd.carry(sid, num, uid)
+  local info = get_data(uid, "elf_carry_items")
+  if not info then return err.err_info end
+
+  -- 统计数量
+  local del = {}
+  for k, v in pairs(info.carry_items_bag) do
+    if v.sid == sid then
+      table.insert(del, k)
+    end
+  end
+
+  if #del == num then
+    for _, id in pairs(del) do
+      info.carry_items_bag[id] = nil
+      info.carry_items_num = info.carry_items_num or 1
+      info.carry_items_num = info.carry_items_num - 1
+    end
+    set_data(uid, "elf_carry_items", info)
+  else
+    return err.err_than
+  end
+end
+
+-- TODO: 扣除代金券  CDPK3-1002
+function cmd.coupon(cid, sid, uid)    -- cid:配置id sid:唯一id
+  local info = get_data(uid, "coupon")
+  if not info then return err.err_info end
+  logger.trace("################ sid:%s, cid:%s", sid, cid)
+  if info.bag[sid] and info.bag[sid].cid == cid then
+    info.bag[sid] = nil
+    set_data(uid, "coupon", info)
+  else
+    return err.err_than
+  end
+end
+
+function cmd.fashion(id, num, uid)
+  local info = get_data(uid, "dress")
+  if not info then return err.err_info end
+
+  --[[
+  if info.owned[id] then
+    if id == info.carry then
+      info.carry = nil
+    end
+    info.owned[id] = nil
+    set_data(uid, "dress", info)
+  end
+  ]]
+  if info.owned[id] then
+    if id == info.carry then
+      info.carry = nil
+    end
+    -- info.owned[id] = nil
+    if info.owned[id][1] > num then
+      info.owned[id][1] = info.owned[id][1] - num
+    elseif info.owned[id][1] == num then
+      -- 已经进阶过得时装必须保留一件
+      if info.owned[id][2] > 0 then
+        return err.err_than
+      end
+      info.owned[id] = nil
+    else
+      return err.err_than
+    end
+    set_data(uid, "dress", info)
+  end
+end
+
+function cmd.title(id, _, uid) --PK3-1030
+  local info = get_data(uid, "title")
+  if not info then return err.err_info end
+  if info.titlelist[id] then
+    if id == info.settitle then
+      info.settitle = nil
+    end
+    info.titlelist[id] = nil
+    set_data(uid, "title", info)
+  end
+end
+
+function cmd.debris(sid, num, uid)
+  local info = get_data(uid, "oncedata")
+  if not info then return err.err_info end
+
+  if info.debris and info.debris[sid] and info.debris[sid].num and info.debris[sid].num > 0 then
+    local snum = info.debris[sid].num - num
+    if snum == 0 then
+      info.debris[sid] = nil
+    elseif snum > 0 then
+      info.debris[sid].num = snum
+    else
+        return err.err_than
+    end
+    set_data(uid, "oncedata", info)
+  else
+    return err.err_than
+  end
+end
+
+function cmd.rune_debris(sid, num , uid)
+  local info = get_data(uid, "rune_debris")
+  if not info then return err.err_info end
+
+  local rnum = 0
+  if info.runedebris and info.runedebris[sid] and info.runedebris[sid].num and info.runedebris[sid].num > 0 then
+    rnum = info.runedebris[sid].num
+  end
+  if rnum == 0 then
+    return err.err_than
+  end
+
+  local value = rnum - num
+  if value == 0 then
+    info.runedebris[sid] = nil
+  elseif value > 0 then
+    info.runedebris[sid].num = value
+  else
+    return err.err_than
+  end
+
+  set_data(uid, "rune_debris", info)
+end
+
+function cmd.carry_debris(sid, num , uid)
+  local info = get_data(uid, "carry_debris")
+  if not info then return err.err_info end
+
+  local rnum = 0
+  if info.carrydebris and info.carrydebris[sid] and info.carrydebris[sid].num and info.carrydebris[sid].num > 0 then
+    rnum = info.carrydebris[sid].num
+  end
+  if rnum == 0 then
+    return err.err_than
+  end
+
+  local value = rnum - num
+  if value == 0 then
+    info.carrydebris[sid] = nil
+  elseif value > 0 then
+    info.carrydebris[sid].num = value
+  else
+    return err.err_than
+  end
+
+  set_data(uid, "carry_debris", info)
+end
+
+
+-- elf_awaken={[1]={sid = sid, lv = 0}}},    -- 觉醒降级  sid 精灵的模板id lv 觉醒的等级
+function cmd.elf_awaken(key, value, uid)
+    if not next(value) then return err.err_empty end
+    local info = get_data(uid, "elf_awaken")
+    if not info then return err.err_info end
+    local elfdata = get_data(uid, "elfdata")
+
+    local function add(individual)
+        local he = 0
+        for _, v in pairs(individual) do
+            he = he + v
+        end
+        return he
+    end
+
+    for _, elf in pairs(value) do
+        if (not elf.individual) or elf.individual <= 0 then
+            return cjson.encode({errno = 400, host = header.host, info = "精灵的个体值未空或0  ------- 1"})
+        end
+        local sign = true
+        for id, elf_info in pairs(elfdata.elflist) do
+            if elf_info.sid == elf.sid then
+                local he = add(elf_info.individual)
+                -- 精灵定位
+                if he == elf.individual then
+                    -- 查找修改觉醒数据
+                    for k, v in pairs(info.awaken_list) do
+                        if v.sid == elf.sid and v.lv == elf.lv then
+                            if info.awaken_list[k].lv > 0 then
+                                -- d.awaken_list[k].lv = d.awaken_list[k].lv - 1
+                                info.awaken_list[k].lv = info.awaken_list[k].lv - 1
+                            else
+                                info.awaken_list[k] = nil
+                            end
+                            sign = false
+                            break
+                        end
+                    end
+                    -- break
+                end
+            end
+        end
+        if sign then return err.err_elf end
+    end
+    set_data(uid, "elf_awaken", info)
+end
+
+-- TODO: 扣除精灵
+function cmd.pokemon(e_sid, e_id, uid)
+  local info = get_data(uid, "elfdata")
+  if not info then return err.err_info end
+
+  local state_info = get_data(uid, "elf_state")
+  if not state_info then return err.err_info end
+
+  local function check_elf(id, state_info)
+    if state_info.elf_employ[id] then
+      return true
+    end
+  end
+
+  local detail = {}
+  if info.elflist[e_id] and info.elflist[e_id].sid == e_sid then
+    if check_elf(e_id, state_info) then
+      return err.err_set      -- 精灵使用中
+    end
+    info.elfcount.bagnum = info.elfcount.bagnum - 1
+    detail = info.elflist[e_id]
+    info.elflist[e_id] = nil
+    set_data(uid, "elfdata", info)
+  else
+    return err.err_elf
+  end
+  return nil, detail
+end
+
+-- 类型邮件系统
+-- http://服务器ip地址:端口号/delitem?code="xxx"&uid="00-xx"&json=移除的道具or代币列表
+-- http://192.168.108.19:9002/delitem?code="xxx"&uid="00-xx"&json={"items":[[104,0,10000000],[102,0,3000],[1,20014,100]]}
+--[[ -- PK3-1366 数据统计
+local log = skynet.localname('.log')
+local function record(character, opcode, context)
+  assert(character)
+  assert(opcode)
+  local bytes = cjson.encode({
+    opcode=opcode,
+    uid=character.uid,
+    nickname=character.nickname,
+    context=context,
+    date=os.time()
+  })
+  skynet.send(log, "lua", "record", character.uid, bytes)
+end
+]]
+local log = require "model.log" -- PK3-1366 数据统计
+local function fire(character, module, event, specific)
+    assert(module)
+    assert(event)
+    assert(type(module) == "string")
+    assert(type(event) == "string")
+    --[[
+    record(character, "event", {
+      module=module,
+      type=event,
+      specific=specific
+    })
+    ]]	-- PK3-1366 数据统计
+    logger.trace(" ### specific %s", stringify(specific))
+    log.fire(character,module,event, specific)
+  end
+
+local delitem = function(args, ipaddr,header)
+    return synchronized(function()
+        logger.trace("处理来自主机 %s 的 扣除 请求", ipaddr)
+
+        -- 验证主机id
+        if not whitelist[ipaddr] then
+          -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+        end
+        -- 验证gm账号
+        local code = string.sub(args.code, 1, 16)
+        -- if code ~= content.sign then
+        --     return cjson.encode({state = 403, msg = "账号或密码错误"})
+        -- end
+
+        -- 连接数据库
+        local conf = assert(option.redis)
+        redis = redisdriver.connect(conf)
+        redis:select(0)
+
+        -- 目标玩家的处理过程: 验证玩家uid是否存在
+        logger.trace(" args = %s", stringify(args))
+        --[[local uid = check_receiver(args.uid)
+        if not uid then
+          redis:disconnect()
+          return cjson.encode({state = 403, msg = "没有找到该玩家 2 "})
+        end]]
+        local uid
+        if args.name then
+          uid =get_id(args.name)
+        elseif args.uid then
+          uid=args.uid
+        elseif (not args.uid) and (not args.name) then
+          return cjson.encode({state = 403,msg= "请输入玩家uid或者玩家name"})
+        end
+        uid=check_receiver(uid)
+        if not uid then
+          redis:disconnect()
+          return cjson.encode({state = 403, msg = "没有找到该玩家 2 "})
+        end
+
+        local ok, delinfo = pcall(cjson.decode, args.json)
+        logger.trace(" delinfo = %s", stringify(delinfo))
+        if not ok then
+          redis:disconnect()
+          return cjson.encode({state = 403, msg = "内容错误"})
+        end
+
+        -- 循环处理需要处理得数据
+        local entity = {}
+        local key, value, ret
+        for _, v in ipairs(delinfo.items) do
+          if v[1] == GOODS_MONEY then      -- 货币 精灵经验单独处理
+            key = MONEY_TYPE[v[2]]        -- 货币字段名
+            value = v[3]                  -- 需要扣除得值
+            if key == "exp1" then
+              ret = cmd.pokemon_exp(key, value, uid)
+            else
+              ret = cmd.currency(key, value, uid)
+            end
+            entity[key] = value
+          elseif v[1] == GOODS_ITEM then       -- 道具
+            ret = cmd.item(v[2], v[3], uid)           -- 道具id, 数量
+            entity["item"] = {[v[2]] = v[3] }
+          elseif v[1] == TYPE_ELF then        -- 精灵
+            local detail
+            ret,detail = cmd.pokemon(v[2], v[3], uid) -- 精灵模板, 精灵唯一id
+            entity["pokemon"] = {[v[2]] = detail }
+          elseif v[1] == TYPE_FASHION then    -- 时装
+            ret = cmd.fashion(v[2], v[3], uid)        -- 时装id
+            entity["fashion"] = {v[2]}
+          elseif v[1] == TYPE_DEBRIS then     -- 碎片
+            ret = cmd.debris(v[2], v[3], uid)         -- 碎片id, 数量
+            entity["debris"] = {[v[2]] = v[3]}
+          elseif v[1] == TYPE_RUNE then       -- 符文
+            ret = cmd.rune(v[2], v[3], uid)           -- 符文id, 数量
+            entity["rune"] = {v[2]}
+          elseif v[1] == TYPE_RUNEDEBRIS then -- 符文碎片
+            ret = cmd.rune_debris(v[2], v[3], uid)    -- 碎片id, 数量
+            entity["rune_debris"] = {[v[2]] = v[3]}
+          elseif v[1] == TYPE_TITLE then    -- 称号 --PK3-1030
+            ret = cmd.title(v[2], v[3], uid)        -- 称号
+            entity["title"] = {v[2]}
+          elseif v[1] == TYPE_CARRY then       -- 符文
+            ret = cmd.carry(v[2], v[3], uid)           -- 符文id, 数量
+            entity["carry"] = {v[2]}
+          elseif v[1] == TYPE_CARRYDEBRIS then -- 符文碎片
+            ret = cmd.carry_debris(v[2], v[3], uid)    -- 碎片id, 数量
+            entity["carry_debris"] = {[v[2]] = v[3]}
+          elseif v[1] == TYPE_COUPON then       -- CDPK3-1002 代金券
+            ret = cmd.coupon(v[2], v[3], uid)           -- 符文id, 数量
+            entity["coupon"] = {v[2]}
+          else
+            return cjson.encode({state = 403, msg = "没有需要修改的数据 9"})
+          end
+        end
+        if ret then
+          -- 数据库断开
+          redis:disconnect()
+          return ret
+        else
+          logger.trace(" entity = %s" , stringify(entity))
+          entity.other = delinfo.items    -- PK3-1366 数据统计
+          entity.note = "web_delitem"
+          fire({
+            uid = uid,
+            nickname = get_data(uid, "nickname")
+          }, "delitem", "delitem", entity)
+          redis:disconnect()
+          return cjson.encode({state = 200, msg = "success"})
+        end
+    end)
+end
+
+
+return delitem

+ 146 - 0
project/webpage/exchange_account.lua

@@ -0,0 +1,146 @@
+-- 映射账号(老帐号映射到新账号上)
+local skynet = require "skynet"
+require "skynet.manager"
+local redisdriver = require "skynet.db.redis"
+local logger = require "logger"
+local queue = require "skynet.queue"
+local cjson = require "cjson"
+local stringify = require "stringify"
+local synchronized = queue()
+local namecenter
+local usercenter
+local skynet_send = skynet.send
+local skynet_call = skynet.call
+local md5 = require "md5"
+
+local THIS = {}
+
+local state = 0
+local redis
+local whitelist = {
+}
+
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+
+local function initialize()
+    state = 1
+    local conf = assert(option.redis)
+    redis = redisdriver.connect(conf)
+    redis:select(0)
+    namecenter = skynet.localname(".namecenter")
+    usercenter = skynet.localname(".usercenter")
+end
+
+-- http://192.168.108.10:9002/exchange_account?code=xxx&type=1&json={"attach":["00-6658977989381873664","00-6659002777164865536"]}
+local account = function(args, ipaddr, header)
+    return synchronized(function()
+        logger.trace("处理来自主机 %s 的GM管理账号映射请求",ipaddr)
+
+        if state == 0 then initialize() end
+
+        --验证gm账号
+        local code = string.sub(args.code, 1, 16)
+        if code ~= content.sign then
+            return cjson.encode({state = 403, msg = "账号或密码错误"})
+        end
+
+        if not whitelist[ipaddr] then
+            -- return { code=403, msg="Forbidden" }
+            -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+        end
+
+
+        local json = cjson.decode(args.json)
+        local attach = json.attach or {}
+        local type = args.type
+        local new,old = attach[1],attach[2]
+        local o_string = string.format("character:%s",old)
+        local n_string = string.format("character:%s",new)
+
+        logger.trace(" %s %s %s",type,new,old)
+
+
+
+        function THIS.account_ys()
+            local o_num = redis:HGET(o_string,'sid')
+            local n_num = redis:HGET(n_string,'sid')
+            logger.trace("#### ,account_ys   %s  %s",o_num,n_num)
+
+            if o_num ~= n_num then
+                logger.trace("#### 账号区服不一致,禁止映射")
+                local context = "账号区服不一致,禁止映射"
+                return -1,context
+            end
+
+            local o_acc =  redis:HGET(o_string,'account')
+            local n_acc =  redis:HGET(n_string,'account')
+
+            local migration_account_key = "migration_account:%s:%s" -- 区服+新账号
+
+            local acco_string = string.format(migration_account_key,o_num,o_acc)
+            local ret = redis:HGET(acco_string,'account')
+            if ret then
+                logger.trace("#### 新账号已有映射账号 不可再次映射")
+                local context = "新账号已有映射账号 不可再次映射"
+                return -1,context
+            end
+
+            local accn_string = string.format(migration_account_key,n_num,n_acc)
+            ret = redis:HGET(accn_string,'account')
+            if ret and ret ~= n_acc then
+                logger.trace("#### 老账号已有映射账号 不可再次映射")
+                local context = "老账号已有映射账号 不可再次映射"
+                return -1,context
+            end
+            local err = redis:HSET(accn_string,'account',o_acc)
+            return err
+        end
+
+        --取消绑定
+        function THIS.account_qx()
+
+            local n_num = redis:HGET(n_string,'sid')
+            local n_acc =  redis:HGET(n_string,'account')
+            local migration_account_key = "migration_account:%s:%s" -- 区服+新账号
+            logger.trace("#### ,account_qx   server:%s  account:%s ",n_num,n_acc )
+
+            local accn_string = string.format(migration_account_key,n_num,n_acc)
+            local ret = redis:HGET(accn_string,'account')
+            if not ret then
+                logger.trace("#### 账号没有映射账号 不可取消")
+                local context = "账号没有映射账号 不可取消"
+                return -1,context
+            end
+            local num = redis:HLEN(accn_string)
+            num = (num or 0) + 1
+            local add_string = 'account' ..num
+            redis:HSET(accn_string,add_string,ret)
+            redis:HSET(accn_string,'account',n_acc)
+            return 0
+        end
+
+
+        if tonumber(type) == 1 then
+            local erron,context = THIS.account_ys()  --映射
+            if erron == 1 then
+                return cjson.encode({state=0,msg="success"})
+            else
+                return cjson.encode({state=-1,msg = context})
+            end
+        elseif tonumber(type) == 2 then
+            local erron,context = THIS.account_qx()  --取消
+            if erron == 0 then
+                return cjson.encode({state=0,msg="success"})
+            else
+                return cjson.encode({state=-1,msg=context})
+            end
+        end
+    end)
+end
+
+return account
+

+ 128 - 0
project/webpage/field_inquire.lua

@@ -0,0 +1,128 @@
+--[[
+    查询玩家指定的key值数据
+]] local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local stringify = require "stringify"
+local usercenter = skynet.localname(".usercenter")
+local logind = skynet.localname(".loginserver")
+local redisdriver = require "skynet.db.redis"
+local namecenter = skynet.localname(".namecenter")
+
+local cjson = require "cjson"
+local md5 = require "md5"
+
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+-- Generate request data
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+local whitelist = {
+    ["192.168.1.23"] = true,
+    ["192.168.1.127"] = true,
+    ["192.168.1.41"] = true,
+    ["222.212.88.4"] = true,
+}
+
+--[[
+    byname = zlf        -- 玩家的名字
+]]
+local function get_id(name) return
+    skynet.call(namecenter, "lua", "findby", name) end
+
+local function get_data(redis, uid, key)
+    local ret = redis:HMGET(string.format("character:%s", uid), key)
+    if ret[1] then
+        if key == "coins" -- 金币
+        or key == "exp"                     -- 角色经验
+        or key == "pkpoint" -- 竞技积分
+        or key == "diamonds" -- 钻石
+        or key == "friendship" -- 友情点
+        or key == "contribute" -- 公会贡献
+        or key == "advintegral" -- 公会冒险积分
+        or key == "recharge_point" -- 充值积分
+        or key == "research_note" -- 研究笔记 --PK3-1148
+        or key == "megacoin" -- mege点
+        or key == "bind_diamonds" -- 绑定钻石
+        or key == "budo_point" -- 道馆积分
+        or key == "manual_exp" -- 训练师手册积分 --PK3-1077
+        or key == "explore" -- 探险值
+        or key == "explore_exp" -- 探险经验
+        or key == "gashapon_num" -- 扭蛋积分
+        or key == "smeltpoint" -- 熔炼点
+        or key =="mission"     --当前关卡
+        or key =="sex"           --性别
+        or key =="power"
+        or key =="account"
+        or key =="sid"
+        or key =="lastlogout"
+        or key =="lastlogin"
+        or key =="level"
+        or key =="channel"
+        or key =="forbidden"
+        or key =="createtime"       --创号时间
+        or key =="appid"
+        or key =="platform"
+        or key == "span_elf_king_point" -- 跨服至尊天王积分 -- CDPK3-86
+        or key == "ruins_point"         -- 古代金币 -- CDPK3-276 宝可梦遗迹
+	or key == "tourist"
+        or key == "google"
+        or key == "facebook"
+        or key == "uid" or key == "nickname" then
+            return ret[1]
+        else
+            return skynet.unpack(ret[1])
+        end
+    else
+        return nil
+    end
+end
+
+-- example:
+--[[
+    http://192.168.1.51:9001/field_inquire?code=xxx&uid=00-6395766577223962624&key=elfdata
+]]
+local function field_inquire(args, ipaddr, header)
+    logger.trace("处理来自主机 %s 的 获取 玩家指定key--%s的数据", ipaddr,args.key)
+    if not whitelist[ipaddr] then
+        -- return { "403 - Forbidden" }
+        -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+    end
+
+    -- 验证gm账号
+    local code = string.sub(args.code, 1, 16)
+    if code ~= content.sign then
+        return cjson.encode({state = 403, msg = "账号或密码错误"})
+    end
+
+    local conf = assert(option.redis)
+    local redis
+    redis = redisdriver.connect(conf)
+    redis:select(0)
+
+    local uid
+    if args.name then
+        uid = get_id(args.name)
+    elseif args.uid then
+        uid = args.uid
+    elseif (not args.uid) and (not args.name) then
+        return cjson.encode({state = 403,msg = "请输入玩家uid或者玩家name"})
+    end
+
+    local key = args.key or nil
+    if not key then
+        return cjson.encode({state = 403, msg = "请输入key值"})
+    end
+
+    local ret_info = {}
+
+    ret_info[key] = get_data(redis, uid, key)
+    if not ret_info[key] then
+        return cjson.encode({state = 403, msg = "错误的key值"})
+    end
+
+    redis:disconnect()
+    return cjson.encode({state = 0, msg = "success", data = ret_info})
+end
+
+return field_inquire

+ 154 - 0
project/webpage/forbit.lua

@@ -0,0 +1,154 @@
+-- GM管理对玩家封号和 禁言
+local skynet = require "skynet"
+require "skynet.manager"
+local redisdriver = require "skynet.db.redis"
+local logger = require "logger"
+local queue = require "skynet.queue"
+local cjson = require "cjson"
+local stringify = require "stringify"
+local synchronized = queue()
+local namecenter
+local usercenter
+local rankinglist
+local skynet_send = skynet.send
+local skynet_call = skynet.call
+local md5 = require "md5"
+
+local state = 0
+local db_character
+
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+-- Generate request data
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+local whitelist = {
+    ["192.168.1.23"] = true,
+    ["192.168.1.127"] = true,
+    ["192.168.1.50"] = true,
+    ["192.168.1.41"] = true,
+    ["14.29.136.211"] = true,
+    ["222.212.88.4"] = true,
+}
+
+-- 根据名字得到玩家uid
+local function get_id(name) return
+    skynet.call(namecenter, "lua", "findby", name)
+end
+
+local function initialize()
+    state = 1
+    local conf = assert(option.redis)
+    db_character = redisdriver.connect(conf)
+    db_character:select(0)
+    namecenter = skynet.localname(".namecenter")
+    usercenter = skynet.localname(".usercenter")
+    rankinglist = skynet.localname(".rankinglist")
+end
+
+local forbit = function(args, ipaddr, header)
+    return synchronized(function()
+        logger.trace("处理来自主机 %s 的GM管理封号禁言请求",
+                     ipaddr)
+
+        if state == 0 then initialize() end
+
+        if not whitelist[ipaddr] then
+            -- return { code=403, msg="Forbidden" }
+            -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+        end
+
+        -- 验证gm账号
+        -- local code = string.sub(args.code, 1, 16)
+        -- if code ~= content.sign then
+        --     return cjson.encode({state = 403, msg = "账号或密码错误"})
+        -- end
+        local ntype=tonumber(args.ntype)
+        local entity=cjson.decode(args.json)
+        local uid={}
+        local succ1={}
+        local failed1={}
+        if entity.names then
+            for k,v in pairs(entity.names) do
+                local v1=get_id(v)
+                table.insert(uid, v1)
+            end
+        elseif entity.uids then
+          uid=entity.uids
+        elseif (not entity.uids) and (not entity.names) then
+          return cjson.encode({state = 403,msg= "请输入玩家uid或者玩家name"})
+        end
+        local times = tonumber(entity.times) or 0
+
+        if ntype==3 or ntype==1 then
+            if not times or times < 0 then
+                return cjson.encode({state = 400, msg = "wrong times"})
+            end
+        end
+        if ntype==4 or ntype==2 then
+            times=0
+        end
+
+        local now = os.time()
+        local forbittime = now + times
+
+        for key,value in pairs(uid) do
+            local k = string.format("character:%s",value)
+            if true == db_character:exists(k) then
+                -- local forbitstr = "GM工具对玩家:"    -- CDPK3-172
+                local forbitstr_mode = "%s,%s,%s"
+                local timeas = os.date("%Y.%m.%d %X", forbittime)
+                if ntype == 6 then
+                    skynet_send(usercenter, "lua", "agent_command", value, "ban_rank", 0)
+                    db_character:hset(k, "ban_rank", 0)
+                elseif ntype == 5 then
+                        skynet_send(usercenter, "lua", "agent_command", value, "ban_rank", 1)
+                        db_character:hset(k, "ban_rank", 1)
+                        skynet_send(rankinglist, "lua", "del", value)
+                elseif ntype == 4 then
+                    -- forbitstr = forbitstr .. value .. " 解除禁言--" .. timeas  -- CDPK3-172
+                    local forbitstr = string.format(forbitstr_mode, STD_ERR.PLAYER_FORBIT_4,value,timeas)
+                    skynet_send(usercenter, "lua", "agent_command", value, "setsilent",forbitstr, forbittime)
+                    db_character:hset(k, "silent", forbittime)
+                elseif ntype==3 then
+                    -- forbitstr = forbitstr .. value .. " 禁言到" .. timeas    -- CDPK3-172
+                    local forbitstr = string.format(forbitstr_mode, STD_ERR.PLAYER_FORBIT_3,value,timeas)
+                    skynet_send(usercenter, "lua", "agent_command", value, "setsilent",forbitstr, forbittime)
+                    db_character:hset(k, "silent", forbittime)
+                elseif ntype==2 then
+                    -- forbitstr = forbitstr .. value .. " 解除封号--" .. timeas    -- CDPK3-172
+                    local forbitstr = string.format(forbitstr_mode, STD_ERR.PLAYER_FORBIT_2,value,timeas)
+                    --logger.error(forbiddenstr)
+                    db_character:hset(k,"forbidden",forbittime)
+                elseif ntype==1 then
+                    -- forbitstr = forbitstr .. value .. " 封号到" .. timeas    -- CDPK3-172
+                    local forbitstr = string.format(forbitstr_mode, STD_ERR.PLAYER_FORBIT_1,value,timeas)
+                    skynet_call(usercenter,"lua","agent_command",value,"kick",forbitstr)
+                    db_character:hset(k,"forbidden",forbittime)
+                end
+                table.insert(succ1, value)
+            else
+                table.insert(failed1, value)
+                --return cjson.encode({state = 400, msg = "without the player"})
+            end
+        end
+
+        local pack={}
+        pack.succ=succ1
+        pack.failed=failed1
+        if ntype ==6 then
+            return cjson.encode({state = 0, msg = "排行解禁止success",content=pack})
+        elseif ntype==5 then
+            return cjson.encode({state = 0, msg = "排行封禁success",content=pack})
+        elseif ntype==3 then
+            return cjson.encode({state = 0, msg = "禁言success",content=pack})
+        elseif ntype==4 then
+            return cjson.encode({state = 0, msg = "解除禁言success",content=pack})
+        elseif ntype==2 then
+            return cjson.encode({state = 0, msg = "解除封号success",content=pack})
+        elseif ntype==1 then
+            return cjson.encode({state = 0, msg = "封号success",content=pack})
+        end
+    end)
+end
+return forbit

+ 108 - 0
project/webpage/get_server_uid.lua

@@ -0,0 +1,108 @@
+--[[
+    luaide  模板位置位于 Template/FunTemplate/NewFileTemplate.lua 其中 Template 为配置路径 与luaide.luaTemplatesDir
+    luaide.luaTemplatesDir 配置 https://www.showdoc.cc/web/#/luaide?page_id=713062580213505
+    author:{zlf}
+    time:2019-10-21 16:33:17
+    jira: PK3-987
+    note: 确定玩家在本服务器是否有角色
+]]
+--查询是否建号
+local logger = require "logger"
+local redisdriver = require "skynet.db.redis"
+local stringify = require "stringify"
+local skynet = require "skynet"
+local queue = require "skynet.queue"
+local synchronized = queue()
+
+local trace = logger.trace
+--local trace = function(...) end
+
+local redis
+local redis_recharge
+local start
+local rechargeser
+local REBATE = "rebate:"
+local KEYGEN_PAY = "%s:%s"
+
+local authz = {
+    lee = "yytx-Games-9527"
+}
+
+local function start_pay()
+  local conf = assert(option.redis)
+  redis = redisdriver.connect(conf)
+  redis_recharge = redisdriver.connect(conf)
+  redis:select(0)--切换到数据库db1
+  redis_recharge:select(3)--切换到数据库db1
+
+  rechargeser = rechargeser or skynet.localname(".recharge")
+  return 1
+end
+
+local facebook = nil -- 云账号登陆废弃
+local function retrieve_facebook_account(account, sid, channel)
+  facebook = facebook or skynet.localname(".facebook")
+
+  local errno, fb = skynet.call(facebook, "lua", "retrieve", account, channel)
+  local uid = nil
+  if errno == 200 then
+    uid = redis:get(string.format("account:%s:%s:%s", channel, sid, fb))
+  end
+  return errno, uid
+end
+
+--192.168.1.44:9001/get_server_uid?uid=9999&&channel=185sy&account=1232123
+local get_server_uid = function(args, ipaddr)
+  return synchronized(function()
+    start = start or start_pay()
+    trace("处理来自主机 %s 的uid检测请求", ipaddr)
+
+    local channel = args.channel
+    local account = args.account
+    local serverid = tonumber(args.serverid)
+    trace("开始处理uid检测信息:%s",stringify(args))
+
+    -- 验证gm账号
+    local user = args.user or ""
+    local pwd = args.pwd or ""
+    if authz[user] ~= pwd then
+      return { code=2 }
+    end
+
+    if not account or not channel then
+      return { code=1 }--数据不足
+    end
+
+    local uid
+    if account then
+      local k = string.format("account:%s:%s:%s", channel, serverid, account)
+      uid = redis:get(k)
+    end
+    if not uid then -- 云账号登陆废弃
+      local errno, val = retrieve_facebook_account(account, serverid, channel)
+      if errno == 200 then
+        uid = val
+      end
+    end
+    if not uid then
+      trace("验证玩家建号数据错误:setuid:%s, uid:%s",setuid, uid)
+      return { code=3 }--未在该服务器创建玩家
+    end
+
+    local name
+    local level
+    if uid then
+      local k = string.format("character:%s", uid)
+      if redis:hget(k,"sex") then
+        name = redis:hget(k,"nickname")
+        level = redis:hget(k,"level")
+      else
+        return { code=3 }--未在该服务器创建玩家
+      end
+    end
+
+    return { code=0,uid=uid,name=name,level=level }
+  end)
+end
+
+return get_server_uid

+ 269 - 0
project/webpage/gm_test.lua

@@ -0,0 +1,269 @@
+--[[
+  GM 系统:
+    系统使用说明, \asset\design\GM.md 文本说明
+]]
+local skynet = require "skynet"
+local codecache = require "skynet.codecache"
+require "skynet.manager"
+local logger = require "logger"
+local stringify = require "stringify"
+local cjson = require "cjson"
+local common_fun = require "model.common_fun"
+local module_fun = require "model.module_fun"
+local asset = require "model.asset"
+local util = require "util"
+local usercenter
+
+local function create_html_page(text)
+    return string.format(
+        "<!DOCTYPE html><html><head><meta charset='UTF-8'></head> <body>%s</body> </html>",
+        string.gsub(text, "[\n]", "<br/>"))
+end
+  
+local function response(header, content)
+    header = header or {}
+    if header['pokemon-version'] then
+        return { content }
+    else
+        return create_html_page(content)
+    end
+end
+
+local whitelist = {
+}
+
+local CMD = {}
+
+function CMD.gm_check_award(args)
+    if type(args) ~= "table" then
+        return "失败"
+    end
+    local errno, list = common_fun.get_award(args[1])
+    if errno ~= 0 then
+        return errno
+    end
+    return stringify(list)
+end
+
+function CMD.gm_check_award_all(args)
+    if type(args) ~= "table" then
+        return false, "失败"
+    end
+    for k in pairs(asset.AwardConfig_proto) do
+        for i = 1, 10000 do
+            local errno, list = common_fun.get_award(k)
+            if errno ~= 0 then
+                return "配置异常:"..k
+            end
+            if not next(list or {}) then
+                return "奖励为空:"..k
+            end
+            errno = module_fun.reward_check(list)
+            if errno ~= 0 then
+                return string.format("配置异常:%d, errno:%d", k, errno)
+            end
+        end
+    end
+    return false, "成功"
+end
+
+function CMD.gm_draw(args)
+    local function draw(data, conf_list, num, special_list)
+        local award_list = {}
+        local id_list = {}
+        local times = num
+        data.all = data.all or 0
+        data.daily = data.daily or 0
+        data.list = data.list or {}
+
+        while(times > 0) do
+            local awardid = 0
+            local awardtimes = 0
+            data.all = data.all + 1
+            data.daily = data.daily + 1
+            -- 先随特殊库, 看下特殊库的必出是否达成
+            for _, conf in ipairs(special_list or {}) do
+                if conf.must > 0 then
+                    local id = conf.ID
+                    local list = data.list
+                    if not data.list[id] then
+                        list[id] = {
+                            score = 0,
+                            times = 0
+                        }
+                    end
+                    local cur_score = data.all - list[id].score
+                    if cur_score >= conf.must then
+                        list[id].score = data.all
+                        list[id].times = list[id].times + 1
+                        awardid = conf.points
+                        awardtimes = list[id].times
+                        break
+                    end
+                end
+            end
+    
+            if awardid == 0 then
+                for _, conf in ipairs(conf_list) do
+                    local id = conf.ID
+                    local list = data.list
+                    if not list[id] then
+                        list[id] = {
+                            score = 0,
+                            times = 0
+                        }
+                    end
+                    local cur_score = data.all - list[id].score
+                    if awardid == 0 and conf.must > 0 and cur_score >= conf.must then
+                        list[id].score = data.all
+                        list[id].times = list[id].times + 1
+                        awardid = conf.points
+                        awardtimes = list[id].times
+                        break
+                    end
+    
+                    if awardid == 0 and cur_score >= conf.enter then
+                        local score = (cur_score - conf.enter)
+                        local all = conf.luckya*score^conf.luckyn+conf.luckyb*score+conf.chance
+                        local randnum = util.rand(1, MAXRAND)
+                        if all >= randnum then
+                            list[id].score = data.all
+                            list[id].times = list[id].times + 1
+                            awardid = conf.points
+                            awardtimes = list[id].times
+                            break
+                        end
+                    end
+                end
+            end
+    
+            if awardid == 0 then
+                local conf = conf_list[#conf_list]
+                local id = conf.ID
+                local list = data.list
+                list[id].score = 0
+                list[id].times = list[id].times + 1
+                awardid = conf_list[#conf_list].points
+                awardtimes = list[id].times
+            end
+            id_list[awardid] = (id_list[awardid] or 0) + 1
+            local ok, temp = common_fun.get_award(awardid, "CallawardConfig_proto", awardtimes)
+            if ok then
+                award_list = common_fun.merge2list(award_list, temp)
+            else
+                break
+            end
+            times = times - 1
+        end
+        if times ~= 0 then
+            return {}
+        end
+        return award_list, id_list
+    end
+    local num = tonumber(args[2] or 1000)
+    local draw_type = tonumber(args[1] or 1)
+    if num < 1 then
+        return "次数异常"
+    end
+
+    local conf_list = {}
+    local special_list = {}
+    local draw_name = ""
+    if draw_type == 1 then
+        draw_name = "高级英雄召唤"
+        local data_conf = asset.DataConfig_proto[20]
+        local special_conf = asset.HeroescallConfig_proto[data_conf.data2]
+        if special_conf then
+            table.insert(special_list, special_conf)
+        end
+    
+        for _, v in pairs(asset.HeroescallConfig_proto) do
+            if v.type == 1 then
+                table.insert(conf_list, v)
+            end
+        end
+    elseif draw_type == 2 then
+        draw_name = "普通英雄召唤"
+        local data_conf = asset.DataConfig_proto[20]
+        local special_conf = asset.HeroescallConfig_proto[data_conf.data1]
+        if special_conf then
+            table.insert(special_list, special_conf)
+        end
+
+        for _, v in pairs(asset.HeroescallConfig_proto) do
+            if v.type == 2 then
+                table.insert(conf_list, v)
+            end
+        end
+
+    elseif draw_type == 3 then
+        draw_name = "装备锻造"
+        local data_conf = asset.DataConfig_proto[20]
+        local special_conf = asset.HeroescallConfig_proto[data_conf.data3]
+        if special_conf then
+            table.insert(special_list, special_conf)
+        end
+    
+        special_conf = asset.HeroescallConfig_proto[data_conf.data4]
+        if special_conf then
+            table.insert(special_list, special_conf)
+        end
+    
+        for _, v in pairs(asset.HeroescallConfig_proto) do
+            if v.type == 3 then
+                table.insert(conf_list, v)
+            end
+        end
+    else 
+        return "类型异常"
+    end
+    table.sort(conf_list, function(a, b) 
+        if a.priority > b.priority then
+            return true
+        end
+        return false
+    end)
+    local _, show = draw({}, conf_list, num, special_list)
+    local str = string.format("====%s====,  %d次:\n\n\t", draw_name, num)
+    return str..stringify(show or {})
+end
+
+function CMD.show_avglist(args)
+    local itemavg = skynet.localname(".itemavg")
+    return skynet.call(itemavg, "lua", "show", args and args[1])
+end
+
+function CMD.show_statistics(args)
+    local statistics = skynet.localname(".statistics")
+    return skynet.call(statistics, "lua", "get_statistics_data", args and args[1])
+end
+
+--[[
+example:
+  参数说明:
+  http://192.168.108.19:9002/gm_test?user=yytx&pwd=lee@YY-Games.520&context={"uid":"00-xx","func":"gm_item","args":[1,2,3,4]}
+]]
+local function gm_test(args, ipaddr, header)
+    logger.warn("处理来自主机 %s 的 gm_test 请求,:%s", ipaddr,args.context)
+    usercenter = usercenter or skynet.localname(".usercenter")
+    local context = cjson.decode(args.context)
+    if not context.uid then
+        local f = CMD[context.func]
+        local info = "成功"
+        if not f then
+            info =  "没有该函数"
+        else
+            info = f(context.args)
+        end
+        if(context.func =="gm_draw") then
+            return create_html_page(info)
+        end
+        return {stats = 400, msg = info}
+    else
+        local errno, msg = skynet.call(usercenter, "lua", "gm_command",
+            context.uid, context.func, context.args
+        )
+        return {stats = errno, msg = msg}
+    end
+end
+return gm_test

+ 102 - 0
project/webpage/guild.lua

@@ -0,0 +1,102 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local cjson = require "cjson"
+local queue = require "skynet.queue"
+local synchronized = queue()
+local redisdriver = require "skynet.db.redis"
+-- local namecenter = skynet.localname(".namecenter")
+-- local temp_config = mt
+local mysql = require "skynet.db.mysql"
+local util = require "util"
+local stringify = require "stringify"
+local s_guild = skynet.localname(".guild")
+local md5 = require "md5"
+
+-- local zlf_log  = require "zlf_log"
+-- local z_trace  = zlf_log.trace
+
+local db
+
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+-- Generate request data
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+local whitelist = {
+    ["14.29.136.211"] = true,
+    ["192.168.1.41"] = true,
+    ["222.212.88.4"] = true,
+}
+
+-- 模板
+--[[
+    请求方式:	http://服务器ip:端口号/ban?code=xxx&type=操作类型&content=xxxx&sid=xxxxx
+    参数:
+        type:
+            1: 封禁公会 conten=1
+            2: 解封公会 conten=2
+            3: 重置公告 conten=公告内容
+            4: 重置名称 conten=公会行名称
+            5: 解锁公会 conten=5
+        sid: 公会sid
+
+    反馈信息内容(json格式):
+    {
+        state:		反馈码(0表示成功)
+        msg:		反馈描述(成功的时返回success)
+    }
+]]
+local guild = function(args, ipaddr,header)
+    return synchronized(function()
+        logger.trace("处理来自主机 %s 的guild请求", ipaddr)
+        -- 验证主机id
+        if not whitelist[ipaddr] then
+        --   return cjson.encode({errno = 403, msg = "不信任ip"})
+        end
+        -- 验证gm账号2
+        local code = string.sub(args.code, 1, 16)
+        if code ~= content.sign then
+            return cjson.encode({state = 403, msg = "账号或密码错误"})
+        end
+
+        local ftype = tonumber(args.type)       -- 操作类型
+        local fcont = args.content              -- 操作参数
+        local sid = args.sid                    -- 公会sid
+        logger.trace(" %s:%s:%s",ftype,fcont,sid)
+        -- 封 公会
+        if ftype == 1 then
+            local ret1,ret2 = skynet.call(s_guild, "lua", "set_seal", _,sid,1)
+            if ret1 >= 0 then
+                return cjson.encode({errno = 200, msg = "封禁公会 成功"})
+            else
+                return cjson.encode({errno = 400, msg = ret2})
+            end
+        elseif ftype == 2 then
+            local ret1,ret2 = skynet.call(s_guild, "lua", "set_seal", _,sid,2)
+            if ret1 >= 0 then
+                return cjson.encode({errno = 200, msg = "解封公会 成功"})
+            else
+                return cjson.encode({errno = 400, msg = ret2})
+            end
+        elseif ftype == 3 then       -- 设置公告
+            local ret1,ret2 = skynet.call(s_guild, "lua", "web_set_notice", _,sid,fcont)
+            if ret1 >= 0 then
+                return cjson.encode({errno = 200, msg = "设置新的公告 成功"})
+            else
+                return cjson.encode({errno = 400, msg = ret2})
+            end
+        elseif ftype == 4 then          -- 公会改名
+            local ret1,ret2 = skynet.call(s_guild, "lua", "web_rename", _,sid,fcont)
+            if ret1 >= 0 then
+                return cjson.encode({errno = 200, msg = "重新命名字 成功"})
+            else
+                return cjson.encode({errno = 400, msg = ret2})
+            end
+        else
+            -- return {code = 0, msg = "type 没有找到"}
+            return cjson.encode({errno = 400, msg = "type 没有找到"})
+        end
+    end)
+end
+return guild

+ 123 - 0
project/webpage/hotfix.lua

@@ -0,0 +1,123 @@
+local skynet = require "skynet"
+local codecache = require "skynet.codecache"
+require "skynet.manager"
+local logger = require "logger"
+local stringify = require "stringify"
+local cjson = require "cjson"
+local assetcenter
+local watchdog
+local authz = {
+  yytx = "lee@YY-Games.520"
+}
+
+local whitelist = {
+  ["192.168.1.23"] = true,
+  ["192.168.1.127"] = true,
+  ["14.29.136.211"] = true,
+  ["192.168.1.41"] = true,
+}
+
+local updateserver
+
+local function newserver()
+  local newnum = {}
+  table.insert(newnum,{server=skynet.localname(".quest"),configname={"TaskConfig_proto",}})--z力量
+  return newnum
+end
+
+local function hotfix_true(hotfixlist,configname)
+  if hotfixlist then
+    if hotfixlist == "*" then
+      return 1
+    end
+    for k, v in pairs(configname) do
+      if hotfixlist[v] then
+        logger.trace("_______________________热更配置:%s,更新服务",v)
+        return 1
+      end
+    end
+  end
+  return
+end
+
+--对需要更新配置的服务进行更新
+local function updateconfig(hotfixlist)
+  updateserver = updateserver or newserver()
+  if next(updateserver) then
+    for k, v in pairs(updateserver) do
+      if v.configname and hotfix_true(hotfixlist,v.configname) then
+        skynet.send(v.server, "lua", "hotfix", hotfixlist)
+      end
+    end
+  else
+    logger.trace("_______________________热更配置无服务需要")
+  end
+end
+
+-- local z_trace = require "zlf_log"
+-- local z_trace = z_trace.trace
+
+-- example:
+--    http://192.168.1.57:9001/hotfix?user=lee&pwd=lee@123&config=*
+--    http://192.168.1.57:9001/hotfix?user=lee&pwd=lee@123&code=*
+--    http://192.168.1.57:9001/hotfix?user=lee&pwd=lee@123&code=*&config=*
+local function hotfix(args, ipaddr, header)
+  logger.warn("处理来自主机 %s 的hotfix请求,:%s", ipaddr,args.config)
+  if not whitelist[ipaddr] then
+    --return { "403 - Forbidden" }
+    -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+  end
+
+  -- 鉴权
+--   local user = args.user or ""
+--   local pwd = args.pwd or ""
+--   if authz[user] ~= pwd then
+--     -- return { "403 - Forbidden" }
+--     return cjson.encode({errno = 403, host = header.host, info = "账号或密码错误"})
+--   end
+
+  logger.warn("更新配置文件:%s",args.config)
+  -- 热更配置文件
+  if args.config then
+    local hotfixlist
+    assetcenter = assetcenter or skynet.localname(".assetcenter")
+    local error
+    if args.config == "*" then
+      error = skynet.call(assetcenter, "lua", "hotfix")
+      hotfixlist = "*"
+    elseif args.config then
+      hotfixlist = {}
+      local ok, entity = pcall(cjson.decode, args.config)
+        -- z_trace.guild(" --- entity = ", entity)
+      if not ok then
+        return { code=400, msg="Bad request" }
+      end
+      for k, v in pairs(entity) do
+        logger.warn("更新配置文件:%s",v)
+        error = skynet.call(assetcenter, "lua", "hotfix",v)
+        hotfixlist[v] = 1
+      end
+    else
+      return cjson.encode({errno = 400, host = header.host, msg = "缺少配置表参数"})
+    end
+    if error then
+      return cjson.encode({errno = 400, host = header.host, msg = "读取文件失败: ".. error})
+    end
+    updateconfig(hotfixlist)
+--   else
+    -- return cjson.encode({errno = 400, host = header.host, info = "参数未nil : "})
+  end
+
+  -- 热更源代码
+  if args.code then
+    logger.warn("热更新代码")
+    codecache.clear()
+  end
+
+  -- 通知 watchdog, 放弃 agent 池中的失效对象
+  watchdog = watchdog or skynet.localname(".ws_watchdog")
+  skynet.call(watchdog, "lua", "hotfix")
+
+  return  cjson.encode({errno = 200, host = header.host, msg = "热更新 成功"})
+end
+return hotfix

+ 286 - 0
project/webpage/inquire_deltime.lua

@@ -0,0 +1,286 @@
+--[[
+    author:{zlf}
+    time:2020-06-30 15:56:21
+    note:
+      玩家道具/精灵 等 删除,查询数据
+      1. 精灵数据: 分析记录数据, 并按权重排序推荐删除。
+      2. 符文: 返回指定类型符文唯一id
+      3. 装备:返回指定类型装备唯一id
+      3. 其他类型: 返回数据
+]]
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local stringify = require "stringify"
+local usercenter = skynet.localname(".usercenter")
+local logind = skynet.localname(".loginserver")
+local redisdriver = require "skynet.db.redis"
+local namecenter = skynet.localname(".namecenter")
+
+local cjson = require "cjson"
+local md5 = require "md5"
+local THIS = {}
+local ITEM_TYPE_DIS = {}
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+-- Generate request data
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+local whitelist = {
+    ["192.168.1.23"] = true,
+    ["192.168.1.127"] = true,
+    ["192.168.1.41"] = true,
+    ["222.212.88.4"] = true,
+}
+
+function THIS.get_data(redis, uid, key)
+    local ret = redis:HMGET(string.format("character:%s", uid), key)
+    if ret[1] then
+        if key == "coins" -- 金币
+        or key == "exp"                     -- 角色经验
+        or key == "pkpoint" -- 竞技积分
+        or key == "diamonds" -- 钻石
+        or key == "friendship" -- 友情点
+        or key == "contribute" -- 公会贡献
+        or key == "advintegral" -- 公会冒险积分
+        or key == "recharge_point" -- 充值积分
+        or key == "span_elf_king_point" -- 跨服至尊天王积分 -- CDPK3-86
+        or key == "ruins_point" -- 古代金币 -- CDPK3-276 宝可梦遗迹
+        or key == "research_note" -- 研究笔记 --PK3-1148
+        or key == "megacoin" -- mege点
+        or key == "bind_diamonds" -- 绑定钻石
+        or key == "budo_point" -- 道馆积分
+        or key == "manual_exp" -- 训练师手册积分 --PK3-1077
+        or key == "explore" -- 探险值
+        or key == "explore_exp" -- 探险经验
+        or key == "gashapon_num" -- 扭蛋积分
+        or key == "smeltpoint" -- 熔炼点
+        then
+            return ret[1]
+        else
+            return skynet.unpack(ret[1])
+        end
+    else
+        return nil
+    end
+end
+
+ITEM_TYPE_DIS[GOODS_EQUIP] = function(redis,uid,itemid)       -- 1  装备
+  itemid = tonumber(itemid)
+  local bagdata = THIS.get_data(redis, uid, "bagdata")
+  if not bagdata then return 409 end      -- 数据加载失败
+  local ret_sid = {}                      -- 返回数据
+  for sid, info in pairs(bagdata.equip) do
+    if itemid == info.sid then
+      table.insert(ret_sid, sid)
+    end
+  end
+  -- 返回查询到的数据
+  return 0,ret_sid
+end
+ITEM_TYPE_DIS[GOODS_ITEM] = function(redis,uid,itemid)        -- 2  道具
+  itemid = tonumber(itemid)
+  local bagdata = THIS.get_data(redis, uid, "bagdata")
+  if not bagdata then return 409 end      -- 数据加载失败
+  local ret_num = 0                       -- 指定类型道具的数量
+  if bagdata.item[itemid] then
+    ret_num = bagdata.item[itemid].num
+  end
+  return 0,ret_num
+end
+ITEM_TYPE_DIS[TYPE_ELF] = function(redis,uid,itemid)        -- 3  精灵
+  itemid = tonumber(itemid)
+  local elfdata = THIS.get_data(redis, uid, "elfdata")
+  if not elfdata then return 409 end      -- 数据加载失败
+  local ret_elf = {}                       -- 返回数据
+  -- logger.trace(" elfdata.elflist %s", stringify(elfdata.elflist))
+  for _, info in pairs(elfdata.elflist) do
+    if info.sid == itemid then
+      table.insert(ret_elf, info)
+    end
+  end
+  -- 对筛选出来的数据进行排序
+  -- logger.trace( "### ret_elf %s", stringify(ret_elf))
+  table.sort(ret_elf, function(a,b)
+    local a_lv = a.level or 0
+    local b_lv = b.level or 0
+    -- 比较等级
+    if a_lv < b_lv then
+      return true
+    elseif a_lv == b_lv then
+      -- 亲密度等级
+      local a_lo = a.love or 0
+      local b_lo = b.love or 0
+      if a_lo < b_lo then
+        return true
+      end
+    end
+  end)
+  return 0, ret_elf
+end
+ITEM_TYPE_DIS[TYPE_FASHION] = function(redis,uid,itemid)    -- 4  时装
+  itemid = tonumber(itemid)
+  local dressdata = THIS.get_data(redis, uid, "dress")
+  if not dressdata then return 409 end      -- 数据加载失败
+  return 0,dressdata.owned[itemid] or {0,0}
+end
+ITEM_TYPE_DIS[TYPE_DEBRIS] = function(redis,uid,itemid)     -- 5  精灵碎片
+  itemid = tonumber(itemid)
+  local oncedata = THIS.get_data(redis, uid, "oncedata")
+  if not oncedata then return 409 end      -- 数据加载失败
+  local ret_num = 0
+  if oncedata.debris[itemid] then
+    ret_num = oncedata.debris[itemid].num
+  end
+  return 0, ret_num
+end
+ITEM_TYPE_DIS[TYPE_RUNE] = function(redis,uid,itemid)       -- 6  精灵符文
+  itemid = tonumber(itemid)
+  local elf_runedata = THIS.get_data(redis, uid, "elf_rune")
+  if not elf_runedata then return 409 end      -- 数据加载失败
+
+  local ret = {}
+  for _, info in pairs(elf_runedata.rune_bag or {}) do
+    if info.sid == itemid then
+      table.insert(ret, info.id)
+    end
+  end
+  return 0, ret
+end
+ITEM_TYPE_DIS[GOODS_MONEY] = function(redis,uid,itemid)      -- 8  货币
+  itemid = tonumber(itemid)
+  local key = MONEY_TYPE[itemid]
+  if not key then
+    return 409          -- 数据加载失败
+  end
+  local data = THIS.get_data(redis, uid, key)
+  return 0, data or 0
+end
+ITEM_TYPE_DIS[TYPE_RUNEDEBRIS] = function(redis,uid,itemid) -- 9  符文碎片
+  itemid = tonumber(itemid)
+  local oncedata = THIS.get_data(redis, uid, "oncedata")
+  if not oncedata then return 409 end      -- 数据加载失败
+
+  local ret_num = 0
+  if oncedata.rune[itemid] then
+    ret_num = oncedata.rune[itemid].num
+  end
+  return 0,0
+end
+ITEM_TYPE_DIS[TYPE_TITLE] = function(redis,uid,itemid)      -- 10 称号
+  itemid = tonumber(itemid)
+  local titledata = THIS.get_data(redis, uid, "title")
+  if not titledata then return 409 end      -- 数据加载失败
+
+  local ret_num = 0
+  if titledata.titlelist[itemid] then
+    ret_num = 1
+  end
+  return 0,ret_num
+end
+--CDPK3-271
+ITEM_TYPE_DIS[TYPE_CARRY] = function(redis,uid,itemid)       -- 12  精灵携带品
+  itemid = tonumber(itemid)
+  local elf_carry_items = THIS.get_data(redis, uid, "elf_carry_items")
+  if not elf_carry_items then return 409 end      -- 数据加载失败
+
+  local ret = {}
+  for _, info in pairs(elf_carry_items.carry_items_bag or {}) do
+    if info.sid == itemid then
+      table.insert(ret, info.id)
+    end
+  end
+  ITEM_TYPE_DIS[TYPE_CARRYDEBRIS] = function(redis,uid,itemid) -- 13  携带品碎片
+    itemid = tonumber(itemid)
+    local oncedata = THIS.get_data(redis, uid, "oncedata")
+    if not oncedata then return 409 end      -- 数据加载失败
+
+    local ret_num = 0
+    if oncedata.carry[itemid] then
+      ret_num = oncedata.carry[itemid].num
+    end
+    return 0,0
+  end
+
+  return 0, ret
+end
+
+-- CDPK3-1002 代金券
+ITEM_TYPE_DIS[TYPE_COUPON] = function(redis,uid,cid)       -- 12  精灵携带品
+  cid = tonumber(cid)
+  local coupon = THIS.get_data(redis, uid, "coupon")
+  local ret = {}
+  for _, info in pairs(coupon.bag) do
+    if info.cid == cid then
+      table.insert(ret, info.id)
+    end
+  end
+  return 0, ret
+end
+
+--[[
+返回数据结构:
+    类型1-装备:{装备唯一id, ...}
+    类型2-道具:数量
+    类型3-精灵(已经完成排序,展示返回数据):{
+        [1] = {
+            sid = 精灵模板id,
+            id = 精灵唯一id,
+            ...(其他数据)
+        },
+    }
+    类型4-时装: {时装数量, 时装进阶等级(时装进阶后,时装必须留一件)}
+    类型5-精灵碎片: 碎片数量
+    类型6-精灵符文: {符文唯一id, ...}
+    类型8-货币: 货币数量
+    类型9-符文碎片: 碎片数量
+    类型10-称号: 称号数量(默认为1)
+]]
+
+
+-- example:
+--[[
+    http://192.168.108.19:9002/inquire_deltime?code=xxx&uid=00-6395766577223962624
+      &itemtype=主类型
+      &itemid=子id
+]]
+local function inquire_deltime(args, ipaddr, header)
+    logger.trace("处理来自主机 %s 的 获取 查询删除的数据 %s", ipaddr, stringify(args))
+    if not whitelist[ipaddr] then
+        -- return { "403 - Forbidden" }
+        -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+    end
+
+    -- 验证gm账号
+    local code = string.sub(args.code, 1, 16)
+    logger.trace(" ### %s",content.sign)
+    if code ~= content.sign then
+        return cjson.encode({state = 403, msg = "账号或密码错误"})
+    end
+
+    local conf = assert(option.redis)
+    local redis
+    redis = redisdriver.connect(conf)
+    redis:select(0)
+
+    -- 获取玩家uid
+    local uid
+    if args.uid then
+      uid = args.uid
+    else
+      return cjson.encode({state = 403,msg = "请输入玩家uid或者玩家name"})
+    end
+
+    local itemtype = tonumber(args.itemtype)      -- 物品主类型
+    local itemid = tonumber(args.itemid)          -- 物品子类型
+    logger.trace(" ### args %s", stringify(args))
+    local errno, info = ITEM_TYPE_DIS[itemtype](redis,uid,itemid)
+    if errno ~= 0 then
+      return cjson.encode({state = 409,msg = "数据加载失败"})
+    end
+    logger.trace(" info %s", stringify({info}))
+    redis:disconnect()
+    return cjson.encode({state = 0, msg = "success", data = info})
+end
+
+return inquire_deltime

+ 133 - 0
project/webpage/mail.lua

@@ -0,0 +1,133 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local queue = require "skynet.queue"
+local md5 = require "md5"
+local synchronized = queue()
+local cjson = require "cjson"
+local asset = require "model.asset"
+local common_fun = require "model.common_fun"
+
+local mailbox
+
+local table_insert = table.insert
+
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+-- Generate request data
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+local function create_html_page(text)
+    return string.format(
+        "<!DOCTYPE html><html><head><meta charset='UTF-8'></head> <body>%s</body> </html>",
+        string.gsub(text, "[\n]", "<br/>"))
+end
+
+local whitelist = {
+    -- ["192.168.1.23"] = true,
+}
+
+local function func(args, ipaddr, header)
+    logger.trace("处理来自主机 %s 的发送邮件请求", ipaddr)
+    -- 验证主机id
+    -- if not whitelist[ipaddr] then
+    --     return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+    -- end
+    -- 验证gm账号
+    -- local code = string.sub(args.code, 1, 16)
+    -- if code ~= content.sign then
+    --     return cjson.encode({state = 403, msg = "账号或密码错误"})
+    -- end
+
+    local mtype = tonumber(args.ntype)
+    local json = cjson.decode(args.json)
+    local attach = json.attach or {}
+
+    local detail = json.attach or {}
+    local errno = 0
+    local info = "成功"
+
+    ----------------------------- THIS ----------------------------------
+    -- if attach ~= nil then
+    --     for i=1, #attach, 2 do
+    --         local id = attach[i]
+    --         local num = attach[i+1]
+    --         if not id or not num or num <= 0 then
+    --             errno = 1
+    --             info = "格式错误"
+    --             break
+    --         end
+            
+    --         errno = common_fun.goods_check(id)
+    --         if errno == 0 then
+    --             table_insert(detail, {id = id, num = num})
+    --         else
+    --             info = "错误的道具id:"..id
+    --             break
+    --         end
+    --     end
+    -- else
+    --     detail = {}
+    -- end
+
+    -- if errno ~= 0 then
+    --     return cjson.encode({state = errno, msg = info})
+    -- end
+
+    for _, v in ipairs(detail) do
+        if not v.id or not v.num or v.num <= 0 then
+            errno = 1
+            info = "格式错误"
+            break
+        end
+        local goods_type = common_fun.goods_type(v.id)
+        if goods_type== GOODS_NONE then
+            errno = 1
+            info = "错误的道具id"
+        end
+    end
+
+    if errno ~= 0 then
+        return cjson.encode({state = errno, msg = info})
+    end
+
+    local body = json.content
+    local source = {
+        module = "sendmail",
+        brief = "GM发送的邮件",
+        context = string.format("GM ip: %s  tm = %s",
+                                ipaddr, os.time())
+    }
+    local receiver
+    mailbox = mailbox or skynet.localname(".mailbox")
+    local subject = tonumber(json.title)
+
+    if type(subject) ~= 'number' then
+        return cjson.encode({state = 400, msg = "错误的title"})
+    end
+    for k, v in pairs(detail) do
+        logger.trace("=======k=%s--v=%s----", v.id, v.num)
+    end
+    if mtype == 2 then -- 全服邮件
+        skynet.call(mailbox, "lua", "broadcast", subject, body, detail,
+                    source)
+    elseif mtype == 1 then -- 给特定玩家
+        receiver = json.roles
+        skynet.call(mailbox, "lua", "off_line_mail", receiver, subject,
+                    body, detail, source)
+    else
+        return cjson.encode({state = 400, msg = "ntype不存在"})
+    end
+
+    return cjson.encode({state = 0, msg = "success"})
+end
+
+-- http://192.168.108.4:9002/mail?code=xxxx&ntype=3&json={"roles":["00-6527363693544497152","00-6527368552515657728"],"attach":[100041, 100, 100037, 100],"title":"这是标题","content":"这是正文"}
+local mail = function(args, ipaddr, header)
+    return synchronized(function()
+        -- return create_html_page(func(args, ipaddr, header))
+        return func(args, ipaddr, header)
+    end)
+end
+
+return mail

+ 84 - 0
project/webpage/modify_time.lua

@@ -0,0 +1,84 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local stringify = require "stringify"
+local usercenter = skynet.localname(".usercenter")
+local logind = skynet.localname(".loginserver")
+local redisdriver = require "skynet.db.redis"
+local namecenter = skynet.localname(".namecenter")
+local md5 = require "md5"
+
+local cjson = require "cjson"
+
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+-- Generate request data
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+local whitelist = {
+  ["192.168.1.23"] = true,
+  ["192.168.1.127"] = true,
+  ["192.168.1.41"] = true,
+  ["222.212.88.4"] = true,
+}
+
+--[[
+    byname = zlf        -- 玩家的名字
+]]
+
+-- example:
+--[[
+    type = 1/2      1: 获取当前时间 2:设置时间 3:指定格式修改时间
+    time = utc      目标时间
+    http://192.168.1.51:9001/set_time?user=yytx&pwd=lee@YY-Games.520&arg={"type":1,"time":0}
+    http://192.168.1.51:9001/set_time?user=yytx&pwd=lee@YY-Games.520&arg={"type":2,"time":1531822162}
+    http://192.168.1.51:9001/set_time?user=yytx&pwd=lee@YY-Games.520&arg={"type":3,"time":"2018-7-17 17:44:50"}
+]]
+local function modify_time(args, ipaddr, header)
+  logger.trace("处理来自主机 %s 的 服务器时间设置请求", ipaddr)
+  if not whitelist[ipaddr] then
+    --return { "403 - Forbidden" }
+    -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+  end
+
+
+  -- 验证gm账号
+  local code = string.sub(args.code, 1, 16)
+  if code ~= content.sign then
+    return cjson.encode({state = 403, msg = "账号或密码错误"})
+  end
+
+  local par = cjson.decode(args.arg)
+  if par.type == 1 then                     -- 获取系统时间
+    -- 直接返回即可
+  elseif par.type == 2 then                 -- 设置系统时间
+    -- 检查时间
+    if par.time <= os.time() then
+        return cjson.encode({state = 403,msg = "errno less than now time"})
+    end
+    -- 转换时间格式
+    local format = os.date("%Y-%m-%d %X", par.time)
+    local key = string.format("sudo date -s '%s'", format)
+    logger.trace(" === %s", key)
+    local ret,ret2 = os.execute(key)
+    logger.trace("ret = %s, ret2 = %s", ret, ret2)
+    if not ret then
+        -- return cjson.encode({errno = 403, host = header.host, info = "errno args.time"})
+        return cjson.encode({state = 403, state = "errno abnormal"})
+    end
+  elseif par.type == 3 then                 -- 设置系统时间
+    logger.trace(" === --- %s", par.time)
+    local key = string.format("sudo date -s '%s'", par.time)
+    logger.trace(" === %s", key)
+    local ret,ret2 = os.execute(key)
+    logger.trace("ret = %s, ret2 = %s", ret, ret2)
+    if not ret then
+        return cjson.encode({state = 403, msg = "errno args.time"})
+    end
+  else
+    return cjson.encode({state = 403, msg = "errno ars.type"})
+  end
+  return cjson.encode({state = 0, msg = "set succeed",type = par.type, time = os.time(),format = os.date("%Y-%m-%d %X", os.time())})
+end
+
+return modify_time

+ 77 - 0
project/webpage/notice.lua

@@ -0,0 +1,77 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local stringify = require "stringify"
+local usercenter = skynet.localname(".usercenter")
+local chat = skynet.localname(".chat")
+local cjson = require "cjson"
+local md5 = require "md5"
+
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+-- Generate request data
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+local whitelist = {
+    ["192.168.1.23"] = true,
+    ["192.168.1.127"] = true,
+    ["192.168.1.41"] = true,
+    ["14.29.136.211"] = true,
+    ["222.212.88.4"] = true,
+}
+
+--请求方式:	http://服务器ip地址:端口号/notice?code=xxx&ntype=1&interval=10&content=想要发送的跑马灯内容
+--示例:		http://192.168.1.102:8002/notice?code=xxx&ntype=1&interval=10&content=this%20is%20a%20test
+local function notice(args, ipaddr, header)
+    logger.trace("处理来自主机 %s 的公告请求", ipaddr)
+    if not whitelist[ipaddr] then
+        -- return { "403 - Forbidden" }
+        -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+    end
+    -- 验证gm账号
+    local code = string.sub(args.code, 1, 16)
+    if code ~= content.sign then
+        return cjson.encode({state = 403, msg = "账号或密码错误"})
+    end
+
+    local ntype = math.tointeger(args.ntype) or 1
+    if ntype < 1 then
+        return cjson.encode({state = 400, msg = "ntype次数少于 1"})
+    end
+
+    local details = args.content
+    if not details or #details == 0 then
+        return cjson.encode({state = 400, msg = "content内容异常"})
+    end
+    logger.error("______GM请求发送公告:%s", details)
+
+    local interval = math.tointeger(args.interval) or 0
+    if ntype > 1 then
+        if interval < 1 then
+            return cjson.encode({state = 400, msg = "间隔时间太短"})
+        end
+    else
+        interval = 0
+    end
+    skynet.fork(function()
+        for i = 1, ntype do
+            logger.trace("notice: %s,times:%s,interval:%s", details, ntype,interval)
+            local now = os.time()
+            local message = {
+                h_type = 2, -- 聊天的主类型
+                c_type = CHAT_SON_TYPE.GM_NOTICE, -- 子类型
+                content = details, -- 内容
+                tm = now, -- 时间
+                sid = option.sid -- 服务器
+            }
+            skynet.call(chat, "lua", "reqChat", message)
+            -- skynet.call(usercenter, "lua", "broadcast", "notice", content)
+            skynet.sleep(interval * 100)
+        end
+    end)
+
+    logger.warn("notice (%s, %s)", code, ipaddr)
+    return cjson.encode({state = 0, msg = "success"})
+end
+
+return notice

+ 63 - 0
project/webpage/online.lua

@@ -0,0 +1,63 @@
+local skynet = require "skynet"
+require "skynet.manager"
+local logger = require "logger"
+local stringify = require "stringify"
+local watchdog = skynet.localname(".watchdog")
+local cjson = require "cjson"
+local md5 = require "md5"
+local redisdriver = require "skynet.db.redis"
+local s_public  = skynet.localname(".public")       -- PK3-1384 增加区服平均等级展示
+local authz = {acc = "yytx", pwd = "lee@YY-Games.520"}
+-- Generate request data
+local content = {acc = authz.acc, pwd = authz.pwd, sign = false}
+content.sign = string.sub(md5.sumhexa(content.acc .. content.pwd), 9, 24)
+
+local whitelist = {
+    ["192.168.1.23"] = true,
+    ["192.168.1.127"] = true,
+    ["14.29.136.211"] = true,
+    ["192.168.1.41"] = true,
+    ["222.212.88.4"] = true,
+}
+
+local redis
+local status = 0
+local function initialize()
+    status = 1
+    local conf = assert(option.redis)
+    redis = redisdriver.connect(conf)
+    redis:select(0)
+  end
+--    http://192.168.1.57:9001/console?user=lee&pwd=lee@123&cmd=info#address=.watchdog
+--请求方式:	http://服务器ip地址:端口号/online?code=xxx
+--示例:	http://192.168.1.102:9002/online?code=xxx
+local function online(args, ipaddr, header)
+    logger.warn("处理来自主机 %s 获取服务器当前在线人数请求", ipaddr)
+    if not whitelist[ipaddr] then
+        -- return { "403 - Forbidden" }
+        -- return cjson.encode({errno = 403, host = header.host, info = "不信任ip"})
+    end
+    -- 验证gm账号
+    local code = string.sub(args.code, 1, 16)
+    if code ~= content.sign then
+        return cjson.encode({state = 403, msg = "账号或密码错误"})
+    end
+
+    local content
+    content = skynet.call(args.address or ".watchdog", "debug", "INFO")
+    logger.trace(" content = %s", content)
+    if not content then
+        return cjson.encode({state = 400, msg = "获取服务器失败"})
+    else
+        if status == 0 then
+            initialize()
+        end
+        local ret = redis:KEYS("character:*")
+        content.offline_avg_number = #ret
+        local average_lv = skynet.call(s_public, "lua", "compensate", "gm_average_lv")              -- PK3-1384 增加区服平均等级展示
+        content.average_lv = average_lv
+        return cjson.encode({state = 0, msg = "success",content=content})
+    end
+end
+
+return online

+ 0 - 0
project/webpage/player.lua


Some files were not shown because too many files changed in this diff