藏宝湾网游单机站

 找回密码
 注册

QQ登录

只需一步,快速开始

★69 元包站 Gm 手游平台★

★69 元包站 Gm 手游平台★

★Gm 游戏平台【无限资源包站平台,真正体验gm和土豪感觉】★

★白嫖放置传奇 玩通关算你赢★

★白嫖放置传奇 玩通关算你赢★

★白嫖长久耐玩型 放置类挂机传奇游戏 充值可打 安全挂机 可交易 无PK★

★@梦幻长久耐玩全新大唐九黎★

★@!!——梦幻长久耐玩全新大唐九黎——!!★

★★新增全新门派九黎城, ★侵烛系统 ★静脉系统 ★赐福系统 ★战斗动作声效系统 ★全场景NPC、召唤兽迭代最新 ★17个副本,完全还原 ★独家PK系统,享受极致免费PK★★

承接推广
★承接推广★

★承接推广★

☆ 六一活动开放-放置传奇☆

★ 六一活动开放-放置传奇★

★可白嫖 上班摸鱼类 放置文字传奇游戏(放置休闲)qq群756025698★

★三职业复古★开局送神装★爆率100%★零氪首选★

★三职业复古★开局送神装★爆率100%★零氪首选★

★〖双线〗〖无忧传奇〗〖每日新区〗〖开局送神装 100%高爆率 装备元宝打怪爆〗★

承接推广

★承接推广★

★承接推广★

承接推广

★承接推广★

★承接推广★

★DNF★全职业平衡★公益服★
★DNF★全职业平衡★公益服★

【dnf86版本】【怀旧服】【全职业平衡】【非变态服】【非商业服】【774031300】

★承接推广★

★承接推广★

★承接推广★

★承接推广★

★承接推广★

★承接推广★

★承接推广★

★承接推广★

★承接推广★

查看: 695|回复: 5

[原创] 征途抽奖脚本

