العودة إلى المدونة
Tutorial7 دقيقة قراءة

مزامنة الطقس FiveM: سكريبتات الطقس والوقت الديناميكية

مزامنة الطقس والوقت عبر خادم FiveM الخاص بك. دورات ديناميكية، رعد، ثلج، حزم المواسم وأفضل سكربتات الطقس لضبط الأجواء.

Agency Scripts

المؤسس والمطور الرئيسي في Agency Scripts

المشكلة مع الطقس غير المتزامن

بشكل افتراضي، يتعامل GTA V مع الطقس والوقت بشكل مستقل على كل عميل. هذا يعني أن لاعبًا واحدًا يمكن أن يقود في ضوء الشمس الساطع بينما يرى لاعب آخر يقف بجانبه عاصفة رعدية. بالنسبة لخوادم اللعب التمثيلي، هذا يكسر الانغماس تمامًا. تخيل محاولة تنسيق مشهد حيث يشتكي شخصية من المطر بينما تظهر شاشة لاعب آخر سماء صافية. يضمن نظام مزامنة الطقس والوقت أن يختبر كل لاعب على الخادم نفس البيئة في نفس اللحظة، وهو أمر أساسي للعب تمثيلي متسق، وظروف قيادة واقعية، وتنسيق الفعاليات. في هذا الدرس، سنبني نظام مزامنة طقس كامل مع سرعة وقت قابلة للتكوين، انتقالات موسمية، تصويت على الطقس، وأحداث انقطاع التيار.

إدارة حالة الطقس على جانب الخادم

الخادم هو المصدر الوحيد للحقيقة للطقس والوقت. يحتفظ بنوع الطقس الحالي، نوع الطقس التالي للانتقالات، والساعة داخل اللعبة. يتزامن كل عميل مع هذه الحالة عند الانضمام ويتلقى تحديثات كلما تغير الطقس. تدعم GTA V مجموعة محددة من أنواع الطقس بما في ذلك CLEAR, EXTRASUNNY, CLOUDS, OVERCAST, RAIN, THUNDER, CLEARING, FOGGY, SMOG, SNOW, SNOWLIGHT, BLIZZARD، و XMAS. عرّف قواعد انتقال منطقية حتى يتدفق الطقس بشكل طبيعي بدلاً من القفز من عاصفة ثلجية إلى مشمس جداً بدون حالة وسيطة.

-- server.lua
local Config = {
    timeSpeed    = 2,        -- how many in-game minutes per real second
    syncInterval = 30000,    -- full sync broadcast every 30s
    autoChange   = true,     -- automatic weather cycling
    changeCooldown = 600,    -- seconds between automatic changes
}

local State = {
    weather     = 'CLEAR',
    nextWeather = 'CLOUDS',
    hour        = 12,
    minute      = 0,
    frozen      = false,
    blackout    = false,
    season      = 'summer',
}

local weatherTransitions = {
    EXTRASUNNY = {'CLEAR', 'CLOUDS'},
    CLEAR      = {'EXTRASUNNY', 'CLOUDS', 'OVERCAST'},
    CLOUDS     = {'CLEAR', 'OVERCAST', 'RAIN'},
    OVERCAST   = {'CLOUDS', 'RAIN', 'THUNDER', 'FOGGY'},
    RAIN       = {'OVERCAST', 'THUNDER', 'CLEARING'},
    THUNDER    = {'RAIN', 'OVERCAST'},
    CLEARING   = {'CLEAR', 'CLOUDS'},
    FOGGY      = {'CLEAR', 'CLOUDS', 'SMOG'},
    SMOG       = {'FOGGY', 'CLEAR'},
}

local seasonWeights = {
    summer = {EXTRASUNNY = 30, CLEAR = 30, CLOUDS = 20, RAIN = 10, THUNDER = 5, FOGGY = 5},
    winter = {CLOUDS = 20, OVERCAST = 20, SNOW = 25, SNOWLIGHT = 15, BLIZZARD = 10, FOGGY = 10},
    spring = {CLEAR = 25, CLOUDS = 25, RAIN = 20, CLEARING = 15, FOGGY = 10, OVERCAST = 5},
    autumn = {CLOUDS = 25, OVERCAST = 25, RAIN = 20, FOGGY = 15, CLEAR = 10, SMOG = 5},
}

تقدم الوقت ودورة التزامن

يعمل نظام الوقت كحلقة على جانب الخادم تزيد الساعة داخل اللعبة بناءً على السرعة المكونة. timeSpeed تعني 2 أن كل ثانية حقيقية تتقدم ساعة اللعبة بمقدار دقيقتين. هذا يخلق دورة ليل ونهار تستغرق حوالي 12 دقيقة حقيقية لساعات اللعبة الـ24 كاملة. يبث الخادم الوقت الحالي وحالة الطقس لجميع العملاء على فترات منتظمة ويدفع تحديثات فورية عندما يغير المسؤول الطقس أو يطلق حدث انقطاع التيار. اللاعبون الجدد الذين ينضمون في منتصف الجلسة يتلقون الحالة الحالية من خلال حدث تزامن مخصص ليطابقوا الجميع فورًا.

