返回博客
Tutorial3 分钟阅读

FiveM 租车系统:脚本临时车辆

为您的FiveM服务器添加租赁车辆。定时钥匙、押金、自动归还逻辑以及适用于QBCore、ESX和独立职业的最佳租赁脚本。

Agency Scripts

Agency Scripts 创始人兼首席开发者

为什么租赁系统提升角色扮演

并非每个角色扮演服务器玩家都拥有车辆,尤其是刚起步的新角色。租车和自行车系统通过提供临时交通工具以实惠价格填补这一空白,创造租赁柜台的自然角色扮演机会,并让新玩家无需依赖公共交通或步行即可自由探索地图。除基础交通外,租赁系统支持旅游角色扮演,游客租用敞篷车进行风景游览,配送任务需要特定车辆类型,以及活动场景中玩家需要匹配车辆进行组织巡游或比赛。该系统还通过租金、押金和迟还罚款为服务器经济产生被动收入。

车队配置与车辆分类

将租赁车队按不同玩家需求和预算分类。经济型车辆提供实惠的基础交通,中档车提供更多舒适和性能,豪华车满足奢华体验,摩托车为玩家提供两轮选择。每个类别应有多款车辆及明确价格。包含小时和日租费率,供玩家选择最经济方案。通过配置表定义车队,指定每辆车的租金、押金、类别和生成模型:

Config.RentalLocations = {
    ['airport'] = {
        label = 'LS Airport Car Rental',
        pedModel = 's_f_y_airhostess_01',
        pedCoords = vector4(-1037.34, -2736.82, 20.17, 238.52),
        spawnPoint = vector4(-1029.12, -2728.91, 20.17, 148.76),
        returnZone = vector3(-1035.00, -2730.00, 20.17),
        returnRadius = 10.0,
        categories = {'economy', 'midrange', 'premium', 'motorcycle'},
    },
    ['downtown'] = {
        label = 'Downtown Bike Rental',
        pedModel = 'a_m_y_business_02',
        pedCoords = vector4(-259.68, -892.41, 31.08, 69.24),
        spawnPoint = vector4(-264.30, -888.12, 31.08, 339.87),
        returnZone = vector3(-260.00, -890.00, 31.08),
        returnRadius = 8.0,
        categories = {'motorcycle', 'bicycle'},
    },
}

Config.RentalFleet = {
    economy = {
        {model = 'blista', label = 'Blista', priceHour = 150, priceDay = 800, deposit = 500},
        {model = 'asea', label = 'Asea', priceHour = 120, priceDay = 650, deposit = 400},
        {model = 'stanier', label = 'Stanier', priceHour = 130, priceDay = 700, deposit = 450},
    },
    midrange = {
        {model = 'schafter2', label = 'Schafter', priceHour = 300, priceDay = 1600, deposit = 1000},
        {model = 'oracle', label = 'Oracle', priceHour = 280, priceDay = 1500, deposit = 900},
    },
    premium = {
        {model = 'felon', label = 'Felon GT', priceHour = 600, priceDay = 3200, deposit = 2500},
        {model = 'exemplar', label = 'Exemplar', priceHour = 550, priceDay = 3000, deposit = 2000},
    },
    motorcycle = {
        {model = 'pcj', label = 'PCJ-600', priceHour = 100, priceDay = 500, deposit = 300},
        {model = 'sanchez', label = 'Sanchez', priceHour = 80, priceDay = 400, deposit = 250},
    },
    bicycle = {
        {model = 'cruiser', label = 'Cruiser Bike', priceHour = 30, priceDay = 150, deposit = 50},
        {model = 'tribike', label = 'Tri-Cycles Race', priceHour = 25, priceDay = 120, deposit = 50},
    },
}

租赁流程和NUI界面

租赁交互从玩家接近租赁柜台NPC开始。使用目标系统或接近检测,打开NUI菜单,显示可用车辆类别及筛选选项。每个车辆列表应显示车型名称、图片预览(如有)、小时和日租费、所需押金及可用状态。玩家选择车辆并确认租赁时长后,服务器处理交易。总费用为所选时长的租赁费加押金。押金由系统保管,车辆完好归还时退还。将租赁确认实现为服务器事件,验证玩家资金充足,检查车辆可用,扣费并在指定生成点生成租赁车辆:

RegisterNetEvent('rental:server:rentVehicle', function(locationId, vehicleModel, duration)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local location = Config.RentalLocations[locationId]
    if not location then return end

    -- Check if player already has an active rental
    if ActiveRentals[Player.PlayerData.citizenid] then
        TriggerClientEvent('QBCore:Notify', src, 'You already have an active rental', 'error')
        return
    end

    -- Find vehicle config
    local vehicleConfig = nil
    for _, category in pairs(Config.RentalFleet) do
        for _, v in ipairs(category) do
            if v.model == vehicleModel then
                vehicleConfig = v
                break
            end
        end
        if vehicleConfig then break end
    end

    if not vehicleConfig then return end

    -- Calculate cost
    local rentalFee = duration == 'day' and vehicleConfig.priceDay or vehicleConfig.priceHour
    local totalCost = rentalFee + vehicleConfig.deposit

    if Player.Functions.GetMoney('bank') < totalCost then
        TriggerClientEvent('QBCore:Notify', src, 'Insufficient funds. Need $' .. totalCost, 'error')
        return
    end

    -- Charge player
    Player.Functions.RemoveMoney('bank', totalCost, 'vehicle-rental')

    -- Generate rental plate
    local plate = 'RNT' .. math.random(10000, 99999)

    -- Store active rental
    ActiveRentals[Player.PlayerData.citizenid] = {
        model = vehicleModel,
        plate = plate,
        deposit = vehicleConfig.deposit,
        rentalFee = rentalFee,
        startTime = os.time(),
        duration = duration,
        expiresAt = os.time() + (duration == 'day' and 86400 or 3600),
        locationId = locationId,
        initialHealth = 1000,
    }

    TriggerClientEvent('rental:client:spawnVehicle', src, vehicleModel, plate, location.spawnPoint)
    TriggerClientEvent('QBCore:Notify', src, 'Vehicle rented! Return before your time expires.', 'success')
end)