[复制链接]
 楼主| 发表于 2026-2-9 18:42:54 | 显示全部楼层 |阅读模式
  1. // CmdDrawActivity.h
  2. #ifndef CMD_DRAW_ACTIVITY_H
  3. #define CMD_DRAW_ACTIVITY_H

  4. #include "zType.h"

  5. namespace Cmd {
  6. namespace Draw {

  7. // 协议主ID: 0x3000-0x30FF
  8. const WORD CMD_DRAW_ACTIVITY = 0x3000;

  9. // 子协议
  10. enum {
  11.     PARA_DRAW_CHECK_REQ     = 1,    // 检查抽奖资格
  12.     PARA_DRAW_CHECK_RET     = 2,    // 检查结果
  13.     PARA_DRAW_DO_REQ        = 3,    // 执行抽奖
  14.     PARA_DRAW_DO_RET        = 4,    // 抽奖结果
  15.     PARA_DRAW_INFO_REQ      = 5,    // 请求活动信息
  16.     PARA_DRAW_INFO_RET      = 6,    // 活动信息
  17.     PARA_DRAW_LOG_REQ       = 7,    // 请求抽奖记录
  18.     PARA_DRAW_LOG_RET       = 8,    // 记录返回
  19.     PARA_DRAW_GLOBAL_NOTIFY = 9,    // 全服广播
  20.     PARA_DRAW_GM_CMD        = 10,   // GM命令
  21. };

  22. // 奖品类型
  23. enum PrizeType {
  24.     PRIZE_TYPE_ITEM     = 1,    // 道具
  25.     PRIZE_TYPE_MONEY    = 2,    // 金币
  26.     PRIZE_TYPE_EXP      = 3,    // 经验
  27.     PRIZE_TYPE_WING     = 4,    // 翅膀
  28.     PRIZE_TYPE_PET      = 5,    // 宠物
  29.     PRIZE_TYPE_TITLE    = 6,    // 称号
  30.     PRIZE_TYPE_NONE     = 99,   // 未中奖
  31. };

  32. // 检查请求
  33. struct stDrawCheckReq {
  34.     WORD cmd;           // 0x3000
  35.     WORD para;          // 1
  36.     DWORD activityId;   // 活动ID
  37.     DWORD drawTimes;    // 抽几次(1或10)
  38. };

  39. // 检查返回
  40. struct stDrawCheckRet {
  41.     WORD cmd;
  42.     WORD para;          // 2
  43.     DWORD activityId;
  44.     BYTE ret;           // 0成功,1活动未开启,2次数不足,3道具不足,4包裹满
  45.     DWORD remainTimes;  // 剩余次数
  46.     DWORD needItemId;   // 需要道具ID
  47.     DWORD needItemNum;  // 需要数量
  48. };

  49. // 执行抽奖请求
  50. struct stDrawDoReq {
  51.     WORD cmd;
  52.     WORD para;          // 3
  53.     DWORD activityId;
  54.     DWORD drawTimes;    // 1或10
  55.     BYTE useMoney;      // 是否使用元宝代替道具(0否1是)
  56. };

  57. // 单次奖品信息
  58. struct PrizeInfo {
  59.     DWORD prizeId;      // 奖品配置ID
  60.     DWORD type;         // PrizeType
  61.     DWORD itemId;       // 物品ID(如果是物品)
  62.     DWORD count;        // 数量
  63.     DWORD level;        // 等级/品质
  64.     DWORD broadcast;    // 是否广播(0/1)
  65.     char name[64];      // 奖品名称
  66. };

  67. // 执行抽奖返回
  68. struct stDrawDoRet {
  69.     WORD cmd;
  70.     WORD para;          // 4
  71.     DWORD activityId;
  72.     BYTE ret;           // 0成功,1失败,2包裹满
  73.     DWORD drawTimes;    // 实际抽了几次
  74.     DWORD prizeCount;   // 奖品数量(用于变长数组)
  75.     // PrizeInfo prizes[prizeCount];  // 奖品列表
  76. };

  77. // 活动信息请求
  78. struct stDrawInfoReq {
  79.     WORD cmd;
  80.     WORD para;          // 5
  81.     DWORD activityId;   // 0表示请求所有活动
  82. };

  83. // 活动基本信息
  84. struct ActivityInfo {
  85.     DWORD id;
  86.     char name[64];
  87.     char desc[256];
  88.     BYTE enabled;       // 是否开启
  89.     DWORD startTime;    // 开始时间
  90.     DWORD endTime;      // 结束时间
  91.     DWORD dailyLimit;   // 每日限制
  92.     DWORD costItemId;   // 消耗道具
  93.     DWORD costItemNum;  // 消耗数量
  94.     DWORD costMoney;    // 替代元宝
  95.     BYTE hasGuarantee;  // 是否有保底
  96.     DWORD guaranteeTimes; // 保底次数
  97. };

  98. // 活动信息返回
  99. struct stDrawInfoRet {
  100.     WORD cmd;
  101.     WORD para;          // 6
  102.     DWORD count;
  103.     // ActivityInfo activities[count];
  104. };

  105. // 抽奖记录
  106. struct DrawLog {
  107.     DWORD logId;
  108.     DWORD activityId;
  109.     DWORD prizeType;
  110.     DWORD itemId;
  111.     DWORD count;
  112.     DWORD time;
  113.     char prizeName[64];
  114. };

  115. // 记录请求
  116. struct stDrawLogReq {
  117.     WORD cmd;
  118.     WORD para;          // 7
  119.     DWORD activityId;   // 0表示所有
  120.     DWORD startTime;
  121.     DWORD endTime;
  122. };

  123. // 记录返回
  124. struct stDrawLogRet {
  125.     WORD cmd;
  126.     WORD para;          // 8
  127.     DWORD count;
  128.     // DrawLog logs[count];
  129. };

  130. // 全服广播(服务器推送给所有客户端)
  131. struct stDrawGlobalNotify {
  132.     WORD cmd;
  133.     WORD para;          // 9
  134.     DWORD activityId;
  135.     char playerName[64];
  136.     char prizeName[64];
  137.     DWORD prizeLevel;   // 奖品等级(用于显示不同颜色)
  138.     char msg[256];      // 完整消息
  139. };

  140. // GM命令
  141. enum GMCmd {
  142.     GM_CMD_RELOAD_CONFIG = 1,   // 重载配置
  143.     GM_CMD_RESET_PLAYER = 2,    // 重置玩家数据
  144.     GM_CMD_SET_STOCK = 3,       // 设置库存
  145.     GM_CMD_SET_STATUS = 4,      // 开启/关闭活动
  146.     GM_CMD_ADD_TIMES = 5,       // 增加玩家次数
  147.     GM_CMD_QUERY_INFO = 6,      // 查询信息
  148. };

  149. struct stDrawGMCmd {
  150.     WORD cmd;
  151.     WORD para;          // 10
  152.     DWORD gmCmd;
  153.     DWORD targetId;     // 玩家ID或活动ID
  154.     DWORD param1;
  155.     DWORD param2;
  156.     char paramStr[256];
  157. };

  158. #pragma pack()
  159. }
  160. }
  161. #endif
