返回博客
Guide4 分钟阅读

FiveM 法庭与法律系统:编写真实审判

为您的FiveM服务器添加一个完整的法庭系统。法官工具、律师角色、证据UI以及适合深度角色扮演的最佳法律系统脚本。

Agency Scripts

Agency Scripts 创始人兼首席开发者

法律系统架构

法院及法律系统将 FiveM 角色扮演服务器从基础的警匪玩法提升为有法律效力和程序正义的结构化社会。系统涵盖警方使用的搜查和逮捕令、法庭听证流程、辩护和控诉律师角色、证据管理系统(追踪实体及数字证据)及罚款、监禁和替代处罚的判决框架。架构以案件管理数据库为核心,追踪从起诉、提审、审判到裁决的每个法律程序。每个案件关联相关当事人、其角色、提交的证据及最终结果。数据永久保存,构建服务器的法律历史,供玩家和官员在未来程序中参考。

法律记录的数据库架构

您的数据库需要处理逮捕令、法庭案件、证据物品和法律记录。架构通过共享证据系统将警方调查与法庭程序连接起来,调查中收集的物品成为庭审中展示的证据。设计表格以维护法律程序中每个操作的完整审计轨迹:

CREATE TABLE IF NOT EXISTS warrants (
    id INT AUTO_INCREMENT PRIMARY KEY,
    type ENUM('arrest', 'search', 'bench') NOT NULL,
    target_citizenid VARCHAR(50) NOT NULL,
    target_name VARCHAR(100) NOT NULL,
    reason TEXT NOT NULL,
    issued_by VARCHAR(50) NOT NULL,
    approved_by VARCHAR(50) DEFAULT NULL,
    status ENUM('pending', 'active', 'executed', 'expired', 'revoked') DEFAULT 'pending',
    issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP NULL,
    executed_at TIMESTAMP NULL,
    INDEX idx_target (target_citizenid),
    INDEX idx_status (status)
);

CREATE TABLE IF NOT EXISTS court_cases (
    id INT AUTO_INCREMENT PRIMARY KEY,
    case_number VARCHAR(20) UNIQUE NOT NULL,
    defendant_citizenid VARCHAR(50) NOT NULL,
    defendant_name VARCHAR(100) NOT NULL,
    prosecutor_citizenid VARCHAR(50) DEFAULT NULL,
    defense_citizenid VARCHAR(50) DEFAULT NULL,
    judge_citizenid VARCHAR(50) DEFAULT NULL,
    charges TEXT NOT NULL,
    status ENUM('filed', 'arraignment', 'pretrial', 'trial', 'verdict', 'closed', 'dismissed') DEFAULT 'filed',
    plea ENUM('not_guilty', 'guilty', 'no_contest') DEFAULT NULL,
    verdict ENUM('guilty', 'not_guilty', 'mistrial', 'dismissed') DEFAULT NULL,
    sentence TEXT DEFAULT NULL,
    fine_amount INT DEFAULT 0,
    jail_minutes INT DEFAULT 0,
    filed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    hearing_scheduled TIMESTAMP NULL,
    closed_at TIMESTAMP NULL,
    INDEX idx_defendant (defendant_citizenid),
    INDEX idx_status (status)
);

CREATE TABLE IF NOT EXISTS evidence (
    id INT AUTO_INCREMENT PRIMARY KEY,
    case_id INT DEFAULT NULL,
    type ENUM('physical', 'digital', 'testimony', 'document', 'photo') NOT NULL,
    label VARCHAR(200) NOT NULL,
    description TEXT DEFAULT NULL,
    collected_by VARCHAR(50) NOT NULL,
    collected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    chain_of_custody TEXT DEFAULT NULL,
    is_admitted BOOLEAN DEFAULT FALSE,
    FOREIGN KEY (case_id) REFERENCES court_cases(id)
);

CREATE TABLE IF NOT EXISTS legal_records (
    id INT AUTO_INCREMENT PRIMARY KEY,
    citizenid VARCHAR(50) NOT NULL,
    record_type ENUM('conviction', 'acquittal', 'fine', 'warning', 'restraining_order') NOT NULL,
    description TEXT NOT NULL,
    case_id INT DEFAULT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expunged BOOLEAN DEFAULT FALSE,
    INDEX idx_citizen (citizenid),
    FOREIGN KEY (case_id) REFERENCES court_cases(id)
);

通缉令系统

通缉令是警方调查与行动之间的法律桥梁。逮捕令授权警员拘留特定人员,搜查令允许搜查某处房产或车辆,法庭令由法官在某人未出庭时签发。警察通过 MDT 创建通缉令请求,进入待审队列。法官或授权官员审查请求,检查合理理由描述,批准或拒绝。批准的通缉令生效,警员与目标人物互动时会在 MDT 上显示,提醒存在通缉令。实现通缉令过期机制,避免通缉令永久存在,逮捕令有效期为7个现实天,搜查令批准后有效期为48小时:

-- Warrant request by police
RegisterNetEvent('legal:server:requestWarrant', function(data)
    local src = source
    local Officer = QBCore.Functions.GetPlayer(src)
    if not Officer or Officer.PlayerData.job.name ~= 'police' then return end

    local warrant = {
        type = data.type,
        target_citizenid = data.targetCitizenid,
        target_name = data.targetName,
        reason = data.reason,
        issued_by = Officer.PlayerData.citizenid,
    }

    local id = MySQL.insert.await(
        'INSERT INTO warrants (type, target_citizenid, target_name, reason, issued_by) VALUES (?, ?, ?, ?, ?)',
        { warrant.type, warrant.target_citizenid, warrant.target_name, warrant.reason, warrant.issued_by }
    )

    -- Notify judges
    local players = QBCore.Functions.GetQBPlayers()
    for _, player in pairs(players) do
        if player.PlayerData.job.name == 'judge' then
            TriggerClientEvent('legal:client:warrantPending', player.PlayerData.source, {
                id = id,
                type = warrant.type,
                targetName = warrant.target_name,
                reason = warrant.reason,
                officerName = Officer.PlayerData.charinfo.firstname .. ' ' .. Officer.PlayerData.charinfo.lastname,
            })
        end
    end

    TriggerClientEvent('QBCore:Notify', src, 'Warrant request submitted for judicial review', 'success')
end)

-- Judge approval
RegisterNetEvent('legal:server:approveWarrant', function(warrantId)
    local src = source
    local Judge = QBCore.Functions.GetPlayer(src)
    if not Judge or Judge.PlayerData.job.name ~= 'judge' then return end

    local warrant = MySQL.single.await('SELECT * FROM warrants WHERE id = ? AND status = "pending"', { warrantId })
    if not warrant then return end

    local expiryHours = warrant.type == 'search' and 48 or 168
    local expiresAt = os.date('!%Y-%m-%d %H:%M:%S', os.time() + (expiryHours * 3600))

    MySQL.update('UPDATE warrants SET status = "active", approved_by = ?, expires_at = ? WHERE id = ?',
        { Judge.PlayerData.citizenid, expiresAt, warrantId })

    -- Notify requesting officer if online
    TriggerClientEvent('QBCore:Notify', src, 'Warrant #' .. warrantId .. ' approved', 'success')
end)

法庭听证流程

法庭听证遵循结构化流程,案件依次经过提审、审前动议、审判和判决。案件立案时,系统生成唯一案件编号并赋予“已立案”状态。法官审查案件并安排提审听证,嫌疑人进行认罪答辩。如果嫌疑人不认罪,案件进入审前阶段,律师可提交动议和证据,随后进入审判阶段,双方陈述辩论。法官通过命令控制听证流程,推进案件状态并触发所有参与者的界面更新。实现一个法庭 NUI,显示案件信息、当前阶段,并为法官、检察官和辩护律师提供角色专属控件。法官拥有判决按钮和量刑输入,律师拥有异议按钮和证据展示控件。

律师角色与辩护系统

律师职业在服务器上创建了完整的职业路径,具有独特的游戏机制。被告可通过监狱或手机应用访问律师目录聘请辩护律师。律师接受客户后,可访问法律系统中的案件文件,包括检方提交的所有证据。律师可提交排除证据的动议、请求延期听证、与检察官协商认罪协议,并在审判中出示自己的证据和证人证词。实现律师资格考试系统,新律师角色必须通过知识测试才能获得律师职位,确保法律RP质量的最低标准。跟踪律师统计数据,如胜诉案件数、败诉案件数及客户平均减刑幅度,打造竞争激烈的市场,资深律师可收取更高费用:

Config.LawyerSystem = {
    barExamQuestions = 15,
    passingScore = 11,       -- 73% to pass
    examCooldown = 86400,    -- 24 hours between attempts
    maxActiveClients = 5,
    consultationFee = 500,   -- base fee for initial consultation
    trialFeeRange = { min = 2000, max = 25000 },

    motionTypes = {
        { id = 'suppress_evidence', label = 'Motion to Suppress Evidence', description = 'Request exclusion of illegally obtained evidence' },
        { id = 'dismiss_charges', label = 'Motion to Dismiss', description = 'Request dismissal due to insufficient evidence' },
        { id = 'continuance', label = 'Motion for Continuance', description = 'Request to postpone the hearing' },
        { id = 'change_venue', label = 'Motion for Change of Venue', description = 'Request trial at different location' },
        { id = 'bail_reduction', label = 'Motion for Bail Reduction', description = 'Request lower bail amount' },
        { id = 'expungement', label = 'Motion for Expungement', description = 'Request to clear prior conviction' },
    },
}

