-
Notifications
You must be signed in to change notification settings - Fork 139
Expand file tree
/
Copy pathserver.lua
More file actions
375 lines (341 loc) · 14.1 KB
/
Copy pathserver.lua
File metadata and controls
375 lines (341 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
local QBCore = exports['qb-core']:GetCoreObject({ 'Commands' })
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 api.timezonedb.com
local function retrieveTimeFromApi(callback)
Citizen.CreateThread(function()
local apiKey = 'REPLACE_ME_TO_YOUR_API' -- 🔐 Replace with your actual key from your email
local zone = 'America/Los_Angeles' -- 🔐 Replace with your actual TimeZone, ex: America/Los_Angeles
local url = 'http://api.timezonedb.com/v2.1/get-time-zone?key=' .. apiKey .. '&format=json&by=zone&zone=' .. zone
-- print(response) -- 🛠️ Debug: uncomment to inspect raw API response
PerformHttpRequest(url, function(statusCode, response)
if statusCode == 200 and response then
local data = json.decode(response)
if data and data.timestamp then
callback(data.timestamp)
return
end
end
callback(nil)
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(60000) -- ⏱️ Sync server time every 1 minute with real time API. Falls back to OS time if failed.
local newBaseTime = os.time(os.date('!*t')) / 2 + 360 --Set the server time depending of OS time
if Config.RealTimeSync then
retrieveTimeFromApi(function(unixTime)
if unixTime then
baseTime = unixTime
else
baseTime = os.time(os.date('!*t'))
end
end)
else
baseTime = os.time(os.date('!*t')) / 2 + 360
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)