-- Time progression loop (server.lua continued)
CreateThread(function()
    while true do
        Wait(1000)
        if not State.frozen then
            State.minute = State.minute + Config.timeSpeed
            if State.minute >= 60 then
                State.hour = State.hour + math.floor(State.minute / 60)
                State.minute = State.minute % 60
            end
            if State.hour >= 24 then
                State.hour = State.hour % 24
            end
        end
    end
end)

-- Periodic full sync
CreateThread(function()
    while true do
        Wait(Config.syncInterval)
        TriggerClientEvent('weather:fullSync', -1, State)
    end
end)

-- Sync new players on join
AddEventHandler('playerJoining', function()
    local src = source
    Wait(2000)
    TriggerClientEvent('weather:fullSync', src, State)
end)

تطبيق الطقس على جانب العميل

على العميل، تحتاج إلى تجاوز أنظمة الطقس والوقت الأصلية في GTA. الوظائف الأساسية هي SetWeatherTypeNowPersist لتغييرات الطقس الفورية، SetWeatherTypeTransition لدمج سلس بين حالتين من الطقس، و NetworkOverrideClockTime لتعيين ساعة اللعبة. يجب عليك استدعاء NetworkOverrideClockTime كل إطار لمنع اللعبة من العودة إلى حساب الوقت الخاص بها. بالنسبة لانتقالات الطقس، تدعم GTA عامل دمج بين 0.0 و1.0 يسمح لك بالتداخل السلس بين حالتي طقس خلال عدة ثوانٍ، مما يخلق انتقالات واقعية حيث تتدحرج السحب تدريجيًا قبل بدء المطر.

-- client.lua
local currentWeather = 'CLEAR'
local nextWeather    = 'CLEAR'
local weatherBlend   = 0.0
local hour, minute   = 12, 0
local blackout       = false

RegisterNetEvent('weather:fullSync', function(state)
    currentWeather = state.weather
    nextWeather    = state.nextWeather or state.weather
    hour           = state.hour
    minute         = state.minute
    blackout       = state.blackout or false
    applyWeather()
end)

function applyWeather()
    ClearOverrideWeather()
    ClearWeatherTypePersist()
    SetWeatherTypeNowPersist(currentWeather)
    if currentWeather ~= nextWeather then
        SetWeatherTypeTransition(
            GetHashKey(currentWeather),
            GetHashKey(nextWeather),
            weatherBlend
        )
    end
    if blackout then
        SetArtificialLightsState(true)
        SetArtificialLightsStateAffectsVehicles(false)
    else
        SetArtificialLightsState(false)
    end
end

-- Override clock every frame
CreateThread(function()
    while true do
        Wait(0)
        NetworkOverrideClockTime(hour, minute, 0)
    end
end)

نظام تصويت الطقس

نظام تصويت على الطقس يتيح لمجتمعك تحديد الطقس ديمقراطيًا. يمكن للاعبين الإدلاء بصوتهم لنوع الطقس المفضل لديهم، وبعد فترة التصويت يفوز الخيار الأكثر شعبية. يعمل هذا جيدًا للأحداث حيث تريد مشاركة اللاعبين، مثل تحديد ما إذا كان لقاء السيارات يحدث تحت سماء صافية أو عواصف درامية. حدد التصويت مرة واحدة لكل دورة لمنع الرسائل المزعجة، وعرض عدد الأصوات الحالي من خلال نظام الإشعارات أو طبقة NUI بسيطة. يجمع السيرفر الأصوات، يحصيها عند انتهاء الفترة، ويطبق الطقس الفائز من خلال انتقال سلس حتى يختبر جميع اللاعبين التغيير في نفس الوقت.

-- Weather voting (server.lua)
local votes = {}
local voteCooldown = {}
local votingOpen = false
local voteOptions = {'CLEAR', 'RAIN', 'THUNDER', 'FOGGY', 'SNOW'}

RegisterCommand('voteweather', function(source, args)
    if not votingOpen then
        TriggerClientEvent('chat:addMessage', source, {args = {'Weather', 'No vote is currently active.'}})
        return
    end
    if voteCooldown[source] then
        TriggerClientEvent('chat:addMessage', source, {args = {'Weather', 'You already voted this round.'}})
        return
    end
    local choice = string.upper(args[1] or '')
    local valid = false
    for _, opt in ipairs(voteOptions) do
        if opt == choice then valid = true break end
    end
    if not valid then
        TriggerClientEvent('chat:addMessage', source, {
            args = {'Weather', 'Options: ' .. table.concat(voteOptions, ', ')}
        })
        return
    end
    votes[choice] = (votes[choice] or 0) + 1
    voteCooldown[source] = true
    TriggerClientEvent('chat:addMessage', -1, {
        args = {'Weather', GetPlayerName(source) .. ' voted for ' .. choice}
    })
end, false)