损坏押金与返还机制

损害押金系统确保玩家对租赁车辆的使用负责。车辆归还时,将其当前健康值与租赁时记录的初始值比较。GTA 车辆有发动机、车身和各个部件的独立健康值,允许细致的损伤评估。根据损坏程度计算维修费用,从押金中扣除后退还余额。若损坏超过押金,玩家不退还押金且可能被额外收费。若车辆完好归还,全额退还押金。该系统鼓励谨慎驾驶,同时不惩罚 GTA 驾驶物理中不可避免的轻微划痕:

RegisterNetEvent('rental:server:returnVehicle', function(vehicleNet)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local citizenid = Player.PlayerData.citizenid
    local rental = ActiveRentals[citizenid]
    if not rental then return end

    local vehicle = NetworkGetEntityFromNetworkId(vehicleNet)
    if not DoesEntityExist(vehicle) then return end

    -- Check plate matches
    local plate = GetVehicleNumberPlateText(vehicle)
    if plate:trim() ~= rental.plate then
        TriggerClientEvent('QBCore:Notify', src, 'This is not your rental vehicle', 'error')
        return
    end

    -- Calculate damage
    local bodyHealth = GetVehicleBodyHealth(vehicle)
    local engineHealth = GetVehicleEngineHealth(vehicle)
    local damagePercent = 1.0 - ((bodyHealth + engineHealth) / 2000.0)
    local repairCost = math.floor(rental.deposit * damagePercent)

    -- Calculate late fee
    local lateFee = 0
    if os.time() > rental.expiresAt then
        local overHours = math.ceil((os.time() - rental.expiresAt) / 3600)
        lateFee = overHours * 200
    end

    -- Refund deposit minus costs
    local refund = math.max(0, rental.deposit - repairCost - lateFee)

    if refund > 0 then
        Player.Functions.AddMoney('bank', refund, 'rental-deposit-refund')
    end

    DeleteEntity(vehicle)
    ActiveRentals[citizenid] = nil

    local message = string.format('Vehicle returned. Deposit: $%d | Repairs: $%d | Late fee: $%d | Refund: $%d',
        rental.deposit, repairCost, lateFee, refund)
    TriggerClientEvent('QBCore:Notify', src, message, refund > 0 and 'success' or 'warning')
end)

过期与自动归还系统

租赁车辆不应无限期存在。实现计时系统跟踪每辆租赁车辆到期时间并采取相应措施。到期前30分钟、10分钟和5分钟分别发送通知。租赁到期后开始按分钟计收滞纳金,最高有上限。若玩家未在宽限期内归还车辆,自动删除车辆并没收全部押金,防止废弃租赁车辆占用服务器。过期检查应在服务器端定期运行,遍历所有活跃租赁并根据剩余时间采取措施。存储车辆网络ID以便远程删除,并处理车辆在租期内被销毁的情况,自动关闭租赁并没收押金。

多个租赁地点

在地图周围战略点放置租赁地点,临时交通最合理。机场是显而易见的选择,服务刚到城市的玩家。市中心政府建筑附近位置服务参加法庭或商务会议的玩家。海滩位置提供摩托车和自行车,供沿海岸线休闲活动。每个地点可提供不同车队子集,机场拥有从经济型到高端的全系列,海滩仅存摩托车和自行车。定制每个地点的NPC模型以匹配环境。机场租赁使用专业商务模型,海滩租赁使用休闲冲浪者模型,市中心使用西装职员模型。此细节关注使每个租赁点感觉像独特的业务,而非相同系统的复制品。

GPS 追踪和防盗

租赁车辆应内置GPS追踪,允许租赁公司和玩家随时定位车辆。在玩家地图上显示租赁车辆位置,使用独特图标。如果车辆移出可配置边界,发送警告通知。实施防盗措施,防止其他玩家存放或入库租赁车辆,确保车辆归租赁系统控制。当玩家尝试将租赁车辆存入个人车库时,系统应阻止并通知租赁车辆不可存储。摩托车和自行车应添加虚拟锁,只有租用玩家可解锁,防止随意盗窃。若租赁车辆被盗,租用玩家可通过手机应用报告,触发GPS定位帮助寻找。记录租赁期间总里程,可能对高里程租赁收取附加费,增加真实车辆租赁管理层次。

业务整合和玩家拥有的租赁

对于拥有商业所有权系统的服务器,允许玩家购买并运营自己的租赁业务。业主通过购买车辆设置车队组成,确定价格,雇佣员工管理柜台,并收取租金收入。业主应拥有管理仪表盘,显示活跃租赁、每日收入、车队利用率和多次租赁造成损坏车辆的维护成本。员工负责前台操作,如处理租赁和归还,检查归还车辆损坏,并将争议上报业主。这种玩家驱动的方法将租赁系统从静态游戏机制转变为反映角色扮演服务器创业精神的动态业务,并为偏好客户服务角色而非高强度玩法的玩家创造就业机会。

准备好开始了吗?

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