复制代码
  1. // DrawActivityClient.h
  2. #ifndef DRAW_ACTIVITY_CLIENT_H
  3. #define DRAW_ACTIVITY_CLIENT_H

  4. #include "zTCPClient.h"
  5. #include "zSingleton.h"
  6. #include "CmdDrawActivity.h"

  7. using namespace Cmd::Draw;

  8. class DrawActivityClient : public zTCPBufferClient, public zSingleton<DrawActivityClient>
  9. {
  10. public:
  11.     DrawActivityClient(const std::string& ip, WORD port);
  12.     virtual ~DrawActivityClient();
  13.    
  14.     bool connectToServer();
  15.     virtual void run();
  16.     virtual bool msgParse(const BYTE* data, const DWORD len);
  17.    
  18.     // 发送给抽奖服务器
  19.     bool sendToDrawServer(const void* data, DWORD len);
  20.    
  21.     // 转发给客户端
  22.     void sendToClient(DWORD userId, const void* data, DWORD len);
  23.    
  24. private:
  25.     std::string serverIp;
  26.     WORD serverPort;
  27.     bool reconnect();
  28. };

  29. #define DRAW_CLIENT DrawActivityClient::getInstance()

  30. #endif
复制代码
  1. // DrawActivityClient.cpp
  2. #include "DrawActivityClient.h"
  3. #include "SceneUserManager.h"
  4. #include "SceneUser.h"
  5. #include "ScenesServer.h"
  6. #include "LuaEngine.h"

  7. DrawActivityClient::DrawActivityClient(const std::string& ip, WORD port)
  8.     : serverIp(ip), serverPort(port)
  9. {
  10. }

  11. DrawActivityClient::~DrawActivityClient()
  12. {
  13. }

  14. bool DrawActivityClient::connectToServer()
  15. {
  16.     if(!connect(serverIp.c_str(), serverPort))
  17.     {
  18.         Zebra::logger->error("连接抽奖服务器失败 %s:%d", serverIp.c_str(), serverPort);
  19.         return false;
  20.     }
  21.    
  22.     // 发送注册包
  23.     struct {
  24.         WORD cmd;
  25.         WORD para;
  26.         DWORD serverId;
  27.         DWORD serverType;
  28.     } regCmd = {0x3000, 0, ScenesService::getInstance().getServerID(), 1};
  29.    
  30.     return sendCmd(&regCmd, sizeof(regCmd));
  31. }

  32. void DrawActivityClient::run()
  33. {
  34.     while(!ScenesService::getInstance().isTerminate())
  35.     {
  36.         if(!isConnected())
  37.         {
  38.             if(!reconnect())
  39.             {
  40.                 zThread::msleep(5000);
  41.                 continue;
  42.             }
  43.         }
  44.         
  45.         zTCPBufferClient::run();
  46.     }
  47. }

  48. bool DrawActivityClient::reconnect()
  49. {
  50.     close();
  51.     return connectToServer();
  52. }

  53. bool DrawActivityClient::msgParse(const BYTE* data, const DWORD len)
  54. {
  55.     if(len < sizeof(NullCmd))
  56.         return false;
  57.    
  58.     NullCmd* cmd = (NullCmd*)data;
  59.    
  60.     if(cmd->cmd != CMD_DRAW_ACTIVITY)
  61.         return false;
  62.    
  63.     // 转发给Lua处理
  64.     lua_State* L = LuaEngine::getInstance()->getLuaState();
  65.     if(!L) return false;
  66.    
  67.     lua_getglobal(L, "DrawActivityManager");
  68.     if(!lua_istable(L, -1))
  69.     {
  70.         lua_pop(L, 1);
  71.         return false;
  72.     }
  73.    
  74.     lua_getfield(L, -1, "onServerMessage");
  75.     if(!lua_isfunction(L, -1))
  76.     {
  77.         lua_pop(L, 2);
  78.         return false;
  79.     }
  80.    
  81.     // 压入参数:协议号,数据指针,长度
  82.     lua_pushinteger(L, ((WORD*)data)[1]); // para
  83.     lua_pushlightuserdata(L, (void*)data);
  84.     lua_pushinteger(L, len);
  85.    
  86.     if(lua_pcall(L, 3, 1, 0) != 0)
  87.     {
  88.         Zebra::logger->error("DrawActivity Lua error: %s", lua_tostring(L, -1));
  89.         lua_pop(L, 1);
  90.         return false;
  91.     }
  92.    
  93.     bool ret = lua_toboolean(L, -1);
  94.     lua_pop(L, 1);
  95.     return ret;
  96. }

  97. bool DrawActivityClient::sendToDrawServer(const void* data, DWORD len)
  98. {
  99.     return sendCmd(data, len);
  100. }

  101. void DrawActivityClient::sendToClient(DWORD userId, const void* data, DWORD len)
  102. {
  103.     SceneUser* user = SceneUserManager::getMe().getUserByID(userId);
  104.     if(user)
  105.     {
  106.         user->sendCmdToMe(data, len);
  107.     }
  108. }
