├── LICENSE ├── README.md ├── client ├── extra_client.lua ├── time_client.lua └── weather_client.lua ├── config.lua ├── fxmanifest.lua └── server ├── extra_server.lua ├── time_server.lua └── weather_server.lua /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ParadoxWorldSync 2 | World syncing and weather system with bucket support and preset dimmesions with QBCore support. 3 | 4 | #### FiveM Topic: https://forum.cfx.re/t/free-qb-paradox-world-sync-smooth-time-and-weather-sync/4970361 5 | 6 | ### What makes this different than other scripts? 7 | What I am calling “smooth sync”, no longer will you ever (hopefully) see the sky jitter at all. By variaing the time per minute to match near the server time (so if we are behind we speed up time slightly) we can maintain fully synced time (or within a minute) while keeping it optimized with statebags. Basically all you need to know is that you should never see the sky stutter or hitch at all unless a major change is made! This script also supports per-weather buckets allowing you to have unique experiences per world. For instance we have a 666666 world that we used for our Halloween event where we bucketed people to this world which was the “underworld”. 8 | 9 | ### Note! 10 | This script is in BETA and it basically is fully functioning minus some things like making sure the config option for time per minute will replicate correctly so I would not mess with `Config.TimeScaler` unless you want to toy around with the timer loop on the server side. Its also kinda jank sometimes with the weather systems and I have rewritten it a few times but I haven't found a flow I like that allows overridding so any PRs are appreciated! 11 | 12 | ## Commands 13 | 14 | ### Weather 15 | > `weather ` - Overrides current weather to this weather system or GTA default weather type 16 | 17 | > `freezeweather` - Toggles freezing of weather 18 | 19 | > `skipweather` - Trigger a new random weather change 20 | 21 | > `enablesnow ` - Enables/disables snow 22 | 23 | ### Time 24 | > `time ` - Set time to specified hour and minute 25 | 26 | > `freezetime ` - Freeze the time 27 | 28 | ### Extras 29 | > `blackout ` - Enables blackout (does not affect car lights) 30 | 31 | 32 | ## Events 33 | 34 | ### Server 35 | > `ParadoxWorld:server:setBlackout` `bool BlackoutState` - Set blackout from another script 36 | 37 | ### Client 38 | > `ParadoxWeather:client:snowToggled` `bool Enabled` - Emitted when snow is enabled from client 39 | 40 | How to override time/weather for a single client (apartment/housing) 41 | ```lua 42 | TriggerEvent('ParadoxTime:client:setOverrideData', {hours = 21, minutes = 0, seconds = 0, TimeScaler = 999999999}) 43 | TriggerEvent('ParadoxWeather:client:setOverrideData', 'EXTRASUNNY') 44 | -- Call without args to reset to global clock 45 | ``` 46 | 47 | ## Extras 48 | This has a snow plow effect built in. Whenever it snows it will reset the snowplowed variable. If you call this from another script (say a snowplowing script) then it will disable the snow traction (the slippery effect) while keeping snow on the ground. 49 | 50 | 51 | Credit: 52 | 53 | Vespura - vSync for the shift to hours,minutes functions on serverside: https://github.com/DevTestingPizza/vSync 54 | -------------------------------------------------------------------------------- /client/extra_client.lua: -------------------------------------------------------------------------------- 1 | TimeBucket, WeatherBucket, BlackoutBucket = false, false, false 2 | BlackoutOveride = false 3 | CurrentWorldData = {} 4 | 5 | function UpdateBucket() 6 | if not CurrentWorldData then return end 7 | SetArtificialLightsState(false) 8 | SetArtificialLightsStateAffectsVehicles(false) 9 | if CurrentWorldData[LocalPlayer.state.bucket] then 10 | local BucketData = CurrentWorldData[LocalPlayer.state.bucket] 11 | if not TimeOverideData then 12 | if BucketData.hours then 13 | TimeBucket = true -- Activate bucket bypass 14 | NetworkOverrideClockTime(BucketData.hours, BucketData.minutes, 0) 15 | NetworkOverrideClockMillisecondsPerGameMinute(999999999) 16 | else 17 | NetworkOverrideClockMillisecondsPerGameMinute(ServerTime.TimeScaler) 18 | NetworkOverrideClockTime(ServerTime.hours, ServerTime.minutes, 0) 19 | TimeBucket = false 20 | end 21 | end 22 | 23 | if BucketData.blackout then 24 | BlackoutBucket = true 25 | SetArtificialLightsState(toboolean(BucketData.blackout)) 26 | SetArtificialLightsStateAffectsVehicles(false) 27 | else 28 | SetArtificialLightsState(toboolean(GlobalState.currentBlackout) or false) 29 | SetArtificialLightsStateAffectsVehicles(false) 30 | BlackoutBucket = false 31 | end 32 | if toboolean(BucketData.HasSnow) then 33 | UpdateWeatherParticles(true) 34 | else 35 | UpdateWeatherParticles(false) 36 | end 37 | if not WeatherOverideData then 38 | if BucketData.weather then 39 | WeatherBucket = true 40 | SetWeatherTypeNowPersist(BucketData.weather) 41 | else 42 | SetWeatherTypeNowPersist(ServerWeather.currentWeather) 43 | WeatherBucket = false 44 | end 45 | end 46 | end 47 | end 48 | 49 | AddStateBagChangeHandler('bucket' --[[key filter]], nil --[[bag filter]], function(bagName, key, value, _unused, replicated) 50 | if not value or not bagName then return end 51 | local plyId = tonumber(bagName:gsub('player:', ''), 10) 52 | if plyId ~= GetPlayerServerId(PlayerId()) then return end 53 | value = tonumber(value) 54 | if value == 0 then 55 | -- UNSET OVERRIDES -- 56 | TimeBucket = false 57 | WeatherBucket = false 58 | BlackoutBucket = false 59 | NetworkOverrideClockMillisecondsPerGameMinute(ServerTime.TimeScaler) 60 | NetworkOverrideClockTime(ServerTime.hours, ServerTime.minutes, 0) 61 | SetWeatherTypeNowPersist(ServerWeather.currentWeather) 62 | SetArtificialLightsState(false) 63 | SetArtificialLightsStateAffectsVehicles(false) 64 | if GlobalState.currentBlackout then 65 | SetArtificialLightsState(toboolean(GlobalState.currentBlackout)) 66 | end 67 | if (ServerWeather.HasSnow or OverideSnow) then 68 | UpdateWeatherParticles(true) 69 | else 70 | UpdateWeatherParticles(false) 71 | end 72 | elseif not WeatherOverideData and not TimeOverideData then 73 | QBCore.Functions.TriggerCallback("ParadoxWorld:server:GetDimmensionData", function(WorldData) 74 | if WorldData then 75 | CurrentWorldData = WorldData 76 | end 77 | UpdateBucket() 78 | end, value) 79 | end 80 | end) 81 | 82 | RegisterNetEvent('ParadoxWorld:client:updateBucketData', function(bucket, data) 83 | if not bucket or not data then return end 84 | local currentBucket = LocalPlayer.state.bucket 85 | if currentBucket and currentBucket == bucket then 86 | CurrentWorldData = data 87 | UpdateBucket() 88 | end 89 | end) 90 | 91 | AddStateBagChangeHandler('currentBlackout' --[[key filter]], nil --[[bag filter]], function(bagName, key, value, _unused, replicated) 92 | if value == nil or BlackoutOveride or BlackoutBucket then return end 93 | -- Can do cooler shut off sequence if we want in here -- 94 | SetArtificialLightsState(toboolean(value)) 95 | SetArtificialLightsStateAffectsVehicles(false) 96 | end) -------------------------------------------------------------------------------- /client/time_client.lua: -------------------------------------------------------------------------------- 1 | QBCore = exports['qb-core']:GetCoreObject() 2 | ServerTime = GlobalState.currentTime 3 | TimeOverideData = nil -- Allows fun stuff 4 | 5 | -- Some startup shit -- 6 | CreateThread(function () 7 | NetworkOverrideClockTime(ServerTime.hours, ServerTime.minutes, ServerTime.seconds) 8 | NetworkOverrideClockMillisecondsPerGameMinute(Config.TimeScaler) -- Double time 9 | TriggerEvent('chat:addSuggestion', '/time', 'Set the current time', {{name = 'hour', help = 'HH'}, {name = 'minute', help = 'mm'}}) 10 | TriggerEvent('chat:addSuggestion', '/freezetime', 'Sets time and freezes it', {{name = 'hour', help = 'HH'}, {name = 'minute', help = 'mm'}}) 11 | end) 12 | 13 | AddStateBagChangeHandler('currentTime' --[[key filter]], nil --[[bag filter]], function(bagName, key, value, _unused, replicated) -- Keep time updated 14 | if not value then return end 15 | Wait(150) 16 | ServerTime = GlobalState.currentTime -- Update time 17 | if TimeOverideData or TimeBucket then return end -- Break out so we don't override 18 | 19 | -- Start Time Sync -- 20 | local hours, minutes, seconds = GetClockHours(), GetClockMinutes(), GetClockSeconds() 21 | local TimeVariance = math.abs(minutes - ServerTime.minutes) 22 | -- print('Time variance:', TimeVariance) 23 | if TimeVariance > 11 and TimeVariance <= 60 then -- Check for end of hour so a slight error doesnt become huge because of a hour tickover 24 | if math.abs(hours - ServerTime.hours) <= 1 then 25 | local debugBefore = TimeVariance 26 | TimeVariance = math.abs(minutes - ServerTime.minutes) - 60 27 | --print('New time variance:', TimeVariance, 'Old time variance:', debugBefore) 28 | end 29 | end 30 | --print('Update', TimeVariance, minutes - ServerTime.minutes, minutes, ServerTime.minutes) 31 | if hours ~= ServerTime.hours and math.abs(hours - ServerTime.hours) > 1 then 32 | NetworkOverrideClockMillisecondsPerGameMinute(ServerTime.TimeScaler) 33 | NetworkOverrideClockTime(ServerTime.hours, ServerTime.minutes, seconds) 34 | elseif TimeVariance >= 10 then -- BIG CHANGE 35 | NetworkOverrideClockMillisecondsPerGameMinute(ServerTime.TimeScaler) 36 | NetworkOverrideClockTime(ServerTime.hours, ServerTime.minutes, seconds) 37 | -- This should basically dynamically redo the time-per-minute (we don't care) to variance slightly to catch up to/slow down to the current time 38 | -- This will hopefully find an equilibrium for all people based on their current FPS. 39 | -- Should prevent any "Stuttery Sky" 40 | elseif TimeVariance >= 3 and TimeVariance < 10 then 41 | if minutes - ServerTime.minutes >= 3 then -- Ahead 42 | local Scaler = (ServerTime.TimeScaler) + ((TimeVariance * 1000) / (ServerTime.TimeScaler / 100)) 43 | NetworkOverrideClockMillisecondsPerGameMinute(math.ceil(Scaler)) 44 | --NetworkOverrideClockTime(hours, minutes, seconds) 45 | --print('Current Time Scaler Ahead:', math.ceil(Scaler), hours, minutes, seconds, GetMillisecondsPerGameMinute()) 46 | elseif minutes - ServerTime.minutes <= -3 then -- Behind 47 | local Scaler = (ServerTime.TimeScaler) - ((TimeVariance * 1000) / (ServerTime.TimeScaler / 100)) 48 | NetworkOverrideClockMillisecondsPerGameMinute(math.ceil(Scaler)) 49 | -- NetworkOverrideClockTime(hours, minutes, seconds) 50 | --print('Current Time Scaler Behind:', math.ceil(Scaler), hours, minutes, seconds, GetMillisecondsPerGameMinute()) 51 | end 52 | end 53 | end) 54 | 55 | RegisterNetEvent('ParadoxTime:client:setOverrideData', function(data) 56 | if data then 57 | TimeOverideData = data 58 | local hours, minutes, seconds = GetClockHours(), GetClockMinutes(), GetClockSeconds() 59 | if hours ~= TimeOverideData.hours or math.abs(minutes - TimeOverideData.minutes) >= 2 then -- Buffer for error of margin 60 | NetworkOverrideClockTime(TimeOverideData.hours, TimeOverideData.minutes, TimeOverideData.seconds) 61 | end 62 | NetworkOverrideClockMillisecondsPerGameMinute(TimeOverideData.TimeScaler) 63 | else -- RESET TO GLOBAL 64 | TimeOverideData = nil 65 | ServerTime = GlobalState.currentTime -- Update time JIC 66 | local hours, minutes, seconds = GetClockHours(), GetClockMinutes(), GetClockSeconds() 67 | if hours ~= ServerTime.hours or math.abs(minutes - ServerTime.minutes) >= 2 then -- Buffer for error of margin 68 | NetworkOverrideClockTime(ServerTime.hours, ServerTime.minutes, seconds) 69 | end 70 | NetworkOverrideClockMillisecondsPerGameMinute(ServerTime.TimeScaler) 71 | end 72 | end) 73 | -------------------------------------------------------------------------------- /client/weather_client.lua: -------------------------------------------------------------------------------- 1 | ServerWeather = GlobalState.currentWeather 2 | SnowEnabled = false 3 | WeatherOverideData = nil -- Allows fun stuff 4 | OverideSnow = false 5 | 6 | -- Some startup shit -- 7 | CreateThread(function () 8 | RemoveIpl('alamo_ice') -- NVE addon resource 9 | SetWind(0.1) 10 | WaterOverrideSetStrength(0.5) 11 | Wait(5000) 12 | SetWeatherTypeNowPersist(ServerWeather.currentWeather) 13 | TriggerEvent('chat:addSuggestion', '/weather', 'Set the current weather', {{name = 'weather', help = 'Weather name or blank for list'}}) 14 | TriggerEvent('chat:addSuggestion', '/freezeweather', 'Freeze current weather', {}) 15 | end) 16 | 17 | -- loads/unloads the snow fx particles if needed 18 | function UpdateWeatherParticles(enable) 19 | if enable == true then 20 | SnowEnabled = true 21 | -- GlobalState.snowPlowed is something you can set in another script! 22 | if not GlobalState.snowPlowed then 23 | SetForceVehicleTrails(true) 24 | SetForcePedFootstepsTracks(true) 25 | end 26 | RequestScriptAudioBank('ICE_FOOTSTEPS', false) -- Icey footsteps 27 | RequestScriptAudioBank('SNOW_FOOTSTEPS', false) -- Snowy footsteps 28 | RequestNamedPtfxAsset('core_snow') -- Last part here is the blowing snow PTFX 29 | while not HasNamedPtfxAssetLoaded('core_snow') do Citizen.Wait(100) end 30 | UseParticleFxAssetNextCall('core_snow') 31 | ForceSnowPass(true) 32 | RequestIpl('alamo_ice') -- NVE addon resource 33 | WaterOverrideSetStrength(0.9) 34 | else 35 | SnowEnabled = false 36 | SetForceVehicleTrails(false) 37 | SetForcePedFootstepsTracks(false) 38 | ReleaseScriptAudioBank('ICE_FOOTSTEPS') 39 | ReleaseScriptAudioBank('SNOW_FOOTSTEPS') 40 | ForceSnowPass(false) 41 | if HasNamedPtfxAssetLoaded('core_snow') then RemoveNamedPtfxAsset('core_snow') end 42 | RemoveIpl('alamo_ice') -- NVE addon resource 43 | WaterOverrideSetStrength(0.5) 44 | end 45 | TriggerEvent('ParadoxWeather:client:snowToggled', enable) 46 | end 47 | 48 | function toboolean(str) 49 | local bool = false 50 | if tostring(str) == "true" then 51 | bool = true 52 | end 53 | return bool 54 | end 55 | 56 | 57 | AddStateBagChangeHandler('currentWeather' --[[key filter]], nil --[[bag filter]], function(bagName, key, value, _unused, replicated) 58 | if not value then return end 59 | Wait(250) 60 | ServerWeather = value 61 | if WeatherOverideData or WeatherBucket then return end 62 | SetRainLevel(-1.0) 63 | SetWeatherTypeOvertimePersist(ServerWeather.currentWeather, 60.0) 64 | 65 | if ServerWeather.WindDirection then 66 | SetWindDirection(math.rad(ServerWeather.WindDirection)) 67 | end 68 | 69 | if ServerWeather.WindSpeed then 70 | SetWind(ServerWeather.WindSpeed / 5) 71 | end 72 | 73 | if (ServerWeather.HasSnow or OverideSnow) and not SnowEnabled then 74 | UpdateWeatherParticles(true) 75 | elseif not (ServerWeather.HasSnow or OverideSnow) and SnowEnabled then 76 | if SnowEnabled then 77 | UpdateWeatherParticles(false) 78 | end 79 | end 80 | 81 | end) 82 | 83 | AddStateBagChangeHandler('snowEnabled' --[[key filter]], nil --[[bag filter]], function(bagName, key, value, _unused, replicated) 84 | if not value then return end 85 | OverideSnow = toboolean(value) 86 | UpdateWeatherParticles(OverideSnow) 87 | end) 88 | 89 | -- GlobalState.snowPlowed is something you can set in another script! 90 | AddStateBagChangeHandler('snowPlowed' --[[key filter]], nil --[[bag filter]], function(bagName, key, value, _unused, replicated) 91 | if not value then return end 92 | if value and SnowEnabled then 93 | print('SNOW PLOWED') 94 | SetForceVehicleTrails(false) 95 | SetForcePedFootstepsTracks(false) 96 | end 97 | end) 98 | 99 | 100 | RegisterNetEvent('ParadoxWeather:client:setOverrideData', function(data) 101 | if data then 102 | WeatherOverideData = data 103 | if WeatherOverideData == 'EXTRASUNNY' then 104 | UpdateWeatherParticles(false) 105 | end 106 | SetWeatherTypePersist(WeatherOverideData) 107 | SetWeatherTypeNow(WeatherOverideData) 108 | SetWeatherTypeNowPersist(WeatherOverideData) 109 | else -- RESET TO GLOBAL 110 | WeatherOverideData = nil 111 | ServerWeather = GlobalState.currentWeather 112 | SetRainLevel(-1.0) 113 | if ServerWeather.WindDirection then 114 | SetWindDirection(math.rad(ServerWeather.WindDirection)) 115 | end 116 | 117 | if ServerWeather.WindSpeed then 118 | SetWind(ServerWeather.WindSpeed / 5) 119 | end 120 | 121 | if ServerWeather.HasSnow then 122 | UpdateWeatherParticles(true) 123 | else 124 | if SnowEnabled then 125 | UpdateWeatherParticles(false) 126 | end 127 | end 128 | 129 | SetWeatherTypeOvertimePersist(ServerWeather.currentWeather, 15) 130 | end 131 | end) -------------------------------------------------------------------------------- /config.lua: -------------------------------------------------------------------------------- 1 | Config = Config or {} 2 | Config.PermsGroup = 'god' 3 | 4 | -- Preset Dimmensions -- 5 | Config.PresetWorlds = { 6 | [666666] = { -- HALLOWEEN UNDERWORLD 7 | weather = 'HALLOWEEN', 8 | hours = 23, 9 | minutes = 59, 10 | blackout = true, 11 | }, 12 | [1225] = { -- Christmas Land 13 | weather = 'SNOW', 14 | HasSnow = true, -- Optional 15 | } 16 | } 17 | 18 | --- TIME OPTIONS --- 19 | Config.TimeScaler = 4000 -- MS per minute in GTA time (2000ms is normal for a 48 minute day) 20 | Config.StartupTime = { 21 | hours = 16, 22 | minutes = 00, 23 | seconds = 00 24 | } 25 | 26 | --- Weather Options --- 27 | Config.WeatherPool = {} -- Leave blank (Probability Pool) 28 | Config.WeatherStr = '' -- leave blank 29 | Config.StartupWeather = 'CLOUDS' 30 | Config.WeatherCycleTime = 24 -- minutes (MUST BE LONGER THAN THE LONGEST SEQUENCE) 31 | Config.WeatherEvents = { 32 | ['SUNNY'] = { 33 | Probability = 5, 34 | WindSpeed = 0.0, 35 | WindDirection = 0.0, -- Storms come from the south 36 | Options = {'EXTRASUNNY', 'CLEAR', 'SMOG'}, 37 | }, 38 | ['CLOUDY'] = { 39 | Probability = 10, 40 | WindSpeed = 0.5, 41 | WindDirection = 0.0, -- Storms come from the south 42 | Options = {'OVERCAST', 'CLOUDS', 'SNOWLIGHT'}, 43 | }, 44 | ['SNOWING'] = { 45 | Probability = 3, 46 | WindDirection = 180.0, -- Storms come from the south 47 | Alert = 'WINTERWATCH', 48 | Sequence = { 49 | [1] = { 50 | Weather = 'OVERCAST', 51 | Time = 10, -- Minutes 52 | WindSpeed = 0.0, 53 | }, 54 | [2] = { 55 | Weather = 'SNOWLIGHT', 56 | Time = 10, -- Minutes 57 | WindSpeed = 0.1, 58 | }, 59 | [3] = { 60 | Weather = 'SNOW', 61 | Time = 5, -- Minutes 62 | WindSpeed = 0.3, 63 | }, 64 | [4] = { 65 | Weather = 'SNOWLIGHT', 66 | Time = 10, -- Minutes 67 | WindSpeed = 0.1, 68 | }, 69 | [5] = { 70 | Weather = 'OVERCAST', 71 | Time = 5, -- Minutes 72 | WindSpeed = 0.0, 73 | }, 74 | [6] = { 75 | Weather = 'CLOUDS', 76 | Time = 5, -- Minutes 77 | WindSpeed = 0.0, 78 | }, 79 | }, 80 | }, 81 | ['SNOWSTORM'] = { 82 | Probability = 2, 83 | WindDirection = 120.0, -- Storms come from the south 84 | Alert = 'WINTERWARNING', 85 | Sequence = { 86 | [1] = { 87 | Weather = 'OVERCAST', 88 | Time = 10, -- Minutes 89 | WindSpeed = 0.5, 90 | }, 91 | [2] = { 92 | Weather = 'SNOWLIGHT', 93 | Time = 5, -- Minutes 94 | WindSpeed = 1.0, 95 | }, 96 | [3] = { 97 | Weather = 'SNOW', 98 | Time = 5, -- Minutes 99 | WindSpeed = 1.0, 100 | }, 101 | [4] = { 102 | Weather = 'SNOW', 103 | Time = 10, -- Minutes 104 | WindSpeed = 1.0, 105 | HasSnow = true, 106 | }, 107 | [5] = { 108 | Weather = 'BLIZZARD', 109 | Time = 14, -- Minutes 110 | WindSpeed = 3.0, 111 | HasSnow = true, 112 | }, 113 | [6] = { 114 | Weather = 'SNOW', 115 | Time = 15, -- Minutes 116 | WindSpeed = 2.0, 117 | HasSnow = true, 118 | }, 119 | [7] = { 120 | Weather = 'SNOWLIGHT', 121 | Time = 20, -- Minutes 122 | WindSpeed = 1.0, 123 | HasSnow = true, 124 | }, 125 | [8] = { 126 | Weather = 'OVERCAST', 127 | WindSpeed = 0.5, 128 | Time = 15, -- Minutes 129 | HasSnow = true, 130 | }, 131 | [9] = { 132 | Weather = 'CLOUDS', 133 | WindSpeed = 0.5, 134 | Time = 15, -- Minutes 135 | HasSnow = true, 136 | }, 137 | [10] = { 138 | Weather = 'CLEAR', 139 | WindSpeed = 0.5, 140 | Time = 15, -- Minutes 141 | HasSnow = true, 142 | }, 143 | [11] = { 144 | Weather = 'EXTRASUNNY', 145 | WindSpeed = 0.5, 146 | }, 147 | }, 148 | }, 149 | -- NON AUTO CHOOSING -- 150 | -- Removing Probability removes the auto-choosing -- 151 | ['RAINSHOWER'] = { 152 | WindDirection = 240.0, -- Storms come from the south 153 | Sequence = { 154 | [1] = { 155 | Weather = 'CLOUDS', 156 | Time = 2, -- Minutes 157 | WindSpeed = 0.5, 158 | }, 159 | [2] = { 160 | Weather = 'OVERCAST', 161 | Time = 8, -- Minutes 162 | WindSpeed = 1.0, 163 | }, 164 | [3] = { 165 | Weather = 'RAIN', 166 | Time = 10, -- Minutes 167 | WindSpeed = 2.0, 168 | }, 169 | [4] = { 170 | Weather = 'CLEARING', 171 | Time = 4, -- Minutes 172 | WindSpeed = 1.0, 173 | }, 174 | [5] = { 175 | Weather = 'CLOUDS', 176 | Time = 10, -- Minutes 177 | WindSpeed = 0.5, 178 | }, 179 | [6] = { 180 | Weather = 'EXTRASUNNY', 181 | WindSpeed = 0.0, 182 | }, 183 | }, 184 | }, 185 | ['RAINSTORM'] = { 186 | WindDirection = 280.0, -- Storms come from the south 187 | Sequence = { 188 | [1] = { 189 | Weather = 'CLOUDS', 190 | Time = 4, -- Minutes 191 | WindSpeed = 0.5, 192 | }, 193 | [2] = { 194 | Weather = 'OVERCAST', 195 | Time = 8, -- Minutes 196 | WindSpeed = 1.0, 197 | }, 198 | [3] = { 199 | Weather = 'RAIN', 200 | Time = 10, -- Minutes 201 | WindSpeed = 3.5, 202 | }, 203 | [4] = { 204 | Weather = 'CLEARING', 205 | Time = 6, -- Minutes 206 | WindSpeed = 1.5, 207 | }, 208 | [5] = { 209 | Weather = 'CLOUDS', 210 | WindSpeed = 0.5, 211 | }, 212 | }, 213 | }, 214 | ['SMALLSTORM'] = { 215 | WindDirection = 120.0, -- Storms come from the south 216 | Alert = 'WATCH', 217 | Sequence = { 218 | [1] = { 219 | Weather = 'CLOUDS', 220 | Time = 4, -- Minutes 221 | WindSpeed = 0.5, 222 | }, 223 | [2] = { 224 | Weather = 'RAIN', 225 | Time = 4, -- Minutes 226 | WindSpeed = 1.0, 227 | }, 228 | [3] = { 229 | Weather = 'THUNDER', 230 | Time = 4, -- Minutes 231 | WindSpeed = 3.0, 232 | }, 233 | [4] = { 234 | Weather = 'RAIN', 235 | Time = 10, -- Minutes 236 | WindSpeed = 2.0, 237 | }, 238 | [5] = { 239 | Weather = 'CLEARING', 240 | Time = 6, -- Minutes 241 | WindSpeed = 1.0, 242 | }, 243 | [6] = { 244 | Weather = 'EXTRASUNNY', 245 | WindSpeed = 0.5, 246 | }, 247 | }, 248 | }, 249 | ['BIGSTORM'] = { 250 | WindDirection = 180.0, -- Storms come from the south 251 | Alert = 'WARNING', 252 | Sequence = { 253 | [1] = { 254 | Weather = 'OVERCAST', 255 | Time = 2, -- Minutes 256 | WindSpeed = 4.0, 257 | }, 258 | [2] = { 259 | Weather = 'RAIN', 260 | Time = 4, -- Minutes 261 | WindSpeed = 8.0, 262 | }, 263 | [3] = { 264 | Weather = 'THUNDER', 265 | Time = 7, -- Minutes 266 | WindSpeed = 12.0, 267 | }, 268 | [4] = { 269 | Weather = 'RAIN', 270 | Time = 2, -- Minutes 271 | WindSpeed = 12.0, 272 | }, 273 | [5] = { 274 | Weather = 'THUNDER', 275 | Time = 2, -- Minutes 276 | WindSpeed = 12.0, 277 | }, 278 | [6] = { 279 | Weather = 'CLEARING', 280 | Time = 3, -- Minutes 281 | WindSpeed = 3.0, 282 | }, 283 | [7] = { 284 | Weather = 'EXTRASUNNY', 285 | WindSpeed = 0.0, 286 | }, 287 | }, 288 | }, 289 | } 290 | 291 | Config.ValidWeathers = { 292 | 'BLIZZARD', 293 | 'CLEAR', 294 | 'CLEARING', 295 | 'CLOUDS', 296 | 'EXTRASUNNY', 297 | 'FOGGY', 298 | 'HALLOWEEN', 299 | 'NEUTRAL', 300 | 'OVERCAST', 301 | 'RAIN', 302 | 'SMOG', 303 | 'SNOW', 304 | 'SNOWLIGHT', 305 | 'THUNDER', 306 | 'XMAS', 307 | } 308 | 309 | CreateThread(function () -- Generate Probability Pool 310 | for WeatherSystem,Data in pairs(Config.WeatherEvents) do 311 | if Data.Probability then 312 | for i=1, Data.Probability do 313 | Config.WeatherPool[#Config.WeatherPool+1] = WeatherSystem 314 | end 315 | end 316 | end 317 | 318 | for WeatherSystem,Data in pairs(Config.WeatherEvents) do 319 | Config.WeatherStr = WeatherSystem..', '..Config.WeatherStr 320 | end 321 | end) -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | lua54 'yes' 4 | use_fxv2_oal 'yes' 5 | 6 | name 'Paradox Gaming Weather + Time System' 7 | discord 'discord.gg/paradoxgaming' 8 | author 'JnKTechstuff' 9 | version '0.8' 10 | 11 | shared_script 'config.lua' 12 | 13 | client_scripts { 14 | "client/*.lua", 15 | } 16 | 17 | server_scripts { 18 | "server/*.lua" 19 | } 20 | 21 | -------------------------------------------------------------------------------- /server/extra_server.lua: -------------------------------------------------------------------------------- 1 | local QBCore = exports['qb-core']:GetCoreObject() 2 | WorldData = Config.PresetWorlds or {} 3 | GlobalState.currentBlackout = false 4 | 5 | QBCore.Functions.CreateCallback('ParadoxWorld:server:GetDimmensionData', function(source, cb, WorldID) 6 | cb(WorldData) 7 | end) 8 | 9 | RegisterNetEvent('ParadoxWorld:server:setWorldData', function(worldid, weather, hours, minutes, blackout) 10 | WorldData[worldid] = { 11 | weather = weather, 12 | hours = hours, 13 | minutes = minutes, 14 | blackout = blackout, 15 | } 16 | TriggerClientEvent('ParadoxWorld:client:updateBucketData', -1, worldid, WorldData) 17 | end) 18 | 19 | RegisterCommand('blackout', function(source, args) 20 | if source == 0 or QBCore.Functions.HasPermission(source, Config.PermsGroup) then 21 | if args[1] ~= nil then 22 | local worldid = 0 23 | if source ~= 0 then 24 | worldid = GetPlayerRoutingBucket(source) 25 | end 26 | if worldid == 0 then 27 | GlobalState.currentBlackout = args[1] 28 | if source == 0 then 29 | print('[^5WORLD SYNC SYSTEM^0] Blackout enabled:', GlobalState.currentBlackout) 30 | else 31 | TriggerClientEvent('QBCore:Notify', source, 'Blackout enabled: '..GlobalState.currentBlackout, 'error') 32 | end 33 | else 34 | if not WorldData[worldid] then WorldData[worldid] = {} end 35 | WorldData[worldid].blackout = args[1] 36 | TriggerClientEvent('ParadoxWorld:client:updateBucketData', -1, worldid, WorldData) 37 | TriggerClientEvent('QBCore:Notify', source, 'Blackout enabled: '..WorldData[worldid].blackout..' in dimmension: '..worldid, 'success') 38 | end 39 | else 40 | if source == 0 then 41 | print('[^5WORLD SYNC SYSTEM^0] You must specify an argument (true or false)') 42 | else 43 | TriggerClientEvent('QBCore:Notify', source, 'You must specify an argument (true or false)', 'error') 44 | end 45 | end 46 | end 47 | end, false) 48 | 49 | RegisterNetEvent('ParadoxWorld:server:setBlackout', function(BlackoutState) 50 | if BlackoutState == nil then 51 | BlackoutState = not GlobalState.currentBlackout 52 | end 53 | BlackoutState = tostring(BlackoutState) 54 | GlobalState.currentBlackout = BlackoutState 55 | print('[^5WORLD SYNC SYSTEM^0] Blackout enabled:', BlackoutState) 56 | end) -------------------------------------------------------------------------------- /server/time_server.lua: -------------------------------------------------------------------------------- 1 | local QBCore = exports['qb-core']:GetCoreObject() 2 | local freezeTime = false 3 | local baseTime = nil 4 | local currentHour, currentMinute, currentSecond = Config.StartupTime.hours, Config.StartupTime.minutes, Config.StartupTime.seconds 5 | local timeOffset = 0 6 | local CurrentScaler = Config.TimeScaler 7 | 8 | -- Functions -- 9 | function ShiftToMinute(minute) 10 | timeOffset = timeOffset - ( ( (baseTime+timeOffset) % 60 ) - minute ) 11 | end 12 | 13 | function ShiftToHour(hour) 14 | timeOffset = timeOffset - ( ( ((baseTime+timeOffset)/60) % 24 ) - hour ) * 60 15 | end 16 | 17 | function OverideGlobalClock(hours, minutes) 18 | ShiftToHour(hours) 19 | ShiftToMinute(minutes) 20 | currentHour = math.floor(((baseTime+timeOffset)/60)%24) 21 | currentMinute = math.floor((baseTime+timeOffset)%60) 22 | end 23 | 24 | -- Threads -- 25 | CreateThread(function() 26 | local startUp = {currentTime = Config.StartupTime} 27 | baseTime = os.time(os.date("!*t"))/(Config.TimeScaler/1000) + 360 28 | OverideGlobalClock(startUp.currentTime.hours, startUp.currentTime.minutes) 29 | GlobalState.currentTime = startUp 30 | local counter = 0 31 | while true do 32 | if not freezeTime then 33 | counter = counter - 1 34 | local newBaseTime = os.time(os.date("!*t"))/(Config.TimeScaler/1000) + 360 35 | baseTime = newBaseTime 36 | currentHour = math.floor(((baseTime+timeOffset)/60)%24) 37 | currentMinute = math.floor((baseTime+timeOffset)%60) 38 | if counter <= 0 then 39 | counter = 60 40 | GlobalState.currentTime = {hours = currentHour, minutes = currentMinute, seconds = currentSecond, TimeScaler = CurrentScaler} 41 | print('[^2TIME SYSTEM^0] Update time to '..currentHour..':'..currentMinute) 42 | end 43 | end 44 | Wait(4000) 45 | end 46 | end) 47 | 48 | -- COMMANDS -- 49 | RegisterCommand('time', function(source, args) 50 | if source == 0 or QBCore.Functions.HasPermission(source, Config.PermsGroup) then 51 | local src = source 52 | local newHours, newMinutes = tonumber(args[1]), tonumber(args[2]) or 0 53 | local worldid = 0 54 | if src ~= 0 then 55 | worldid = GetPlayerRoutingBucket(src) 56 | end 57 | if newHours then 58 | if worldid ~= 0 then 59 | if not WorldData[worldid] then WorldData[worldid] = {} end 60 | WorldData[worldid].hours = newHours 61 | WorldData[worldid].minutes = newMinutes 62 | TriggerClientEvent('ParadoxWorld:client:updateBucketData', -1, worldid, WorldData) 63 | TriggerClientEvent('QBCore:Notify', src, 'Time set to '..WorldData[worldid].hours..':'..WorldData[worldid].minutes..' in dimmension: '..worldid, 'success') 64 | else 65 | OverideGlobalClock(newHours, newMinutes or 0) 66 | GlobalState.currentTime = {hours = currentHour, minutes = currentMinute, seconds = currentSecond, TimeScaler = CurrentScaler} -- force instant sync 67 | if src == 0 then 68 | print('[^2TIME SYSTEM^0] Time set to '..currentHour..':'..currentMinute) 69 | else 70 | TriggerClientEvent('QBCore:Notify', src, 'Time set to '..currentHour..':'..currentMinute, 'success') 71 | end 72 | end 73 | else 74 | if worldid ~= 0 then 75 | if not WorldData[worldid] then WorldData[worldid] = {} end 76 | WorldData[worldid].hours = nil 77 | WorldData[worldid].minutes = nil 78 | TriggerClientEvent('ParadoxWorld:client:updateBucketData', -1, worldid, WorldData) 79 | TriggerClientEvent('QBCore:Notify', src, 'Time set to '..currentHour..':'..currentMinute..' in dimmension: '..worldid, 'success') 80 | else 81 | if src == 0 then 82 | print('[^2TIME SYSTEM^0] You must specify at least an hour!') 83 | else 84 | TriggerClientEvent('QBCore:Notify', src, 'You must specify at least an hour!', 'error') 85 | end 86 | end 87 | end 88 | end 89 | end, false) 90 | 91 | RegisterCommand('freezetime', function(source, args) 92 | if source == 0 or QBCore.Functions.HasPermission(source, Config.PermsGroup) then 93 | if tonumber(args[1]) or not freezeTime then 94 | if tonumber(args[1]) then 95 | OverideGlobalClock(args[1], args[2] or 0) 96 | else 97 | freezeTime = true 98 | CurrentScaler = 999999999 99 | end 100 | if source == 0 then 101 | print('[^2TIME SYSTEM^0] Frozen time to '..currentHour..':'..currentMinute) 102 | else 103 | TriggerClientEvent('QBCore:Notify', source, 'Frozen time to '..currentHour..':'..currentMinute, 'success') 104 | end 105 | GlobalState.currentTime = {hours = currentHour, minutes = currentMinute, seconds = currentSecond, TimeScaler = 999999999} -- force instant sync 106 | TriggerClientEvent('ParadoxTime:client:setOverrideData', -1, GlobalState.currentTime) 107 | else 108 | freezeTime = false 109 | CurrentScaler = Config.TimeScaler 110 | if source == 0 then 111 | print('[^2TIME SYSTEM^0] Time unfrozen') 112 | else 113 | TriggerClientEvent('QBCore:Notify', source, 'Time unfrozen', 'success') 114 | end 115 | end 116 | GlobalState.currentTime = {hours = currentHour, minutes = currentMinute, seconds = currentSecond, TimeScaler = CurrentScaler} -- force instant sync 117 | TriggerClientEvent('ParadoxTime:client:setOverrideData', -1, GlobalState.currentTime) 118 | end 119 | end, false) -------------------------------------------------------------------------------- /server/weather_server.lua: -------------------------------------------------------------------------------- 1 | local QBCore = exports['qb-core']:GetCoreObject() 2 | local NextWeather = {} 3 | local FrozenWeather = false 4 | local OverideSnow = false 5 | local CurrentSystem = Config.StartupWeather 6 | local inWeatherSequence = false 7 | local BreakSequence = false 8 | local WeatherChange = false 9 | local OverrideWeather = false 10 | local OverrideSet = false 11 | GlobalState.snowPlowed = false 12 | 13 | -- Functions -- 14 | function SendAlert(AlertType) 15 | print('[^3WEATHER SYSTEM^0] Sending NWS Alert: '..AlertType) 16 | if AlertType == 'WATCH' then 17 | TriggerEvent('qb-phone:server:sendNewMail', { 18 | sender = 'Los Santos NWS', 19 | subject = 'SEVERE THUNDERSTORM WATCH', 20 | message = 'The National Weather Service has issued a SEVERE THUNDERSTORM WATCH for the following areas: Paleto Bay, Sandy Shores, Los Santos, Grapeseed.', 21 | button = {} 22 | }, -1) 23 | elseif AlertType == 'WARNING' then 24 | TriggerEvent('qb-phone:server:sendNewMail', { 25 | sender = 'Los Santos NWS', 26 | subject = 'SEVERE THUNDERSTORM WARNING', 27 | message = 'The National Weather Service has issued a SEVERE THUNDERSTORM WARNING for the following areas: Paleto Bay, Sandy Shores, Los Santos, Grapeseed.', 28 | button = {} 29 | }, -1) 30 | elseif AlertType == 'WINTERWATCH' then 31 | TriggerEvent('qb-phone:server:sendNewMail', { 32 | sender = 'Los Santos NWS', 33 | subject = 'WINTER STORM WATCH', 34 | message = 'The National Weather Service has issued a WINTER STORM WATCH for the following areas: Paleto Bay, Sandy Shores, Los Santos, Grapeseed.', 35 | button = {} 36 | }, -1) 37 | elseif AlertType == 'WINTERWARNING' then 38 | TriggerEvent('qb-phone:server:sendNewMail', { 39 | sender = 'Los Santos NWS', 40 | subject = 'WINTER STORM WARNING', 41 | message = 'The National Weather Service has issued a WINTER STORM WARNING for the following areas: Paleto Bay, Sandy Shores, Los Santos, Grapeseed.', 42 | button = {} 43 | }, -1) 44 | end 45 | end 46 | 47 | function StartCooldown() 48 | WeatherCooldown = true 49 | SetTimeout(Config.WeatherCycleTime * 60000, function() 50 | while inWeatherSequence or OverrideWeather do -- prevent changing weather on a sequence 51 | Wait(10000) 52 | end 53 | WeatherCooldown = false 54 | end) 55 | end 56 | 57 | function StartCooldownOverride() 58 | WeatherCooldown = true 59 | print('[^3WEATHER SYSTEM^0] Dynamic weather temp-disabled for weather: '.. OverrideWeather) 60 | SetTimeout((Config.WeatherCycleTime * 1.5) * 60000, function() --slightly longer to ensure it outlasts any other threads 61 | while inWeatherSequence do -- prevent changing weather on a sequence 62 | Wait(10000) 63 | end 64 | print('[^3WEATHER SYSTEM^0] Dynamic weather re-enabled after override') 65 | OverrideWeather = false 66 | WeatherCooldown = false 67 | OverrideSet = false 68 | end) 69 | end 70 | 71 | function WeatherSequence(weathersequence, weathername) 72 | if not inWeatherSequence then 73 | CreateThread(function() 74 | local currentName = weathername 75 | inWeatherSequence = true 76 | for id,SequenceData in ipairs(weathersequence) do 77 | if CurrentSystem == currentName then 78 | SequenceData.currentWeather = SequenceData.Weather 79 | GlobalState.currentWeather = SequenceData 80 | print('[^3WEATHER SYSTEM^0] Next weather: '..SequenceData.currentWeather.. ' | System: '..CurrentSystem..' | Step: '..id..'/'..#weathersequence) 81 | if SequenceData.Time then 82 | Wait((SequenceData.Time) * 60000) 83 | end 84 | end 85 | end 86 | inWeatherSequence = false 87 | end) 88 | end 89 | end 90 | 91 | CreateThread(function() 92 | GlobalState.currentWeather = {currentWeather = Config.StartupWeather} 93 | StartCooldown() 94 | while true do 95 | Wait(5000) 96 | if FrozenWeather then return end 97 | if not WeatherCooldown and not OverrideWeather then 98 | math.randomseed(GetGameTimer()) 99 | local RandomNo = math.random(1, #Config.WeatherPool) 100 | local RandomWeather = Config.WeatherPool[RandomNo] 101 | NextWeather = Config.WeatherEvents[RandomWeather] 102 | CurrentSystem = RandomWeather 103 | GlobalState.snowPlowed = false 104 | TriggerEvent('prdx_smallmissions:server:resetPlowedNodes') 105 | if NextWeather.Alert then 106 | SendAlert(NextWeather.Alert) 107 | end 108 | 109 | if NextWeather.Sequence then 110 | WeatherSequence(NextWeather.Sequence, CurrentSystem) 111 | elseif NextWeather.Options then 112 | local LuckyWeatherOption = math.random(1, #NextWeather.Options) 113 | NextWeather.currentWeather = NextWeather.Options[LuckyWeatherOption] 114 | GlobalState.currentWeather = NextWeather 115 | print('[^3WEATHER SYSTEM^0] Next weather: '..NextWeather.currentWeather.. ' | System: '..CurrentSystem) 116 | else 117 | GlobalState.currentWeather = NextWeather 118 | print('[^3WEATHER SYSTEM^0] Next weather: '..CurrentSystem) 119 | end 120 | StartCooldown() 121 | elseif OverrideWeather and not OverrideSet then 122 | -- Break an ongoing sequence -- 123 | NextWeather = {currentWeather = OverrideWeather} 124 | CurrentSystem = OverrideWeather 125 | 126 | if Config.WeatherEvents[CurrentSystem] then 127 | NextWeather = Config.WeatherEvents[CurrentSystem] 128 | if NextWeather.Alert then 129 | SendAlert(NextWeather.Alert) 130 | end 131 | 132 | if NextWeather.Sequence then 133 | WeatherSequence(NextWeather.Sequence, CurrentSystem) 134 | elseif NextWeather.Options then 135 | local LuckyWeatherOption = math.random(1, #NextWeather.Options) 136 | NextWeather.currentWeather = NextWeather.Options[LuckyWeatherOption] 137 | GlobalState.currentWeather = NextWeather 138 | print('[^3WEATHER SYSTEM^0] Next weather: '..NextWeather.currentWeather.. ' | System: '..CurrentSystem) 139 | else 140 | GlobalState.currentWeather = NextWeather 141 | print('[^3WEATHER SYSTEM^0] Next weather: '..CurrentSystem) 142 | end 143 | end 144 | OverrideSet = true 145 | StartCooldownOverride() 146 | end 147 | end 148 | end) 149 | 150 | -- COMMANDS -- 151 | RegisterCommand('weather', function(source, args) 152 | local src = source 153 | if source == 0 or QBCore.Functions.HasPermission(src, tostring(Config.PermsGroup)) or QBCore.Shared.DevMode then 154 | if args[1] then 155 | local ChosenWeather = args[1]:upper() 156 | local worldid = 0 157 | if src ~= 0 then 158 | worldid = GetPlayerRoutingBucket(src) 159 | end 160 | BreakSequence = true 161 | if Config.WeatherEvents[ChosenWeather] then 162 | if worldid ~= 0 then 163 | TriggerClientEvent('QBCore:Notify', src, 'Sequences are not supported in dimmensions! | Dimmension: '..worldid, 'error') 164 | return 165 | end 166 | OverrideWeather = ChosenWeather 167 | else 168 | if worldid == 0 then 169 | local ValidWeather = false 170 | for k,WeatherName in pairs(Config.ValidWeathers) do 171 | if WeatherName == ChosenWeather then 172 | ValidWeather = true 173 | break 174 | end 175 | end 176 | if ValidWeather then 177 | if src == 0 then 178 | print('[^3WEATHER SYSTEM^0] Overriden weather to: '..ChosenWeather) 179 | else 180 | TriggerClientEvent('QBCore:Notify', src, 'Set weather to: '.. ChosenWeather, 'success') 181 | end 182 | OverrideWeather = ChosenWeather 183 | else 184 | if src == 0 then 185 | print('[^3WEATHER SYSTEM^0] Valid Weathers: '..Config.WeatherStr) 186 | else 187 | TriggerClientEvent('QBCore:Notify', src, 'Valid Weathers: '..Config.WeatherStr, 'error') 188 | end 189 | end 190 | else 191 | if not WorldData[worldid] then WorldData[worldid] = {} end 192 | WorldData[worldid].weather = ChosenWeather 193 | TriggerClientEvent('ParadoxWorld:client:updateBucketData', -1, worldid, WorldData) 194 | TriggerClientEvent('QBCore:Notify', src, 'Set weather to: '..ChosenWeather..' | Dimmension: '..worldid, 'success') 195 | 196 | end 197 | end 198 | else 199 | if src == 0 then 200 | print('[^3WEATHER SYSTEM^0] Valid Weathers: '..Config.WeatherStr) 201 | else 202 | TriggerClientEvent('QBCore:Notify', src, 'Current weather forcast: '.. CurrentSystem, 'success') 203 | TriggerClientEvent('QBCore:Notify', src, 'Valid Weathers: '..Config.WeatherStr, 'error') 204 | end 205 | end 206 | else 207 | TriggerClientEvent('QBCore:Notify', src, 'Current weather forcast: '.. CurrentSystem, 'success') 208 | end 209 | end, false) 210 | 211 | RegisterCommand('freezeweather', function(source) 212 | if source == 0 or QBCore.Functions.HasPermission(source, tostring(Config.PermsGroup)) or QBCore.Shared.DevMode then 213 | FrozenWeather = not FrozenWeather -- Temp freeze out the loop until we complete whatever we requested first! 214 | if source == 0 then 215 | print('[^3WEATHER SYSTEM^0] Dynamic Weather:', FrozenWeather) 216 | else 217 | TriggerClientEvent('QBCore:Notify', source, 'Dynamic Weather: '..FrozenWeather, 'error') 218 | end 219 | end 220 | end, false) 221 | 222 | RegisterCommand('skipweather', function(source) 223 | if source == 0 or QBCore.Functions.HasPermission(source, tostring(Config.PermsGroup)) or QBCore.Shared.DevMode then 224 | if inWeatherSequence then 225 | CurrentSystem = false 226 | if source == 0 then 227 | print('[^3WEATHER SYSTEM^0] Skipping weather system') 228 | else 229 | TriggerClientEvent('QBCore:Notify', source, 'Skipping weather system', 'error') 230 | end 231 | else 232 | if source == 0 then 233 | print('[^3WEATHER SYSTEM^0] Only available during a sequence') 234 | else 235 | TriggerClientEvent('QBCore:Notify', source, 'Only available during a sequence', 'error') 236 | end 237 | end 238 | end 239 | end, false) 240 | 241 | RegisterCommand('enablesnow', function(source, args) 242 | if source == 0 or QBCore.Functions.HasPermission(source, tostring(Config.PermsGroup) or QBCore.Shared.DevMode) and args then 243 | if args[1] then 244 | local worldid = 0 245 | if source ~= 0 then 246 | worldid = GetPlayerRoutingBucket(source) 247 | end 248 | if worldid == 0 then 249 | GlobalState.snowEnabled = args[1] 250 | OverideSnow = args[1] 251 | if source == 0 then 252 | print('[^3WEATHER SYSTEM^0] Snow enabled:', GlobalState.snowEnabled) 253 | else 254 | TriggerClientEvent('QBCore:Notify', source, 'Snow enabled: '..GlobalState.snowEnabled, 'success') 255 | end 256 | else 257 | if worldid ~= 0 then 258 | if not WorldData[worldid] then WorldData[worldid] = {} end 259 | WorldData[worldid].HasSnow = args[1] 260 | TriggerClientEvent('ParadoxWorld:client:updateBucketData', -1, worldid, WorldData) 261 | TriggerClientEvent('QBCore:Notify', source, 'Snow enabled: '..tostring(WorldData[worldid].HasSnow)..' in dimmension: '..worldid, 'success') 262 | end 263 | end 264 | else 265 | if source == 0 then 266 | print('[^3WEATHER SYSTEM^0] Argument needed (true or false)') 267 | else 268 | TriggerClientEvent('QBCore:Notify', source, 'Argument needed (true or false)', 'error') 269 | end 270 | end 271 | end 272 | end, false) 273 | --------------------------------------------------------------------------------