function startWeatherVote()
    votes = {}
    voteCooldown = {}
    votingOpen = true
    TriggerClientEvent('chat:addMessage', -1, {
        args = {'Weather', 'Weather vote started! Use /voteweather [type]. Options: ' .. table.concat(voteOptions, ', ')}
    })
    SetTimeout(60000, function()
        votingOpen = false
        local winner, maxVotes = 'CLEAR', 0
        for weather, count in pairs(votes) do
            if count > maxVotes then winner = weather; maxVotes = count end
        end
        transitionWeather(winner)
        TriggerClientEvent('chat:addMessage', -1, {
            args = {'Weather', 'Vote ended! Weather changing to: ' .. winner}
        })
    end)
end

نظام موسمي وتأثيرات الطقس على طريقة اللعب

يضيف نظام المواسم طبقة من العمق يغفل عنها معظم الخوادم. من خلال تتبع التقويم داخل اللعبة أو مطابقة الأشهر الواقعية مع الفصول، يمكنك وزن احتمالات الطقس وفقاً لذلك. يفضل الصيف الطقس الصافي والمشمس مع عواصف رعدية عرضية، بينما يحول الشتاء التوازن نحو الثلج، السماء الغائمة، والضباب. يمكن أن تؤثر الفصول أيضاً على آليات اللعب بخلاف المرئيات فقط. أثناء المطر، يمكنك تقليل تماسك المركبات من خلال تفعيل معدل تعديل التعامل على جميع المركبات النشطة. يمكن أن يقلل الضباب من مسافة العرض لشخصيات AI، مما يجعل اللعب الخفي أكثر جدوى. يمكن أن يبطئ الثلج سرعة حركة اللاعب قليلاً ويتطلب ملابس شتوية لتجنب استنزاف الصحة البطيء. تخلق هذه اللمسات الصغيرة عالماً غامراً يشعر بالحياة والاستجابة لبيئته.

أحداث انقطاع التيار الكهربائي وأدوات الإدارة

أحداث انقطاع التيار هي أداة قوية لأحداث الخادم وسيناريوهات لعب الأدوار. الـ SetArtificialLightsState native يعطل جميع مصادر الضوء الاصطناعي في عالم اللعبة، مما يغمر المدينة في الظلام. مع حالة الطقس الغائم أو الضبابي، يخلق هذا بيئة جوية مثالية لأحداث الرعب، سيناريوهات السطو، أو لعب الأدوار في البقاء. يجب أن توفر أوامر الإدارة تحكمًا كاملاً في نظام الطقس: تجميد الوقت، تعيين ساعات محددة، فرض أنواع الطقس، تفعيل انقطاعات التيار، وبدء تصويتات الطقس. احمِ هذه الأوامر بأذونات ace بحيث يمكن فقط للموظفين المخولين تعديل البيئة. يصبح نظام الطقس المصمم جيدًا أداة سرد يستخدمها المسؤولون لضبط المزاج لكل حدث وسيناريو على خادمك.

-- Admin commands (server.lua)
RegisterCommand('setweather', function(source, args)
    if source > 0 and not IsPlayerAceAllowed(source, 'command.setweather') then return end
    local weather = string.upper(args[1] or 'CLEAR')
    transitionWeather(weather)
    TriggerClientEvent('chat:addMessage', -1, {
        args = {'Admin', 'Weather changed to ' .. weather}
    })
end, true)

RegisterCommand('settime', function(source, args)
    if source > 0 and not IsPlayerAceAllowed(source, 'command.settime') then return end
    State.hour   = tonumber(args[1]) or 12
    State.minute = tonumber(args[2]) or 0
    TriggerClientEvent('weather:fullSync', -1, State)
end, true)

RegisterCommand('blackout', function(source, args)
    if source > 0 and not IsPlayerAceAllowed(source, 'command.blackout') then return end
    State.blackout = not State.blackout
    TriggerClientEvent('weather:fullSync', -1, State)
    TriggerClientEvent('chat:addMessage', -1, {
        args = {'Admin', 'Blackout ' .. (State.blackout and 'enabled' or 'disabled')}
    })
end, true)

RegisterCommand('freezetime', function(source, args)
    if source > 0 and not IsPlayerAceAllowed(source, 'command.freezetime') then return end
    State.frozen = not State.frozen
    TriggerClientEvent('chat:addMessage', -1, {
        args = {'Admin', 'Time ' .. (State.frozen and 'frozen' or 'unfrozen')}
    })
end, true)

جاهز للبدء؟

احصل على السكربتات من متجرنا، أو انضم إلى Discord للدعم والتحديثات ونظرة على ما هو قادم.