bfdz49 发表于 2026-2-9 18:42:54

征途抽奖脚本

// CmdDrawActivity.h
#ifndef CMD_DRAW_ACTIVITY_H
#define CMD_DRAW_ACTIVITY_H

#include "zType.h"

namespace Cmd {
namespace Draw {

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

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

// 奖品类型
enum PrizeType {
    PRIZE_TYPE_ITEM   = 1,    // 道具
    PRIZE_TYPE_MONEY    = 2,    // 金币
    PRIZE_TYPE_EXP      = 3,    // 经验
    PRIZE_TYPE_WING   = 4,    // 翅膀
    PRIZE_TYPE_PET      = 5,    // 宠物
    PRIZE_TYPE_TITLE    = 6,    // 称号
    PRIZE_TYPE_NONE   = 99,   // 未中奖
};

// 检查请求
struct stDrawCheckReq {
    WORD cmd;         // 0x3000
    WORD para;          // 1
    DWORD activityId;   // 活动ID
    DWORD drawTimes;    // 抽几次(1或10)
};

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

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

// 单次奖品信息
struct PrizeInfo {
    DWORD prizeId;      // 奖品配置ID
    DWORD type;         // PrizeType
    DWORD itemId;       // 物品ID(如果是物品)
    DWORD count;      // 数量
    DWORD level;      // 等级/品质
    DWORD broadcast;    // 是否广播(0/1)
    char name;      // 奖品名称
};

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

// 活动信息请求
struct stDrawInfoReq {
    WORD cmd;
    WORD para;          // 5
    DWORD activityId;   // 0表示请求所有活动
};

// 活动基本信息
struct ActivityInfo {
    DWORD id;
    char name;
    char desc;
    BYTE enabled;       // 是否开启
    DWORD startTime;    // 开始时间
    DWORD endTime;      // 结束时间
    DWORD dailyLimit;   // 每日限制
    DWORD costItemId;   // 消耗道具
    DWORD costItemNum;// 消耗数量
    DWORD costMoney;    // 替代元宝
    BYTE hasGuarantee;// 是否有保底
    DWORD guaranteeTimes; // 保底次数
};

// 活动信息返回
struct stDrawInfoRet {
    WORD cmd;
    WORD para;          // 6
    DWORD count;
    // ActivityInfo activities;
};

// 抽奖记录
struct DrawLog {
    DWORD logId;
    DWORD activityId;
    DWORD prizeType;
    DWORD itemId;
    DWORD count;
    DWORD time;
    char prizeName;
};

// 记录请求
struct stDrawLogReq {
    WORD cmd;
    WORD para;          // 7
    DWORD activityId;   // 0表示所有
    DWORD startTime;
    DWORD endTime;
};

// 记录返回
struct stDrawLogRet {
    WORD cmd;
    WORD para;          // 8
    DWORD count;
    // DrawLog logs;
};

// 全服广播(服务器推送给所有客户端)
struct stDrawGlobalNotify {
    WORD cmd;
    WORD para;          // 9
    DWORD activityId;
    char playerName;
    char prizeName;
    DWORD prizeLevel;   // 奖品等级(用于显示不同颜色)
    char msg;      // 完整消息
};

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

struct stDrawGMCmd {
    WORD cmd;
    WORD para;          // 10
    DWORD gmCmd;
    DWORD targetId;   // 玩家ID或活动ID
    DWORD param1;
    DWORD param2;
    char paramStr;
};

#pragma pack()
}
}
#endif
// DrawActivityClient.h
#ifndef DRAW_ACTIVITY_CLIENT_H
#define DRAW_ACTIVITY_CLIENT_H

#include "zTCPClient.h"
#include "zSingleton.h"
#include "CmdDrawActivity.h"

using namespace Cmd::Draw;

class DrawActivityClient : public zTCPBufferClient, public zSingleton<DrawActivityClient>
{
public:
    DrawActivityClient(const std::string& ip, WORD port);
    virtual ~DrawActivityClient();
   
    bool connectToServer();
    virtual void run();
    virtual bool msgParse(const BYTE* data, const DWORD len);
   