复制代码
  1. <!-- config/draw_activities.xml -->
  2. <activities>
  3.     <!-- 幸运大转盘 -->
  4.     <activity id="1001" name="幸运大转盘" enabled="true"
  5.               start_time="2024-01-01 00:00:00" end_time="2024-12-31 23:59:59"
  6.               daily_limit="50" cost_type="item" cost_item="584" cost_num="1"
  7.               cost_money="100" draw_mode="single">
  8.         
  9.         <guarantee enabled="true" times="50" pool="rare" reset="true"/>
  10.         
  11.         <broadcast min_level="4">恭喜【{player}】鸿运当头,获得【{prize}】!</broadcast>
  12.         
  13.         <prizes>
  14.             <!-- 传说级(5星) -->
  15.             <prize id="10001" type="wing" item_id="1001" name="炽天使之翼"
  16.                    level="5" weight="10" broadcast="true" server_limit="3"/>
  17.             
  18.             <!-- 史诗级(4星) -->
  19.             <prize id="10002" type="item" item_id="585" count="10" name="史诗强化石"
  20.                    level="4" weight="100" broadcast="true"/>
  21.             
  22.             <!-- 稀有级(3星) -->
  23.             <prize id="10003" type="item" item_id="584" count="5" name="进阶石礼包"
  24.                    level="3" weight="500"/>
  25.             
  26.             <prize id="10004" type="money" count="50000" name="金币"
  27.                    level="3" weight="1000"/>
  28.             
  29.             <!-- 普通级(1-2星) -->
  30.             <prize id="10005" type="item" item_id="583" count="3" name="强化石"
  31.                    level="2" weight="3000"/>
  32.             
  33.             <prize id="10006" type="exp" count="10000" name="经验"
  34.                    level="1" weight="5390"/>
  35.         </prizes>
  36.     </activity>
  37.    
  38.     <!-- 十连抽宝箱 -->
  39.     <activity id="1002" name="神秘宝箱" enabled="true"
  40.               daily_limit="999" cost_type="item" cost_item="587" cost_num="10"
  41.               draw_mode="multi">
  42.         
  43.         <guarantee enabled="true" times="10" pool="purple" reset="false"/>
  44.         
  45.         <prizes>
  46.             <prize id="20001" type="pet" item_id="2001" name="神兽·青龙"
  47.                    level="5" weight="5" broadcast="true" server_limit="1" daily_limit="1"/>
  48.             
  49.             <prize id="20002" type="item" item_id="588" count="1" name="紫色装备箱"
  50.                    level="4" weight="50" broadcast="true"/>
  51.             
  52.             <!-- 保底奖品 -->
  53.             <prize id="20003" type="item" item_id="589" count="5" name="紫色碎片"
  54.                    level="4" weight="100"/>
  55.         </prizes>
  56.     </activity>
  57. </activities>
