Add duration parameter to weather command
[MineClone/MineClone2.git] / mods / ENVIRONMENT / mcl_weather / weather_core.lua
blob0838798727673d15f38b455fffb86ed2f8f67342
1 -- weather states, 'none' is default, other states depends from active mods
2 mcl_weather.state = "none"
4 -- player list for saving player meta info
5 mcl_weather.players = {}
7 -- default weather recalculation interval
8 mcl_weather.check_interval = 300
10 -- weather min duration
11 mcl_weather.min_duration = 600
13 -- weather max duration
14 mcl_weather.max_duration = 9000
16 -- weather calculated end time
17 mcl_weather.end_time = nil
19 -- registered weathers
20 mcl_weather.reg_weathers = {}
22 -- automaticly calculates intervals and swap weathers
23 mcl_weather.auto_mode = true
25 -- global flag to disable/enable ABM logic.
26 mcl_weather.allow_abm = true
28 mcl_weather.reg_weathers["none"] = {
29 min_duration = mcl_weather.min_duration,
30 max_duration = mcl_weather.max_duration,
31 light_factor = nil,
32 transitions = {
33 [50] = "rain",
34 [100] = "snow",
36 clear = function() end,
39 local storage = minetest.get_mod_storage()
40 -- Save weather into mod storage, so it can be loaded after restarting the server
41 local save_weather = function()
42 storage:set_string("mcl_weather_state", mcl_weather.state)
43 storage:set_int("mcl_weather_end_time", mcl_weather.end_time)
44 minetest.log("verbose", "[mcl_weather] Weather data saved: state="..mcl_weather.state.." end_time="..mcl_weather.end_time)
45 end
46 minetest.register_on_shutdown(save_weather)
48 mcl_weather.get_rand_end_time = function(min_duration, max_duration)
49 local r
50 if min_duration ~= nil and max_duration ~= nil then
51 r = math.random(min_duration, max_duration);
52 else
53 r = math.random(mcl_weather.min_duration, mcl_weather.max_duration);
54 end
55 return minetest.get_gametime() + r
56 end
58 mcl_weather.get_current_light_factor = function()
59 if mcl_weather.state == "none" then
60 return nil
61 else
62 return mcl_weather.reg_weathers[mcl_weather.state].light_factor
63 end
64 end
66 -- Returns true if pos is outdoor.
67 -- Outdoor is defined as any node in the Overworld under open sky.
68 -- FIXME: Nodes below glass also count as “outdoor”, this should not be the case.
69 mcl_weather.is_outdoor = function(pos)
70 local cpos = {x=pos.x, y=pos.y+1, z=pos.z}
71 local dim = mcl_worlds.pos_to_dimension(cpos)
72 if minetest.get_node_light(cpos, 0.5) == 15 and dim == "overworld" then
73 return true
74 end
75 return false
76 end
78 -- checks if player is undewater. This is needed in order to
79 -- turn off weather particles generation.
80 mcl_weather.is_underwater = function(player)
81 local ppos = player:getpos()
82 local offset = player:get_eye_offset()
83 local player_eye_pos = {x = ppos.x + offset.x,
84 y = ppos.y + offset.y + 1.5,
85 z = ppos.z + offset.z}
86 local node_level = minetest.get_node_level(player_eye_pos)
87 if node_level == 8 or node_level == 7 then
88 return true
89 end
90 return false
91 end
93 -- trying to locate position for particles by player look direction for performance reason.
94 -- it is costly to generate many particles around player so goal is focus mainly on front view.
95 mcl_weather.get_random_pos_by_player_look_dir = function(player)
96 local look_dir = player:get_look_dir()
97 local player_pos = player:getpos()
99 local random_pos_x = 0
100 local random_pos_y = 0
101 local random_pos_z = 0
103 if look_dir.x > 0 then
104 if look_dir.z > 0 then
105 random_pos_x = math.random() + math.random(player_pos.x - 2.5, player_pos.x + 5)
106 random_pos_z = math.random() + math.random(player_pos.z - 2.5, player_pos.z + 5)
107 else
108 random_pos_x = math.random() + math.random(player_pos.x - 2.5, player_pos.x + 5)
109 random_pos_z = math.random() + math.random(player_pos.z - 5, player_pos.z + 2.5)
111 else
112 if look_dir.z > 0 then
113 random_pos_x = math.random() + math.random(player_pos.x - 5, player_pos.x + 2.5)
114 random_pos_z = math.random() + math.random(player_pos.z - 2.5, player_pos.z + 5)
115 else
116 random_pos_x = math.random() + math.random(player_pos.x - 5, player_pos.x + 2.5)
117 random_pos_z = math.random() + math.random(player_pos.z - 5, player_pos.z + 2.5)
121 random_pos_y = math.random() + math.random(player_pos.y + 1, player_pos.y + 3)
122 return random_pos_x, random_pos_y, random_pos_z
125 minetest.register_globalstep(function(dtime)
126 if mcl_weather.auto_mode == false then
127 return 0
130 if mcl_weather.end_time == nil then
131 mcl_weather.end_time = mcl_weather.get_rand_end_time()
133 -- recalculate weather
134 if mcl_weather.end_time <= minetest.get_gametime() then
135 mcl_weather.set_random_weather(mcl_weather.state, mcl_weather.reg_weathers[mcl_weather.state])
137 end)
139 -- Sets random weather (which could be 'none' (no weather)).
140 mcl_weather.set_random_weather = function(weather_name, weather_meta)
141 if (weather_meta ~= nil) then
142 local transitions = weather_meta.transitions
143 local random_roll = math.random(0,100)
144 local new_weather
145 for v, weather in pairs(transitions) do
146 if random_roll < v then
147 new_weather = weather
148 break
151 if new_weather then
152 mcl_weather.change_weather(new_weather)
157 -- Change weather to new_weather.
158 -- * explicit_end_time is OPTIONAL. If specified, explicitly set the
159 -- gametime (minetest.get_gametime) in which the weather ends.
160 mcl_weather.change_weather = function(new_weather, explicit_end_time)
161 if (mcl_weather.reg_weathers ~= nil and mcl_weather.reg_weathers[new_weather] ~= nil) then
162 if (mcl_weather.state ~= nil and mcl_weather.reg_weathers[mcl_weather.state] ~= nil) then
163 mcl_weather.reg_weathers[mcl_weather.state].clear()
165 mcl_weather.state = new_weather
166 local weather_meta = mcl_weather.reg_weathers[mcl_weather.state]
167 if explicit_end_time then
168 mcl_weather.end_time = explicit_end_time
169 else
170 mcl_weather.end_time = mcl_weather.get_rand_end_time(weather_meta.min_duration, weather_meta.max_duration)
172 mcl_weather.skycolor.update_sky_color()
173 save_weather()
174 return true
176 return false
179 mcl_weather.get_weather = function()
180 return mcl_weather.state
183 minetest.register_privilege("weather_manager", {
184 description = "Gives ability to control weather",
185 give_to_singleplayer = false
188 -- Weather command definition. Set
189 minetest.register_chatcommand("weather", {
190 params = "(clear | rain | snow | thunder) [<duration>]",
191 description = "Changes the weather to the specified parameter.",
192 privs = {weather_manager = true},
193 func = function(name, param)
194 if (param == "") then
195 return false, "Error: No weather specified."
197 local new_weather, end_time
198 local parse1, parse2 = string.match(param, "(%w+) ?(%d*)")
199 if parse1 then
200 if parse1 == "clear" then
201 new_weather = "none"
202 else
203 new_weather = parse1
205 else
206 return false, "Error: Invalid parameters."
208 if parse2 then
209 if type(tonumber(parse2)) == "number" then
210 local duration = tonumber(parse2)
211 if duration < 1 then
212 return false, "Error: Duration can't be less than 1 second."
214 end_time = minetest.get_gametime() + duration
218 local success = mcl_weather.change_weather(new_weather, end_time)
219 if success then
220 return true
221 else
222 return false, "Error: Invalid weather specified. Use “clear”, “rain”, “snow” or “thunder”."
227 minetest.register_chatcommand("toggledownfall", {
228 params = "",
229 description = "Toggles between clear weather and weather with downfall (randomly rain, thunderstorm or snow)",
230 privs = {weather_manager = true},
231 func = function(name, param)
232 -- Currently rain/thunder/snow: Set weather to clear
233 if mcl_weather.state ~= "none" then
234 return mcl_weather.change_weather("none")
236 -- Currently clear: Set weather randomly to rain/thunder/snow
237 else
238 local new = { "rain", "thunder", "snow" }
239 local r = math.random(1, #new)
240 return mcl_weather.change_weather(new[r])
245 -- Configuration setting which allows user to disable ABM for weathers (if they use it).
246 -- Weather mods expected to be use this flag before registering ABM.
247 local weather_allow_abm = minetest.settings:get_bool("weather_allow_abm")
248 if weather_allow_abm ~= nil and weather_allow_abm == false then
249 mcl_weather.allow_abm = false
250 end
253 local load_weather = function()
254 local weather = storage:get_string("mcl_weather_state")
255 if weather and weather ~= "" then
256 mcl_weather.state = weather
257 mcl_weather.end_time = storage:get_int("mcl_weather_end_time")
258 mcl_weather.change_weather(weather, mcl_weather.end_time)
259 if type(mcl_weather.end_time) ~= "number" then
260 -- Fallback in case of corrupted end time
261 mcl_weather.end_time = mcl_weather.min_duration
263 minetest.log("action", "[mcl_weather] Weather restored.")
264 else
265 minetest.log("action", "[mcl_weather] No weather data found. Starting with clear weather.")
269 load_weather()