    // 发送给抽奖服务器
    bool sendToDrawServer(const void* data, DWORD len);
   
    // 转发给客户端
    void sendToClient(DWORD userId, const void* data, DWORD len);
   
private:
    std::string serverIp;
    WORD serverPort;
    bool reconnect();
};

#define DRAW_CLIENT DrawActivityClient::getInstance()

#endif
// DrawActivityClient.cpp
#include "DrawActivityClient.h"
#include "SceneUserManager.h"
#include "SceneUser.h"
#include "ScenesServer.h"
#include "LuaEngine.h"

DrawActivityClient::DrawActivityClient(const std::string& ip, WORD port)
    : serverIp(ip), serverPort(port)
{
}

DrawActivityClient::~DrawActivityClient()
{
}

bool DrawActivityClient::connectToServer()
{
    if(!connect(serverIp.c_str(), serverPort))
    {
      Zebra::logger->error("连接抽奖服务器失败 %s:%d", serverIp.c_str(), serverPort);
      return false;
    }
   
    // 发送注册包
    struct {
      WORD cmd;
      WORD para;
      DWORD serverId;
      DWORD serverType;
    } regCmd = {0x3000, 0, ScenesService::getInstance().getServerID(), 1};
   
    return sendCmd(&regCmd, sizeof(regCmd));
}

void DrawActivityClient::run()
{
    while(!ScenesService::getInstance().isTerminate())
    {
      if(!isConnected())
      {
            if(!reconnect())
            {
                zThread::msleep(5000);
                continue;
            }
      }
      
      zTCPBufferClient::run();
    }
}

bool DrawActivityClient::reconnect()
{
    close();
    return connectToServer();
}

bool DrawActivityClient::msgParse(const BYTE* data, const DWORD len)
{
    if(len < sizeof(NullCmd))
      return false;
   
    NullCmd* cmd = (NullCmd*)data;
   
    if(cmd->cmd != CMD_DRAW_ACTIVITY)
      return false;
   
    // 转发给Lua处理
    lua_State* L = LuaEngine::getInstance()->getLuaState();
    if(!L) return false;
   
    lua_getglobal(L, "DrawActivityManager");
    if(!lua_istable(L, -1))
    {
      lua_pop(L, 1);
      return false;
    }
   
    lua_getfield(L, -1, "onServerMessage");
    if(!lua_isfunction(L, -1))
    {
      lua_pop(L, 2);
      return false;
    }
   
    // 压入参数:协议号,数据指针,长度
    lua_pushinteger(L, ((WORD*)data)); // para
    lua_pushlightuserdata(L, (void*)data);
    lua_pushinteger(L, len);
   
    if(lua_pcall(L, 3, 1, 0) != 0)
    {
      Zebra::logger->error("DrawActivity Lua error: %s", lua_tostring(L, -1));
      lua_pop(L, 1);
      return false;
    }
   
    bool ret = lua_toboolean(L, -1);
    lua_pop(L, 1);
    return ret;
}

bool DrawActivityClient::sendToDrawServer(const void* data, DWORD len)
{
    return sendCmd(data, len);
}