复制代码
这些是我做个翅膀随便写下 自己去修改

  1. -- scripts/jackpot/jackpot_manager.lua
  2. -- 全服奖池分红系统

  3. local JackpotManager = {}
  4. local FLDB = require("database.flserver_db")  -- 账号数据库连接
  5. local GameDB = require("database.game_db")    -- 游戏数据库连接
  6. local Timer = require("Timer")

  7. -- 配置
  8. JackpotManager.CONFIG = {
  9.     JACKPOT_LIMIT = 1000000000,  -- 10亿触发
  10.     CHECK_INTERVAL = 60000,      -- 每分钟检查一次
  11.     DISTRIBUTE_BATCH = 1000,     -- 每批处理1000人
  12.     MIN_ONLINE_TIME = 3600,      -- 最小在线时长(秒)才有资格
  13. }

  14. -- 当前状态
  15. JackpotManager.state = {
  16.     currentAmount = 0,
  17.     isDistributing = false,
  18.     lastCheckTime = 0,
  19.     currentBatch = nil
  20. }

  21. -- 初始化
  22. function JackpotManager.init()
  23.     -- 加载当前奖池金额
  24.     local row = GameDB.queryOne("SELECT current_amount, status FROM global_jackpot WHERE id=1")
  25.     if row then
  26.         JackpotManager.state.currentAmount = tonumber(row.current_amount) or 0
  27.         if tonumber(row.status) == 1 then  -- 待发放状态,继续发放
  28.             JackpotManager.resumeDistribute()
  29.         end
  30.     else
  31.         -- 初始化奖池
  32.         GameDB.execute("INSERT INTO global_jackpot (id, current_amount) VALUES (1, 0)")
  33.     end
  34.    
  35.     -- 启动定时检查
  36.     Timer.scheduleRepeat(JackpotManager.CONFIG.CHECK_INTERVAL, JackpotManager.checkAndDistribute)
  37.    
  38.     Log.info("JackpotManager initialized, current jackpot: " .. JackpotManager.state.currentAmount)
  39. end

  40. -- 奖池流入(每次抽奖抽税时调用)
  41. function JackpotManager.addToJackpot(activityId, userId, amount)
  42.     if amount <= 0 then return end
  43.    
  44.     -- 更新数据库(原子操作)
  45.     local sql = string.format(
  46.         "UPDATE global_jackpot SET current_amount = current_amount + %d, total_in = total_in + %d WHERE id=1",
  47.         amount, amount
  48.     )
  49.     local ok = GameDB.execute(sql)
  50.    
  51.     if ok then
  52.         -- 记录流入
  53.         GameDB.execute(string.format(
  54.             "INSERT INTO jackpot_inflow (activity_id, user_id, amount, inflow_time) VALUES (%d, %d, %d, %d)",
  55.             activityId, userId, amount, os.time()
  56.         ))
  57.         
  58.         -- 更新内存
  59.         JackpotManager.state.currentAmount = JackpotManager.state.currentAmount + amount
  60.         
  61.         -- 检查是否达到阈值
  62.         if JackpotManager.state.currentAmount >= JackpotManager.CONFIG.JACKPOT_LIMIT
  63.            and not JackpotManager.state.isDistributing then
  64.             Log.info("Jackpot reached limit: " .. JackpotManager.state.currentAmount)
  65.             -- 异步触发发放(避免阻塞)
  66.             Timer.once(1000, JackpotManager.startDistribute)
  67.         end
  68.     end
  69. end

  70. -- 检查并发放(定时器调用)
  71. function JackpotManager.checkAndDistribute()
  72.     if JackpotManager.state.isDistributing then return end
  73.    
  74.     -- 从数据库获取最新金额(防止多服竞争)
  75.     local row = GameDB.queryOne("SELECT current_amount, status FROM global_jackpot WHERE id=1 FOR UPDATE")
  76.     if not row then return end
  77.    
  78.     local amount = tonumber(row.current_amount) or 0
  79.     local status = tonumber(row.status) or 0
  80.    
  81.     JackpotManager.state.currentAmount = amount
  82.    
  83.     if amount >= JackpotManager.CONFIG.JACKPOT_LIMIT and status == 0 then
  84.         JackpotManager.startDistribute()
  85.     end
  86. end

  87. -- 开始发放流程
  88. function JackpotManager.startDistribute()
  89.     if JackpotManager.state.isDistributing then return false end
  90.    
  91.     JackpotManager.state.isDistributing = true
  92.     Log.info("Starting jackpot distribution, amount: " .. JackpotManager.state.currentAmount)
  93.    
  94.     -- 1. 锁定奖池状态
  95.     local ok = GameDB.execute("UPDATE global_jackpot SET status=2 WHERE id=1 AND status=0")
  96.     if not ok then
  97.         Log.error("Failed to lock jackpot, maybe another server is processing")
  98.         JackpotManager.state.isDistributing = false
  99.         return false
  100.     end
  101.    
  102.     -- 2. 查询FLServerDB获取所有有效账号
  103.     local accounts = FLDB.query([[
  104.         SELECT DISTINCT a.id as account_id,
  105.                (SELECT MAX(id) FROM game_user.user_active
  106.                 WHERE account_id=a.id AND last_login_time > UNIX_TIMESTAMP() - 2592000) as user_id
  107.         FROM account a
  108.         WHERE a.status = 1
  109.         AND a.create_time < UNIX_TIMESTAMP() - 86400  -- 注册超过1天
  110.         AND (SELECT COUNT(*) FROM account_ban ab WHERE ab.account_id=a.id AND ab.end_time > UNIX_TIMESTAMP()) = 0
  111.     ]])
  112.    
  113.     if not accounts or #accounts == 0 then
  114.         Log.error("No valid accounts found for jackpot distribution")
  115.         JackpotManager.resetJackpot()
  116.         return false
  117.     end
  118.    
  119.     -- 3. 计算每人分得
  120.     local totalAmount = JackpotManager.state.currentAmount
  121.     local playerCount = #accounts
  122.     local perAmount = math.floor(totalAmount / playerCount)
  123.     local remainder = totalAmount - (perAmount * playerCount)  -- 余数给前N个玩家
  124.    
  125.     if perAmount < 1 then
  126.         Log.warn("Jackpot amount too small for distribution")
  127.         JackpotManager.resetJackpot()
  128.         return false
  129.     end
  130.    
  131.     -- 4. 生成分红批次
  132.     local batchNo = "JP" .. os.time() .. "_" .. math.random(1000,9999)
  133.     GameDB.execute(string.format(
  134.         "INSERT INTO jackpot_dividend (jackpot_id, batch_no, total_amount, player_count, per_amount, distribute_time, status) "..
  135.         "VALUES (1, '%s', %d, %d, %d, %d, 1)",
  136.         batchNo, totalAmount, playerCount, perAmount, os.time()
  137.     ))
  138.    
  139.     JackpotManager.state.currentBatch = {
  140.         batchNo = batchNo,
  141.         accounts = accounts,
  142.         perAmount = perAmount,
  143.         remainder = remainder,
  144.         total = playerCount,
  145.         processed = 0,
  146.         success = 0,
  147.         failed = 0
  148.     }
  149.    
  150.     -- 5. 写入明细表(分批插入)
  151.     local batchInsert = {}
  152.     for i, acc in ipairs(accounts) do
  153.         local amount = perAmount
  154.         if i <= remainder then amount = amount + 1 end  -- 余数分配
  155.         
  156.         table.insert(batchInsert, string.format("('%s', %d, %d, %d, 0)",
  157.             batchNo, acc.account_id, acc.user_id or 0, amount))
  158.         
  159.         if #batchInsert >= 500 then
  160.             local sql = "INSERT INTO jackpot_player_dividend (batch_no, account_id, user_id, amount, status) VALUES " ..
  161.                        table.concat(batchInsert, ",")
  162.             GameDB.execute(sql)
  163.             batchInsert = {}
  164.         end
  165.     end
  166.     if #batchInsert > 0 then
  167.         local sql = "INSERT INTO jackpot_player_dividend (batch_no, account_id, user_id, amount, status) VALUES " ..
  168.                    table.concat(batchInsert, ",")
  169.         GameDB.execute(sql)
  170.     end
  171.    
  172.     -- 6. 开始分批发放
  173.     Timer.once(100, function() JackpotManager.processBatch(1) end)
  174.    
  175.     -- 全服广播预告
  176.     WorldBroadcast(string.format("【全服福利】奖池累计已达10亿金币,正在为%d位玩家发放分红,每人约%d金币!",
  177.         playerCount, perAmount))
  178.    
  179.     return true
  180. end

  181. -- 分批处理发放
  182. function JackpotManager.processBatch(startIdx)
  183.     local batch = JackpotManager.state.currentBatch
  184.     if not batch then return end
  185.    
  186.     local endIdx = math.min(startIdx + JackpotManager.CONFIG.DISTRIBUTE_BATCH - 1, batch.total)
  187.    
  188.     for i = startIdx, endIdx do
  189.         local acc = batch.accounts[i]
  190.         if acc then
  191.             local amount = batch.perAmount
  192.             if i <= batch.remainder then amount = amount + 1 end
  193.             
  194.             local ok, err = JackpotManager.sendToPlayer(acc, amount, batch.batchNo)
  195.             
  196.             if ok then
  197.                 batch.success = batch.success + 1
  198.                 GameDB.execute(string.format(
  199.                     "UPDATE jackpot_player_dividend SET status=1, send_time=%d WHERE batch_no='%s' AND account_id=%d",
  200.                     os.time(), batch.batchNo, acc.account_id
  201.                 ))
  202.             else
  203.                 batch.failed = batch.failed + 1
  204.                 GameDB.execute(string.format(
  205.                     "UPDATE jackpot_player_dividend SET status=2, error_msg='%s' WHERE batch_no='%s' AND account_id=%d",
  206.                     tostring(err):sub(1, 250), batch.batchNo, acc.account_id
  207.                 ))
  208.                
  209.                 -- 失败时放入邮件队列稍后重发
  210.                 JackpotManager.queueMailReward(acc.account_id, acc.user_id, amount, batch.batchNo)
  211.             end
  212.         end
  213.         
  214.         batch.processed = batch.processed + 1
  215.     end
  216.    
  217.     -- 更新进度
  218.     GameDB.execute(string.format(
  219.         "UPDATE jackpot_dividend SET status=1 WHERE batch_no='%s'",
  220.         batch.batchNo
  221.     ))
  222.    
  223.     Log.info(string.format("Jackpot distribution progress: %d/%d (success:%d, failed:%d)",
  224.         batch.processed, batch.total, batch.success, batch.failed))
  225.    
  226.     -- 继续下一批或完成
  227.     if endIdx < batch.total then
  228.         Timer.once(100, function() JackpotManager.processBatch(endIdx + 1) end)
  229.     else
  230.         JackpotManager.finishDistribute()
  231.     end
  232. end

  233. -- 发放给单个玩家
  234. function JackpotManager.sendToPlayer(accountInfo, amount, batchNo)
  235.     local accountId = accountInfo.account_id
  236.     local userId = accountInfo.user_id
  237.    
  238.     -- 方式1:玩家在线,直接加金币
  239.     if userId and userId > 0 then
  240.         local user = SceneUserManager.getUserByID(userId)
  241.         if user and user:isOnline() then
  242.             -- 直接加金币
  243.             local ok = user:addMoney(amount, "jackpot_dividend_" .. batchNo)
  244.             if ok then
  245.                 -- 发送通知
  246.                 user:sendSysMsg(string.format("恭喜您获得全服奖池分红 %d 金币!", amount))
  247.                
  248.                 -- 播放特效
  249.                 user:playEffect("jackpot_win")
  250.                
  251.                 -- 记录日志
  252.                 Log.info(string.format("Jackpot sent to online user %d (account %d), amount: %d",
  253.                     userId, accountId, amount))
  254.                 return true
  255.             end
  256.         end
  257.     end
  258.    
  259.     -- 方式2:玩家离线,写入待领取表(登录时发放)
  260.     -- 或者发送邮件
  261.     return JackpotManager.sendMailReward(accountId, userId, amount, batchNo)
  262. end

  263. -- 发送邮件奖励(离线玩家)
  264. function JackpotManager.sendMailReward(accountId, userId, amount, batchNo)
  265.     local mailSql = string.format([[
  266.         INSERT INTO mail_system (receiver_id, title, content, attachment, send_time, expire_time, status)
  267.         VALUES (%d, '全服奖池分红', '恭喜!全服奖池累计10亿金币,您分得%d金币(批次:%s)',
  268.                 '{"money":%d}', %d, %d, 0)
  269.     ]], userId or accountId, amount, batchNo, amount, os.time(), os.time() + 2592000)  -- 30天过期
  270.    
  271.     local ok = GameDB.execute(mailSql)
  272.     if ok then
  273.         Log.info(string.format("Jackpot mail sent to account %d, amount: %d", accountId, amount))
  274.         return true, "mail_sent"
  275.     else
  276.         return false, "mail_failed"
  277.     end
  278. end

  279. -- 队列化邮件奖励(用于失败重试)
  280. function JackpotManager.queueMailReward(accountId, userId, amount, batchNo)
  281.     -- 写入重试队列
  282.     GameDB.execute(string.format(
  283.         "INSERT INTO jackpot_retry_queue (batch_no, account_id, user_id, amount, create_time) VALUES ('%s', %d, %d, %d, %d)",
  284.         batchNo, accountId, userId or 0, amount, os.time()
  285.     ))
  286. end

  287. -- 完成发放
  288. function JackpotManager.finishDistribute()
  289.     local batch = JackpotManager.state.currentBatch
  290.     if not batch then return end
  291.    
  292.     -- 更新总表状态
  293.     GameDB.execute(string.format(
  294.         "UPDATE jackpot_dividend SET status=2 WHERE batch_no='%s'",
  295.         batch.batchNo
  296.     ))
  297.    
  298.     -- 清空奖池
  299.     GameDB.execute("UPDATE global_jackpot SET current_amount=0, status=0, last_winner_time=" .. os.time() .. " WHERE id=1")
  300.    
  301.     JackpotManager.state.currentAmount = 0
  302.     JackpotManager.state.isDistributing = false
  303.     JackpotManager.state.currentBatch = nil
  304.    
  305.     -- 全服广播完成
  306.     WorldBroadcast(string.format("【全服福利】奖池分红发放完成!共%d位玩家分享10亿金币!", batch.total))
  307.    
  308.     Log.info(string.format("Jackpot distribution completed. Total: %d, Success: %d, Failed: %d",
  309.         batch.total, batch.success, batch.failed))
  310. end

  311. -- 重置奖池(出错时)
  312. function JackpotManager.resetJackpot()
  313.     GameDB.execute("UPDATE global_jackpot SET status=0 WHERE id=1")
  314.     JackpotManager.state.isDistributing = false
  315.     JackpotManager.state.currentBatch = nil
  316. end

  317. -- 恢复发放(服务器重启后继续)
  318. function JackpotManager.resumeDistribute()
  319.     Log.info("Resuming jackpot distribution...")
  320.    
  321.     local row = GameDB.queryOne("SELECT batch_no FROM jackpot_dividend WHERE status=1 ORDER BY id DESC LIMIT 1")
  322.     if row then
  323.         local batchNo = row.batch_no
  324.         
  325.         -- 加载未完成的明细
  326.         local list = GameDB.query(string.format(
  327.             "SELECT account_id, user_id, amount FROM jackpot_player_dividend WHERE batch_no='%s' AND status=0",
  328.             batchNo
  329.         ))
  330.         
  331.         if list and #list > 0 then
  332.             JackpotManager.state.currentBatch = {
  333.                 batchNo = batchNo,
  334.                 accounts = list,
  335.                 perAmount = 0,  -- 不需要重新计算
  336.                 total = #list,
  337.                 processed = 0,
  338.                 success = 0,
  339.                 failed = 0
  340.             }
  341.             
  342.             -- 继续发放
  343.             Timer.once(1000, function() JackpotManager.processBatch(1) end)
  344.             Log.info("Resumed batch " .. batchNo .. " with " .. #list .. " remaining players")
  345.         else
  346.             -- 没有待发放的,标记完成
  347.             GameDB.execute(string.format("UPDATE jackpot_dividend SET status=2 WHERE batch_no='%s'", batchNo))
  348.             GameDB.execute("UPDATE global_jackpot SET current_amount=0, status=0 WHERE id=1")
  349.             JackpotManager.state.isDistributing = false
  350.         end
  351.     else
  352.         JackpotManager.resetJackpot()
  353.     end
  354. end

  355. -- GM命令:手动触发
  356. function JackpotManager.gmForceDistribute()
  357.     if JackpotManager.state.isDistributing then
  358.         return false, "正在发放中"
  359.     end
  360.    
  361.     -- 检查当前金额
  362.     local row = GameDB.queryOne("SELECT current_amount FROM global_jackpot WHERE id=1")
  363.     if not row or tonumber(row.current_amount) < 1000000 then  -- 至少100万才允许手动触发
  364.         return false, "奖池金额不足(至少100万)"
  365.     end
  366.    
  367.     JackpotManager.startDistribute()
  368.     return true, "已开始发放"
  369. end

  370. -- GM命令:查看奖池状态
  371. function JackpotManager.gmStatus()
  372.     local row = GameDB.queryOne("SELECT * FROM global_jackpot WHERE id=1")
  373.     if row then
  374.         return {
  375.             current = tonumber(row.current_amount),
  376.             total_in = tonumber(row.total_in),
  377.             total_out = tonumber(row.total_out),
  378.             status = tonumber(row.status),
  379.             is_distributing = JackpotManager.state.isDistributing
  380.         }
  381.     end
  382.     return nil
  383. end

  384. -- 注册到全局
  385. _G.JackpotManager = JackpotManager
  386. return JackpotManager
复制代码
  1. -- 玩家抽奖数据
  2. CREATE TABLE draw_player_data (
  3.     user_id BIGINT NOT NULL,
  4.     activity_id INT NOT NULL,
  5.     total_count INT DEFAULT 0 COMMENT '总抽奖次数',
  6.     today_count INT DEFAULT 0 COMMENT '今日次数',
  7.     last_draw_time INT DEFAULT 0 COMMENT '最后抽奖时间',
  8.     guarantee_count INT DEFAULT 0 COMMENT '保底计数',
  9.     update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  10.     PRIMARY KEY (user_id, activity_id),
  11.     INDEX idx_activity (activity_id)
  12. ) ENGINE=InnoDB;

  13. -- 抽奖日志(分表或按月分区)
  14. CREATE TABLE draw_logs (
  15.     id BIGINT AUTO_INCREMENT,
  16.     user_id BIGINT NOT NULL,
  17.     activity_id INT NOT NULL,
  18.     prize_type VARCHAR(16),
  19.     item_id INT,
  20.     count INT,
  21.     prize_level INT COMMENT '奖品等级',
  22.     draw_time INT,
  23.     PRIMARY KEY (id),
  24.     INDEX idx_user_time (user_id, draw_time),
  25.     INDEX idx_activity_time (activity_id, draw_time)
  26. ) ENGINE=InnoDB PARTITION BY RANGE (draw_time) (
  27.     PARTITION p202401 VALUES LESS THAN (1706745600),
  28.     PARTITION p202402 VALUES LESS THAN (1709251200),
  29.     PARTITION p202403 VALUES LESS THAN (1711929600)
  30. );

  31. -- 全服库存
  32. CREATE TABLE draw_stock (
  33.     activity_id INT NOT NULL,
  34.     prize_id INT NOT NULL,
  35.     remain_count INT DEFAULT 0,
  36.     total_count INT DEFAULT 0,
  37.     update_time TIMESTAMP,
  38.     PRIMARY KEY (activity_id, prize_id)
  39. ) ENGINE=InnoDB;

  40. -- 活动配置(用于热重载备份)
  41. CREATE TABLE draw_config (
  42.     activity_id INT PRIMARY KEY,
  43.     config_json TEXT,
  44.     update_time TIMESTAMP,
  45.     operator VARCHAR(64)
  46. ) ENGINE=InnoDB;
复制代码


回复

使用道具 举报

发表于 2026-2-9 19:03:34 | 显示全部楼层
虽不懂但谢谢
回复 支持 反对

使用道具 举报

发表于 2026-2-9 19:56:25 | 显示全部楼层
感觉好高级·!!!
回复 支持 反对

使用道具 举报

发表于 2026-2-9 21:03:55 | 显示全部楼层
感觉好高级 谢谢大佬的分享
回复 支持 反对

使用道具 举报

发表于 2026-2-9 21:39:20 | 显示全部楼层
虽然不会,感谢分享
回复 支持 反对

使用道具 举报

发表于 2026-2-10 08:25:00 | 显示全部楼层
谢谢分享
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

本站内容如若侵犯到您的权益,请来电来函告知,我们会尽快处理!
联系QQ:1953150286,2251387361,123784736,免责申明

排行榜|联系我们|小黑屋|手机版|Archiver|游戏藏宝湾 |

GMT+8, 2026-6-4 23:12 , Processed in 0.207143 second(s), 18 queries .

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表