362 lines
14 KiB
Lua
362 lines
14 KiB
Lua
local QBCore = exports['qb-core']:GetCoreObject()
|
|
local CurrentWeather = Config.StartWeather
|
|
local baseTime = Config.BaseTime
|
|
local timeOffset = Config.TimeOffset
|
|
local freezeTime = Config.FreezeTime
|
|
local blackout = Config.Blackout
|
|
local newWeatherTimer = Config.NewWeatherTimer
|
|
|
|
--- Is the source a client or the server
|
|
--- @param src string | number - source to check
|
|
--- @return int - source
|
|
local function getSource(src)
|
|
return src == '' and 0 or src
|
|
end
|
|
|
|
--- Does source have permissions to run admin commands
|
|
--- @param src number - Source to check
|
|
--- @return boolean - has permission
|
|
local function isAllowedToChange(src)
|
|
return src == 0 or QBCore.Functions.HasPermission(src, "admin") or IsPlayerAceAllowed(src, 'command')
|
|
end
|
|
|
|
--- Sets time offset based on minutes provided
|
|
--- @param minute number - Minutes to offset by
|
|
local function shiftToMinute(minute)
|
|
timeOffset = timeOffset - (((baseTime + timeOffset) % 60) - minute)
|
|
end
|
|
|
|
--- Sets time offset based on hour provided
|
|
--- @param hour number - Hour to offset by
|
|
local function shiftToHour(hour)
|
|
timeOffset = timeOffset - ((((baseTime + timeOffset) / 60) % 24) - hour) * 60
|
|
end
|
|
|
|
--- Triggers event to switch weather to next stage
|
|
local function nextWeatherStage()
|
|
if CurrentWeather == "CLEAR" or CurrentWeather == "CLOUDS" or CurrentWeather == "EXTRASUNNY" then
|
|
CurrentWeather = (math.random(1, 5) > 2) and "CLEARING" or "OVERCAST" -- 60/40 chance
|
|
elseif CurrentWeather == "CLEARING" or CurrentWeather == "OVERCAST" then
|
|
local new = math.random(1, 6)
|
|
if new == 1 then CurrentWeather = (CurrentWeather == "CLEARING") and "FOGGY" or "RAIN"
|
|
elseif new == 2 then CurrentWeather = "CLOUDS"
|
|
elseif new == 3 then CurrentWeather = "CLEAR"
|
|
elseif new == 4 then CurrentWeather = "EXTRASUNNY"
|
|
elseif new == 5 then CurrentWeather = "SMOG"
|
|
else CurrentWeather = "FOGGY"
|
|
end
|
|
elseif CurrentWeather == "THUNDER" or CurrentWeather == "RAIN" then CurrentWeather = "CLEARING"
|
|
elseif CurrentWeather == "SMOG" or CurrentWeather == "FOGGY" then CurrentWeather = "CLEAR"
|
|
else CurrentWeather = "CLEAR"
|
|
end
|
|
TriggerEvent("qb-weathersync:server:RequestStateSync")
|
|
end
|
|
|
|
--- Switch to a specified weather type
|
|
--- @param weather string - Weather type from Config.AvailableWeatherTypes
|
|
--- @return boolean - success
|
|
local function setWeather(weather)
|
|
local validWeatherType = false
|
|
for _, weatherType in pairs(Config.AvailableWeatherTypes) do
|
|
if weatherType == string.upper(weather) then
|
|
validWeatherType = true
|
|
end
|
|
end
|
|
if not validWeatherType then return false end
|
|
CurrentWeather = string.upper(weather)
|
|
newWeatherTimer = Config.NewWeatherTimer
|
|
TriggerEvent('qb-weathersync:server:RequestStateSync')
|
|
return true
|
|
end
|
|
|
|
--- Sets sun position based on time to specified
|
|
--- @param hour number|string - Hour to set (0-24)
|
|
--- @param minute number|string `optional` - Minute to set (0-60)
|
|
--- @return boolean - success
|
|
local function setTime(hour, minute)
|
|
local argh = tonumber(hour)
|
|
local argm = tonumber(minute) or 0
|
|
if argh == nil or argh > 24 then
|
|
print(Lang:t('time.invalid'))
|
|
return false
|
|
end
|
|
shiftToHour((argh < 24) and argh or 0)
|
|
shiftToMinute((argm < 60) and argm or 0)
|
|
print(Lang:t('time.change', {value = argh, value2 = argm}))
|
|
TriggerEvent('qb-weathersync:server:RequestStateSync')
|
|
return true
|
|
end
|
|
|
|
--- Sets or toggles blackout state and returns the state
|
|
--- @param state boolean `optional` - enable blackout?
|
|
--- @return boolean - blackout state
|
|
local function setBlackout(state)
|
|
if state == nil then state = not blackout end
|
|
if state then blackout = true
|
|
else blackout = false end
|
|
TriggerEvent('qb-weathersync:server:RequestStateSync')
|
|
return blackout
|
|
end
|
|
|
|
--- Sets or toggles time freeze state and returns the state
|
|
--- @param state boolean `optional` - Enable time freeze?
|
|
--- @return boolean - Time freeze state
|
|
local function setTimeFreeze(state)
|
|
if state == nil then state = not freezeTime end
|
|
if state then freezeTime = true
|
|
else freezeTime = false end
|
|
TriggerEvent('qb-weathersync:server:RequestStateSync')
|
|
return freezeTime
|
|
end
|
|
|
|
--- Sets or toggles dynamic weather state and returns the state
|
|
--- @param state boolean `optional` - Enable dynamic weather?
|
|
--- @return boolean - Dynamic Weather state
|
|
local function setDynamicWeather(state)
|
|
if state == nil then state = not Config.DynamicWeather end
|
|
if state then Config.DynamicWeather = true
|
|
else Config.DynamicWeather = false end
|
|
TriggerEvent('qb-weathersync:server:RequestStateSync')
|
|
return Config.DynamicWeather
|
|
end
|
|
|
|
--- Retrieves the current time from worldtimeapi.org
|
|
--- @return number - Unix time
|
|
local function retrieveTimeFromApi(callback)
|
|
Citizen.CreateThread(function()
|
|
PerformHttpRequest("http://worldtimeapi.org/api/ip", function(statusCode, response)
|
|
if statusCode == 200 then
|
|
local data = json.decode(response)
|
|
if data == nil or data.unixtime == nil then
|
|
callback(nil)
|
|
else
|
|
callback(data.unixtime)
|
|
end
|
|
else
|
|
callback(nil)
|
|
end
|
|
end, "GET", nil, nil)
|
|
end)
|
|
end
|
|
|
|
-- EVENTS
|
|
RegisterNetEvent('qb-weathersync:server:RequestStateSync', function()
|
|
TriggerClientEvent('qb-weathersync:client:SyncWeather', -1, CurrentWeather, blackout)
|
|
TriggerClientEvent('qb-weathersync:client:SyncTime', -1, baseTime, timeOffset, freezeTime)
|
|
end)
|
|
|
|
RegisterNetEvent('qb-weathersync:server:setWeather', function(weather)
|
|
local src = getSource(source)
|
|
if isAllowedToChange(src) then
|
|
local success = setWeather(weather)
|
|
if src > 0 then
|
|
if (success) then TriggerClientEvent('QBCore:Notify', src, Lang:t('weather.updated'))
|
|
else TriggerClientEvent('QBCore:Notify', src, Lang:t('weather.invalid'))
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-weathersync:server:setTime', function(hour, minute)
|
|
local src = getSource(source)
|
|
if isAllowedToChange(src) then
|
|
local success = setTime(hour, minute)
|
|
if src > 0 then
|
|
if (success) then TriggerClientEvent('QBCore:Notify', src, Lang:t('time.change', {value = hour, value2 = minute or "00"}))
|
|
else TriggerClientEvent('QBCore:Notify', src, Lang:t('time.invalid'))
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-weathersync:server:toggleBlackout', function(state)
|
|
local src = getSource(source)
|
|
if isAllowedToChange(src) then
|
|
local newstate = setBlackout(state)
|
|
if src > 0 then
|
|
if (newstate) then TriggerClientEvent('QBCore:Notify', src, Lang:t('blackout.enabled'))
|
|
else TriggerClientEvent('QBCore:Notify', src, Lang:t('blackout.disabled'))
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-weathersync:server:toggleFreezeTime', function(state)
|
|
local src = getSource(source)
|
|
if isAllowedToChange(src) then
|
|
local newstate = setTimeFreeze(state)
|
|
if src > 0 then
|
|
if (newstate) then TriggerClientEvent('QBCore:Notify', src, Lang:t('time.now_frozen'))
|
|
else TriggerClientEvent('QBCore:Notify', src, Lang:t('time.now_unfrozen'))
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('qb-weathersync:server:toggleDynamicWeather', function(state)
|
|
local src = getSource(source)
|
|
if isAllowedToChange(src) then
|
|
local newstate = setDynamicWeather(state)
|
|
if src > 0 then
|
|
if (newstate) then TriggerClientEvent('QBCore:Notify', src, Lang:t('weather.now_unfrozen'))
|
|
else TriggerClientEvent('QBCore:Notify', src, Lang:t('weather.now_frozen'))
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- COMMANDS
|
|
QBCore.Commands.Add('freezetime', Lang:t('help.freezecommand'), {}, false, function(source)
|
|
local newstate = setTimeFreeze()
|
|
if source > 0 then
|
|
if (newstate) then return TriggerClientEvent('QBCore:Notify', source, Lang:t('time.frozenc')) end
|
|
return TriggerClientEvent('QBCore:Notify', source, Lang:t('time.unfrozenc'))
|
|
end
|
|
if (newstate) then return print(Lang:t('time.now_frozen')) end
|
|
return print(Lang:t('time.now_unfrozen'))
|
|
end, 'admin')
|
|
|
|
QBCore.Commands.Add('freezeweather', Lang:t('help.freezeweathercommand'), {}, false, function(source)
|
|
local newstate = setDynamicWeather()
|
|
if source > 0 then
|
|
if (newstate) then return TriggerClientEvent('QBCore:Notify', source, Lang:t('dynamic_weather.enabled')) end
|
|
return TriggerClientEvent('QBCore:Notify', source, Lang:t('dynamic_weather.disabled'))
|
|
end
|
|
if (newstate) then return print(Lang:t('weather.now_unfrozen')) end
|
|
return print(Lang:t('weather.now_frozen'))
|
|
end, 'admin')
|
|
|
|
QBCore.Commands.Add('weather', Lang:t('help.weathercommand'), {{name = Lang:t('help.weathertype'), help = Lang:t('help.availableweather')}}, true, function(source, args)
|
|
local success = setWeather(args[1])
|
|
if source > 0 then
|
|
if (success) then return TriggerClientEvent('QBCore:Notify', source, Lang:t('weather.willchangeto', {value = string.lower(args[1])})) end
|
|
return TriggerClientEvent('QBCore:Notify', source, Lang:t('weather.invalidc'), 'error')
|
|
end
|
|
if (success) then return print(Lang:t('weather.updated')) end
|
|
return print(Lang:t('weather.invalid'))
|
|
end, 'admin')
|
|
|
|
QBCore.Commands.Add('blackout', Lang:t('help.blackoutcommand'), {}, false, function(source)
|
|
local newstate = setBlackout()
|
|
if source > 0 then
|
|
if (newstate) then return TriggerClientEvent('QBCore:Notify', source, Lang:t('blackout.enabledc')) end
|
|
return TriggerClientEvent('QBCore:Notify', source, Lang:t('blackout.disabledc'))
|
|
end
|
|
if (newstate) then return print(Lang:t('blackout.enabled')) end
|
|
return print(Lang:t('blackout.disabled'))
|
|
end, 'admin')
|
|
|
|
QBCore.Commands.Add('morning', Lang:t('help.morningcommand'), {}, false, function(source)
|
|
setTime(9, 0)
|
|
if source > 0 then return TriggerClientEvent('QBCore:Notify', source, Lang:t('time.morning')) end
|
|
end, 'admin')
|
|
|
|
QBCore.Commands.Add('noon', Lang:t('help.nooncommand'), {}, false, function(source)
|
|
setTime(12, 0)
|
|
if source > 0 then return TriggerClientEvent('QBCore:Notify', source, Lang:t('time.noon')) end
|
|
end, 'admin')
|
|
|
|
QBCore.Commands.Add('evening', Lang:t('help.eveningcommand'), {}, false, function(source)
|
|
setTime(18, 0)
|
|
if source > 0 then return TriggerClientEvent('QBCore:Notify', source, Lang:t('time.evening')) end
|
|
end, 'admin')
|
|
|
|
QBCore.Commands.Add('night', Lang:t('help.nightcommand'), {}, false, function(source)
|
|
setTime(23, 0)
|
|
if source > 0 then return TriggerClientEvent('QBCore:Notify', source, Lang:t('time.night')) end
|
|
end, 'admin')
|
|
|
|
QBCore.Commands.Add('time', Lang:t('help.timecommand'), {{ name=Lang:t('help.timehname'), help=Lang:t('help.timeh') }, { name=Lang:t('help.timemname'), help=Lang:t('help.timem') }}, true, function(source, args)
|
|
local success = setTime(args[1], args[2])
|
|
if source > 0 then
|
|
if (success) then return TriggerClientEvent('QBCore:Notify', source, Lang:t('time.changec', {value = args[1] .. ':' .. (args[2] or "00")})) end
|
|
return TriggerClientEvent('QBCore:Notify', source, Lang:t('time.invalidc'), 'error')
|
|
end
|
|
if (success) then return print(Lang:t('time.change', {value = args[1], value2 = args[2] or "00"})) end
|
|
return print(Lang:t('time.invalid'))
|
|
end, 'admin')
|
|
|
|
-- THREAD LOOPS
|
|
CreateThread(function()
|
|
local previous = 0
|
|
local realTimeFromApi = nil
|
|
local failedCount = 0
|
|
|
|
while true do
|
|
Wait(0)
|
|
local newBaseTime = os.time(os.date("!*t")) / 2 + 360 --Set the server time depending of OS time
|
|
if Config.RealTimeSync then
|
|
newBaseTime = os.time(os.date("!*t")) --Set the server time depending of OS time
|
|
if realTimeFromApi == nil then
|
|
retrieveTimeFromApi(function(unixTime)
|
|
realTimeFromApi = unixTime -- Set the server time depending on real-time retrieved from API
|
|
end)
|
|
end
|
|
while realTimeFromApi == nil do
|
|
if failedCount > 10 then
|
|
print("Failed to retrieve real time from API, falling back to local time")
|
|
break
|
|
end
|
|
failedCount = failedCount + 1
|
|
Wait(100)
|
|
end
|
|
if realTimeFromApi ~= nil then
|
|
newBaseTime = realTimeFromApi
|
|
end
|
|
end
|
|
if (newBaseTime % 60) ~= previous then --Check if a new minute is passed
|
|
previous = newBaseTime % 60 --Only update time with plain minutes, seconds are handled in the client
|
|
if freezeTime then
|
|
timeOffset = timeOffset + baseTime - newBaseTime
|
|
end
|
|
baseTime = newBaseTime
|
|
end
|
|
end
|
|
end)
|
|
|
|
CreateThread(function()
|
|
while true do
|
|
Wait(2000)--Change to send every minute in game sync
|
|
TriggerClientEvent('qb-weathersync:client:SyncTime', -1, baseTime, timeOffset, freezeTime)
|
|
end
|
|
end)
|
|
|
|
CreateThread(function()
|
|
while true do
|
|
Wait(300000)
|
|
TriggerClientEvent('qb-weathersync:client:SyncWeather', -1, CurrentWeather, blackout)
|
|
end
|
|
end)
|
|
|
|
CreateThread(function()
|
|
while true do
|
|
newWeatherTimer = newWeatherTimer - 1
|
|
Wait((1000 * 60) * Config.NewWeatherTimer)
|
|
if newWeatherTimer == 0 then
|
|
if Config.DynamicWeather then
|
|
nextWeatherStage()
|
|
end
|
|
newWeatherTimer = Config.NewWeatherTimer
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- EXPORTS
|
|
exports('nextWeatherStage', nextWeatherStage)
|
|
exports('setWeather', setWeather)
|
|
exports('setTime', setTime)
|
|
exports('setBlackout', setBlackout)
|
|
exports('setTimeFreeze', setTimeFreeze)
|
|
exports('setDynamicWeather', setDynamicWeather)
|
|
exports('getBlackoutState', function() return blackout end)
|
|
exports('getTimeFreezeState', function() return freezeTime end)
|
|
exports('getWeatherState', function() return CurrentWeather end)
|
|
exports('getDynamicWeather', function() return Config.DynamicWeather end)
|
|
|
|
exports('getTime', function()
|
|
local hour = math.floor(((baseTime+timeOffset)/60)%24)
|
|
local minute = math.floor((baseTime+timeOffset)%60)
|
|
|
|
return hour,minute
|
|
end)
|