void DrawActivityClient::sendToClient(DWORD userId, const void* data, DWORD len)
{
    SceneUser* user = SceneUserManager::getMe().getUserByID(userId);
    if(user)
    {
      user->sendCmdToMe(data, len);
    }
}
<!-- config/draw_activities.xml -->
<activities>
    <!-- 幸运大转盘 -->
    <activity id="1001" name="幸运大转盘" enabled="true"
            start_time="2024-01-01 00:00:00" end_time="2024-12-31 23:59:59"
            daily_limit="50" cost_type="item" cost_item="584" cost_num="1"
            cost_money="100" draw_mode="single">
      
      <guarantee enabled="true" times="50" pool="rare" reset="true"/>
      
      <broadcast min_level="4">恭喜【{player}】鸿运当头,获得【{prize}】!</broadcast>
      
      <prizes>
            <!-- 传说级(5星) -->
            <prize id="10001" type="wing" item_id="1001" name="炽天使之翼"
                   level="5" weight="10" broadcast="true" server_limit="3"/>
            
            <!-- 史诗级(4星) -->
            <prize id="10002" type="item" item_id="585" count="10" name="史诗强化石"
                   level="4" weight="100" broadcast="true"/>
            
            <!-- 稀有级(3星) -->
            <prize id="10003" type="item" item_id="584" count="5" name="进阶石礼包"
                   level="3" weight="500"/>
            
            <prize id="10004" type="money" count="50000" name="金币"
                   level="3" weight="1000"/>
            
            <!-- 普通级(1-2星) -->
            <prize id="10005" type="item" item_id="583" count="3" name="强化石"
                   level="2" weight="3000"/>
            
            <prize id="10006" type="exp" count="10000" name="经验"
                   level="1" weight="5390"/>
      </prizes>
    </activity>
   
    <!-- 十连抽宝箱 -->
    <activity id="1002" name="神秘宝箱" enabled="true"
            daily_limit="999" cost_type="item" cost_item="587" cost_num="10"
            draw_mode="multi">
      
      <guarantee enabled="true" times="10" pool="purple" reset="false"/>
      
      <prizes>
            <prize id="20001" type="pet" item_id="2001" name="神兽·青龙"
                   level="5" weight="5" broadcast="true" server_limit="1" daily_limit="1"/>
            
            <prize id="20002" type="item" item_id="588" count="1" name="紫色装备箱"
                   level="4" weight="50" broadcast="true"/>
            
            <!-- 保底奖品 -->
            <prize id="20003" type="item" item_id="589" count="5" name="紫色碎片"
                   level="4" weight="100"/>
      </prizes>
    </activity>
</activities>
这些是我做个翅膀随便写下 自己去修改

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

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

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

-- 当前状态
JackpotManager.state = {
    currentAmount = 0,
    isDistributing = false,
    lastCheckTime = 0,
    currentBatch = nil
}

-- 初始化
function JackpotManager.init()
    -- 加载当前奖池金额
    local row = GameDB.queryOne("SELECT current_amount, status FROM global_jackpot WHERE id=1")
    if row then
      JackpotManager.state.currentAmount = tonumber(row.current_amount) or 0
      if tonumber(row.status) == 1 then-- 待发放状态,继续发放
            JackpotManager.resumeDistribute()
      end
    else
      -- 初始化奖池
      GameDB.execute("INSERT INTO global_jackpot (id, current_amount) VALUES (1, 0)")
    end
   
    -- 启动定时检查
    Timer.scheduleRepeat(JackpotManager.CONFIG.CHECK_INTERVAL, JackpotManager.checkAndDistribute)
   
    Log.info("JackpotManager initialized, current jackpot: " .. JackpotManager.state.currentAmount)
end

-- 奖池流入(每次抽奖抽税时调用)
function JackpotManager.addToJackpot(activityId, userId, amount)
    if amount <= 0 then return end
   
    -- 更新数据库(原子操作)
    local sql = string.format(
      "UPDATE global_jackpot SET current_amount = current_amount + %d, total_in = total_in + %d WHERE id=1",
      amount, amount
    )
    local ok = GameDB.execute(sql)
   
    if ok then
      -- 记录流入
      GameDB.execute(string.format(
            "INSERT INTO jackpot_inflow (activity_id, user_id, amount, inflow_time) VALUES (%d, %d, %d, %d)",
            activityId, userId, amount, os.time()
      ))
      
      -- 更新内存
      JackpotManager.state.currentAmount = JackpotManager.state.currentAmount + amount
      
      -- 检查是否达到阈值
      if JackpotManager.state.currentAmount >= JackpotManager.CONFIG.JACKPOT_LIMIT
         and not JackpotManager.state.isDistributing then
            Log.info("Jackpot reached limit: " .. JackpotManager.state.currentAmount)
            -- 异步触发发放(避免阻塞)
            Timer.once(1000, JackpotManager.startDistribute)
      end
    end
end