-- Hire a lawyer from jail
RegisterNetEvent('legal:server:hireLawyer', function(lawyerCitizenid)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local citizenid = Player.PlayerData.citizenid

    -- Find active case for this player
    local activeCase = MySQL.single.await(
        'SELECT * FROM court_cases WHERE defendant_citizenid = ? AND status NOT IN ("closed", "dismissed") ORDER BY filed_at DESC LIMIT 1',
        { citizenid }
    )
    if not activeCase then
        TriggerClientEvent('QBCore:Notify', src, 'No active case found', 'error')
        return
    end

    -- Assign lawyer to case
    MySQL.update('UPDATE court_cases SET defense_citizenid = ? WHERE id = ?',
        { lawyerCitizenid, activeCase.id })

    -- Notify lawyer
    local lawyerPlayer = QBCore.Functions.GetPlayerByCitizenId(lawyerCitizenid)
    if lawyerPlayer then
        TriggerClientEvent('QBCore:Notify', lawyerPlayer.PlayerData.source,
            'New client: ' .. Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname ..
            ' | Case #' .. activeCase.case_number, 'success')
    end

    TriggerClientEvent('QBCore:Notify', src, 'Attorney assigned to your case', 'success')
end)

证据管理系统

证据系统提供从犯罪现场调查到法庭呈现的证据链。当警察使用证据收集命令在现场收集证据时,系统创建包含收集警员身份、时间戳和位置的证据记录。每件证据都有唯一标识符,贯穿整个法律流程。证据类型包括物理物品如武器、毒品和衣物,数字证据如电话记录和监控录像,证人证词以文本形式记录,以及财务记录或伪造身份证等文件。律师可通过质证动议挑战证据的可采性,质疑收集方法或证据链。法官审查动议,可将证据标记为不可采纳,从案件文件中移除。实现警察局的证据柜,存储物理证据,利用库存系统集成跟踪调查中实际收集的物品。

罚款与保释机制

罚款和保释在法律系统中产生经济后果。被告出庭时,法官根据指控严重程度及犯罪记录设定保释金额。被告可通过法院系统支付保释金以获得释放,等待审判,或继续羁押直至听证。保释金从玩家银行账户扣除,案件结束且被告出席所有听证后退还。若被告未出庭,保释金没收并发出逮捕令。罚款作为判决的一部分,必须在规定时间内缴纳。未缴罚款会产生利息,并可能引发额外法律后果,如驾照吊销或财产留置。实现罚款分期付款系统,允许被告对大额罚款每周分期支付,而非一次性缴清,增加持续的经济负担,使法律系统在玩家日常服务器体验中保持相关性。

法官命令和判决

法官需要一套全面的命令来管理法庭程序并执行法律系统。核心命令包括 /case 查看和管理案件, /warrant 批准或拒绝搜查令请求, /sentence 执行监禁时间和罚款, /bail 设置保释金额, /gag 在听证期间静音扰乱玩家,和 /contempt 监禁不尊重法庭的玩家。判刑命令应引用刑法配置,定义每项指控的最短和最长刑期,确保不同法官之间的一致性。实现判刑指南系统,根据指控和被告的犯罪记录建议适当的惩罚,同时允许法官有理由地偏离建议。将所有司法决定存储在法律记录表中,使服务器建立一套资深玩家可在未来程序中参考的判例。添加司法复审流程,允许判决上诉至更高级别法官或小组,为专注玩家创造额外的法律角色扮演层次。

与警察、监狱和 MDT 集成

法律系统必须与现有警察、监狱和MDT资源无缝集成,形成连贯的司法流程。当警察通过MDT提交指控时,系统应自动创建带有相应案件编号的法庭案件,并关联调查中已收集的证据。法官判处被告监禁时,系统应直接调用监狱资源的监禁导出函数,使判决自动执行,无需警察手动执行监禁命令。活跃逮捕令应在警察MDT中显示,搜索公民时清晰标示逮捕令类型、发出机构和到期日期。法院判决生成的犯罪记录应在MDT中公民档案下可查,显示完整法律历史,包括定罪、无罪释放、活跃案件和未缴罚款。导出所有法律系统功能,方便其他资源程序化检查逮捕令状态、验证律师资格、查询犯罪记录及与法院系统交互。

准备好开始了吗?

在我们的商店获取脚本,或加入 Discord 获取支持、更新以及新功能预告。