-- 检查并发放(定时器调用)
function JackpotManager.checkAndDistribute()
    if JackpotManager.state.isDistributing then return end
   
    -- 从数据库获取最新金额(防止多服竞争)
    local row = GameDB.queryOne("SELECT current_amount, status FROM global_jackpot WHERE id=1 FOR UPDATE")
    if not row then return end
   
    local amount = tonumber(row.current_amount) or 0
    local status = tonumber(row.status) or 0
   
    JackpotManager.state.currentAmount = amount
   
    if amount >= JackpotManager.CONFIG.JACKPOT_LIMIT and status == 0 then
      JackpotManager.startDistribute()
    end
end

-- 开始发放流程
function JackpotManager.startDistribute()
    if JackpotManager.state.isDistributing then return false end
   
    JackpotManager.state.isDistributing = true
    Log.info("Starting jackpot distribution, amount: " .. JackpotManager.state.currentAmount)
   
    -- 1. 锁定奖池状态
    local ok = GameDB.execute("UPDATE global_jackpot SET status=2 WHERE id=1 AND status=0")
    if not ok then
      Log.error("Failed to lock jackpot, maybe another server is processing")
      JackpotManager.state.isDistributing = false
      return false
    end
   
    -- 2. 查询FLServerDB获取所有有效账号
    local accounts = FLDB.query([[
      SELECT DISTINCT a.id as account_id,
               (SELECT MAX(id) FROM game_user.user_active
                WHERE account_id=a.id AND last_login_time > UNIX_TIMESTAMP() - 2592000) as user_id
      FROM account a
      WHERE a.status = 1
      AND a.create_time < UNIX_TIMESTAMP() - 86400-- 注册超过1天
      AND (SELECT COUNT(*) FROM account_ban ab WHERE ab.account_id=a.id AND ab.end_time > UNIX_TIMESTAMP()) = 0
    ]])
   
    if not accounts or #accounts == 0 then
      Log.error("No valid accounts found for jackpot distribution")
      JackpotManager.resetJackpot()
      return false
    end
   
    -- 3. 计算每人分得
    local totalAmount = JackpotManager.state.currentAmount
    local playerCount = #accounts
    local perAmount = math.floor(totalAmount / playerCount)
    local remainder = totalAmount - (perAmount * playerCount)-- 余数给前N个玩家
   
    if perAmount < 1 then
      Log.warn("Jackpot amount too small for distribution")
      JackpotManager.resetJackpot()
      return false
    end
   
    -- 4. 生成分红批次
    local batchNo = "JP" .. os.time() .. "_" .. math.random(1000,9999)
    GameDB.execute(string.format(
      "INSERT INTO jackpot_dividend (jackpot_id, batch_no, total_amount, player_count, per_amount, distribute_time, status) "..
      "VALUES (1, '%s', %d, %d, %d, %d, 1)",
      batchNo, totalAmount, playerCount, perAmount, os.time()
    ))
   
    JackpotManager.state.currentBatch = {
      batchNo = batchNo,
      accounts = accounts,
      perAmount = perAmount,
      remainder = remainder,
      total = playerCount,
      processed = 0,
      success = 0,
      failed = 0
    }
   
    -- 5. 写入明细表(分批插入)
    local batchInsert = {}
    for i, acc in ipairs(accounts) do
      local amount = perAmount
      if i <= remainder then amount = amount + 1 end-- 余数分配
      
      table.insert(batchInsert, string.format("('%s', %d, %d, %d, 0)",
            batchNo, acc.account_id, acc.user_id or 0, amount))
      
      if #batchInsert >= 500 then
            local sql = "INSERT INTO jackpot_player_dividend (batch_no, account_id, user_id, amount, status) VALUES " ..
                     table.concat(batchInsert, ",")
            GameDB.execute(sql)
            batchInsert = {}
      end
    end
    if #batchInsert > 0 then
      local sql = "INSERT INTO jackpot_player_dividend (batch_no, account_id, user_id, amount, status) VALUES " ..
                   table.concat(batchInsert, ",")
      GameDB.execute(sql)
    end
   
    -- 6. 开始分批发放
    Timer.once(100, function() JackpotManager.processBatch(1) end)
   
    -- 全服广播预告
    WorldBroadcast(string.format("【全服福利】奖池累计已达10亿金币,正在为%d位玩家发放分红,每人约%d金币!",
      playerCount, perAmount))
   
    return true
end

-- 分批处理发放
function JackpotManager.processBatch(startIdx)
    local batch = JackpotManager.state.currentBatch
    if not batch then return end
   
    local endIdx = math.min(startIdx + JackpotManager.CONFIG.DISTRIBUTE_BATCH - 1, batch.total)
   
    for i = startIdx, endIdx do
      local acc = batch.accounts
      if acc then
            local amount = batch.perAmount
            if i <= batch.remainder then amount = amount + 1 end
            
            local ok, err = JackpotManager.sendToPlayer(acc, amount, batch.batchNo)
            
            if ok then
                batch.success = batch.success + 1
                GameDB.execute(string.format(
                  "UPDATE jackpot_player_dividend SET status=1, send_time=%d WHERE batch_no='%s' AND account_id=%d",
                  os.time(), batch.batchNo, acc.account_id
                ))
            else
                batch.failed = batch.failed + 1
                GameDB.execute(string.format(
                  "UPDATE jackpot_player_dividend SET status=2, error_msg='%s' WHERE batch_no='%s' AND account_id=%d",
                  tostring(err):sub(1, 250), batch.batchNo, acc.account_id
                ))
               
                -- 失败时放入邮件队列稍后重发
                JackpotManager.queueMailReward(acc.account_id, acc.user_id, amount, batch.batchNo)
            end
      end
      
      batch.processed = batch.processed + 1
    end
   
    -- 更新进度
    GameDB.execute(string.format(
      "UPDATE jackpot_dividend SET status=1 WHERE batch_no='%s'",
      batch.batchNo
    ))
   
    Log.info(string.format("Jackpot distribution progress: %d/%d (success:%d, failed:%d)",
      batch.processed, batch.total, batch.success, batch.failed))
   
    -- 继续下一批或完成
    if endIdx < batch.total then
      Timer.once(100, function() JackpotManager.processBatch(endIdx + 1) end)
    else
      JackpotManager.finishDistribute()
    end
end

-- 发放给单个玩家
function JackpotManager.sendToPlayer(accountInfo, amount, batchNo)
    local accountId = accountInfo.account_id
    local userId = accountInfo.user_id
   
    -- 方式1:玩家在线,直接加金币
    if userId and userId > 0 then
      local user = SceneUserManager.getUserByID(userId)
      if user and user:isOnline() then
            -- 直接加金币
            local ok = user:addMoney(amount, "jackpot_dividend_" .. batchNo)
            if ok then
                -- 发送通知
                user:sendSysMsg(string.format("恭喜您获得全服奖池分红 %d 金币!", amount))
               
                -- 播放特效
                user:playEffect("jackpot_win")
               
                -- 记录日志
                Log.info(string.format("Jackpot sent to online user %d (account %d), amount: %d",
                  userId, accountId, amount))
                return true
            end
      end
    end
   
    -- 方式2:玩家离线,写入待领取表(登录时发放)
    -- 或者发送邮件
    return JackpotManager.sendMailReward(accountId, userId, amount, batchNo)
end

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

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

-- 完成发放
function JackpotManager.finishDistribute()
    local batch = JackpotManager.state.currentBatch
    if not batch then return end
   
    -- 更新总表状态
    GameDB.execute(string.format(
      "UPDATE jackpot_dividend SET status=2 WHERE batch_no='%s'",
      batch.batchNo
    ))
   
    -- 清空奖池
    GameDB.execute("UPDATE global_jackpot SET current_amount=0, status=0, last_winner_time=" .. os.time() .. " WHERE id=1")
   
    JackpotManager.state.currentAmount = 0
    JackpotManager.state.isDistributing = false
    JackpotManager.state.currentBatch = nil
   
    -- 全服广播完成
    WorldBroadcast(string.format("【全服福利】奖池分红发放完成!共%d位玩家分享10亿金币!", batch.total))
   
    Log.info(string.format("Jackpot distribution completed. Total: %d, Success: %d, Failed: %d",
      batch.total, batch.success, batch.failed))
end

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

-- 恢复发放(服务器重启后继续)
function JackpotManager.resumeDistribute()
    Log.info("Resuming jackpot distribution...")
   
    local row = GameDB.queryOne("SELECT batch_no FROM jackpot_dividend WHERE status=1 ORDER BY id DESC LIMIT 1")
    if row then
      local batchNo = row.batch_no
      
      -- 加载未完成的明细
      local list = GameDB.query(string.format(
            "SELECT account_id, user_id, amount FROM jackpot_player_dividend WHERE batch_no='%s' AND status=0",
            batchNo
      ))
      
      if list and #list > 0 then
            JackpotManager.state.currentBatch = {
                batchNo = batchNo,
                accounts = list,
                perAmount = 0,-- 不需要重新计算
                total = #list,
                processed = 0,
                success = 0,
                failed = 0
            }
            
            -- 继续发放
            Timer.once(1000, function() JackpotManager.processBatch(1) end)
            Log.info("Resumed batch " .. batchNo .. " with " .. #list .. " remaining players")
      else
            -- 没有待发放的,标记完成
            GameDB.execute(string.format("UPDATE jackpot_dividend SET status=2 WHERE batch_no='%s'", batchNo))
            GameDB.execute("UPDATE global_jackpot SET current_amount=0, status=0 WHERE id=1")
            JackpotManager.state.isDistributing = false
      end
    else
      JackpotManager.resetJackpot()
    end
end

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

-- GM命令:查看奖池状态
function JackpotManager.gmStatus()
    local row = GameDB.queryOne("SELECT * FROM global_jackpot WHERE id=1")
    if row then
      return {
            current = tonumber(row.current_amount),
            total_in = tonumber(row.total_in),
            total_out = tonumber(row.total_out),
            status = tonumber(row.status),
            is_distributing = JackpotManager.state.isDistributing
      }
    end
    return nil
end

-- 注册到全局
_G.JackpotManager = JackpotManager
return JackpotManager
-- 玩家抽奖数据
CREATE TABLE draw_player_data (
    user_id BIGINT NOT NULL,
    activity_id INT NOT NULL,
    total_count INT DEFAULT 0 COMMENT '总抽奖次数',
    today_count INT DEFAULT 0 COMMENT '今日次数',
    last_draw_time INT DEFAULT 0 COMMENT '最后抽奖时间',
    guarantee_count INT DEFAULT 0 COMMENT '保底计数',
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, activity_id),
    INDEX idx_activity (activity_id)
) ENGINE=InnoDB;

-- 抽奖日志(分表或按月分区)
CREATE TABLE draw_logs (
    id BIGINT AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    activity_id INT NOT NULL,
    prize_type VARCHAR(16),
    item_id INT,
    count INT,
    prize_level INT COMMENT '奖品等级',
    draw_time INT,
    PRIMARY KEY (id),
    INDEX idx_user_time (user_id, draw_time),
    INDEX idx_activity_time (activity_id, draw_time)
) ENGINE=InnoDB PARTITION BY RANGE (draw_time) (
    PARTITION p202401 VALUES LESS THAN (1706745600),
    PARTITION p202402 VALUES LESS THAN (1709251200),
    PARTITION p202403 VALUES LESS THAN (1711929600)
);

-- 全服库存
CREATE TABLE draw_stock (
    activity_id INT NOT NULL,
    prize_id INT NOT NULL,
    remain_count INT DEFAULT 0,
    total_count INT DEFAULT 0,
    update_time TIMESTAMP,
    PRIMARY KEY (activity_id, prize_id)
) ENGINE=InnoDB;

-- 活动配置(用于热重载备份)
CREATE TABLE draw_config (
    activity_id INT PRIMARY KEY,
    config_json TEXT,
    update_time TIMESTAMP,
    operator VARCHAR(64)
) ENGINE=InnoDB;


大大胖墩 发表于 2026-2-9 19:03:34

虽不懂但谢谢

绝对疯子 发表于 2026-2-9 19:56:25

感觉好高级·!!!:

绝恋″℃ 发表于 2026-2-9 21:03:55

感觉好高级 谢谢大佬的分享

liyicheng556 发表于 2026-2-9 21:39:20

虽然不会,感谢分享:

176034095 发表于 2026-2-10 08:25:00

谢谢分享
页: [1]
查看完整版本: 征途抽奖脚本

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