diff --git a/mods/ENVIRONMENT/mcl_weather/init.lua b/mods/ENVIRONMENT/mcl_weather/init.lua index e4ebfb2d..448722a9 100644 --- a/mods/ENVIRONMENT/mcl_weather/init.lua +++ b/mods/ENVIRONMENT/mcl_weather/init.lua @@ -1,17 +1,261 @@ -local modpath = minetest.get_modpath("mcl_weather") - mcl_weather = {} --- If not located then embeded skycolor mod version will be loaded. -if minetest.get_modpath("skycolor") == nil then - dofile(modpath.."/skycolor.lua") +mcl_weather.allow_abm = true +mcl_weather.current = "none" +mcl_weather.state = "none" +mcl_weather.current_endtime = 0 +mcl_weather.next = nil +mcl_weather.registered_weathers = {} + +mcl_weather.min_duration = 600 +mcl_weather.max_duration = 9000 + + +local players = {} +players.particlespawners={} +players.soundhandler={} +players.weatheractive = {} + +local interval=1 + +function mcl_weather.register_weather(name,def) + mcl_weather.registered_weathers[name] = def end -dofile(modpath.."/weather_core.lua") +dofile(modpath.."/skycolor.lua") dofile(modpath.."/snow.lua") dofile(modpath.."/rain.lua") dofile(modpath.."/nether_dust.lua") +dofile(modpath.."/thunder.lua") -if minetest.get_modpath("lightning") ~= nil then - dofile(modpath.."/thunder.lua") +function mcl_weather.get_weatherdef(name) + if mcl_weather.registered_weathers[name] then + return mcl_weather.registered_weathers[name] + end + return mcl_weather.registered_weathers["none"] end + +-- Returns true if pos is outdoor. +-- Outdoor is defined as any node in the Overworld under open sky. +-- FIXME: Nodes below glass also count as “outdoor”, this should not be the case. +function mcl_weather.is_outdoor(pos) + local cpos = {x=pos.x, y=pos.y+1, z=pos.z} + local dim = mcl_worlds.pos_to_dimension(cpos) + if minetest.get_node_light(cpos, 0.5) == 15 and dim == "overworld" then + return true + end + return false +end + +-- checks if player is undewater. This is needed in order to +-- turn off weather particles generation. +function mcl_weather.is_underwater(ppos) + local offset = player:get_eye_offset() + local player_eye_pos = {x = ppos.x + offset.x, + y = ppos.y + offset.y + 1.5, + z = ppos.z + offset.z} + local node_level = minetest.get_node_level(player_eye_pos) + if node_level == 8 or node_level == 7 then + return true + end + return false +end + +function mcl_weather.pos_has_weather(pos) + if not mcl_weather.is_outdoor(pos) or mcl_weather.is_underwater(pos) then + return false + end + return true +end + +function mcl_weather.add_sound(name,sound) + players.soundhandler[name] = minetest.sound_play(sound, { + to_player = name, + loop = true, + }) +end +function mcl_weather.remove_sound(name) + if players.soundhandler[name] then + minetest.sound_fade(players.soundhandler[name], -0.5, 0.0) + end + +end + +function mcl_weather.add_particlespawners_player(name,spawners) + if not spawners then return end + local player = minetest.get_player_by_name(name) + if not player then return end + if players.particlespawners[name] == nil then + players.particlespawners[name]={} + end + + for k,v in ipairs(spawners) do + v.playername=name + v.attached=player + table.insert(players.particlespawners[name],minetest.add_particlespawner(v)) + end +end + +function mcl_weather.delete_particlespawners_player(player) + if players.particlespawners[player] == nil then + players.particlespawners[player]={} + end + for k,v in ipairs(players.particlespawners[player]) do + minetest.delete_particlespawner(v) + end + players.particlespawners[player]=nil +end + +local function doplayers(func,players) + players = players or minetest.get_connected_players() + for k,v in ipairs(players) do + func(v:get_player_name(),v) + end +end + +function mcl_weather.get_next_weather() + local current=mcl_weather.get_weatherdef(mcl_weather.current) + local random_roll = math.random(0,100) + local new_weather + if current and current.transitions then + for v, weather in pairs(current.transitions) do + if random_roll < v then + new_weather=weather + break + end + end + end + if not new_weather then new_weather = "none" end + return new_weather +end + +function mcl_weather.start_weather_player(name,def) + local player=minetest.get_player_by_name(name) + if not mcl_worlds.has_weather(player:get_pos()) then + players.weatheractive[name] = false + return + end + players.weatheractive[name] = true + if def.start_player then def.start_player(name) end + mcl_weather.add_particlespawners_player(name,def.particlespawners) + if def.skycolor then + local player=minetest.get_player_by_name(name) + player:set_clouds({color=def.skycolor}) + end + if def.sound then + mcl_weather.add_sound(name,def.sound) + end +end + +function mcl_weather.stop_weather_player(name,def) + if def.clear_player then def.clear_player(name) end + mcl_weather.delete_particlespawners_player(name) + if def.sound then + mcl_weather.remove_sound(name,def.sound) + end + if def.skycolor then + local player=minetest.get_player_by_name(name) + player:set_clouds({color="#FFF0F0E5"}) + end + players.weatheractive[name] = false +end + +function mcl_weather.start_weather(def) + if def.start then def.start() end + + if def.skylayer then + mcl_weather.skycolor.add_layer("weather",def.skylayer) + mcl_weather.skycolor.update_sky_color() + end + doplayers(function(name) + mcl_weather.start_weather_player(name,def) + end) +end + +function mcl_weather.stop_weather(def) + if def.clear then def.clear() end + doplayers(function(name) + mcl_weather.stop_weather_player(name,def) + end) + if def.clear_player then doplayers(def.clear_player) end + if def.skylayer then + mcl_weather.skycolor.remove_layer("weather") + mcl_weather.skycolor.update_sky_color() + end +end + +function mcl_weather.change(new_weather,force) + if new_weather == mcl_weather.current then return end + if not force and os.time() < mcl_weather.current_endtime then return end + local def=mcl_weather.get_weatherdef(new_weather) + local old=mcl_weather.get_weatherdef(mcl_weather.current) + if not def then + minetest.chat_send_all("weather def fail.") + return end + mcl_weather.stop_weather(old) + mcl_weather.start_weather(def) + mcl_weather.current = new_weather + mcl_weather.state = new_weather + local duration = math.random(def.min_duration,def.max_duration) + mcl_weather.current_endtime = os.time() + duration + minetest.after(duration,function() + mcl_weather.change(mcl_weather.get_next_weather()) + end) + minetest.chat_send_all("weather changed to "..mcl_weather.current) +end + +function mcl_weather.tick() + doplayers(function(name,player) + local pos=player:get_pos() + if players.weatheractive[name] and not mcl_weather.pos_has_weather(pos) then + mcl_weather.stop_weather_player(name,mcl_weather.get_weatherdef(mcl_weather.current)) + elseif not players.weatheractive[name] and mcl_weather.pos_has_weather(pos) then + mcl_weather.start_weather_player(name,mcl_weather.get_weatherdef(mcl_weather.current)) + end + end) + minetest.after(interval,mcl_weather.tick) +end + +mcl_worlds.register_on_dimension_change(function(player, dimension) + if mcl_worlds.has_weather(player:get_pos()) then + mcl_weather.start_weather_player(placer:get_player_name(),mcl_weather.get_weatherdef(mcl_weather.current)) + else + mcl_weather.stop_weather_player(placer:get_player_name(),mcl_weather.get_weatherdef(mcl_weather.current)) + end +end) + +minetest.register_on_joinplayer(function(player) + mcl_weather.start_weather_player(player:get_player_name(),mcl_weather.get_weatherdef(mcl_weather.current)) +end) + +minetest.register_on_leaveplayer(function(player) + cla_weather.stop_weather_player(player:get_player_name()) +end) + + +minetest.register_chatcommand("cw",{ + func=function(name,param) + mcl_weather.change(param,true) + end +}) +minetest.register_chatcommand("sc",{ + func=function(name,param) + mcl_weather.skycolor.add_layer("weather",{{r=0, g=0, b=0}, + {r=85, g=86, b=98}, + {r=135, g=135, b=151}, + {r=85, g=86, b=98}, + {r=0, g=0, b=0}}) + --def.skylayer) + mcl_weather.skycolor.active = true + mcl_weather.skycolor.update_sky_color() + end +}) + +mcl_weather.register_weather("none",{ + min_duration = mcl_weather.min_duration, + max_duration = mcl_weather.max_duration, + transitions = { + [50] = "rain", + [100] = "snow" + } +}) \ No newline at end of file diff --git a/mods/ENVIRONMENT/mcl_weather/weather_core.lua b/mods/ENVIRONMENT/mcl_weather/weather_core.lua deleted file mode 100644 index 365f6e54..00000000 --- a/mods/ENVIRONMENT/mcl_weather/weather_core.lua +++ /dev/null @@ -1,291 +0,0 @@ -local S = minetest.get_translator("mcl_weather") - --- weather states, 'none' is default, other states depends from active mods -mcl_weather.state = "none" - --- player list for saving player meta info -mcl_weather.players = {} - --- default weather check interval for global step -mcl_weather.check_interval = 5 - --- weather min duration -mcl_weather.min_duration = 600 - --- weather max duration -mcl_weather.max_duration = 9000 - --- weather calculated end time -mcl_weather.end_time = nil - --- registered weathers -mcl_weather.reg_weathers = {} - --- global flag to disable/enable ABM logic. -mcl_weather.allow_abm = true - -mcl_weather.reg_weathers["none"] = { - min_duration = mcl_weather.min_duration, - max_duration = mcl_weather.max_duration, - light_factor = nil, - transitions = { - [50] = "rain", - [100] = "snow", - }, - clear = function() end, -} - -local storage = minetest.get_mod_storage() --- Save weather into mod storage, so it can be loaded after restarting the server -local save_weather = function() - storage:set_string("mcl_weather_state", mcl_weather.state) - storage:set_int("mcl_weather_end_time", mcl_weather.end_time) - minetest.log("verbose", "[mcl_weather] Weather data saved: state="..mcl_weather.state.." end_time="..mcl_weather.end_time) -end -minetest.register_on_shutdown(save_weather) - -mcl_weather.get_rand_end_time = function(min_duration, max_duration) - local r - if min_duration ~= nil and max_duration ~= nil then - r = math.random(min_duration, max_duration) - else - r = math.random(mcl_weather.min_duration, mcl_weather.max_duration) - end - return minetest.get_gametime() + r -end - -mcl_weather.get_current_light_factor = function() - if mcl_weather.state == "none" then - return nil - else - return mcl_weather.reg_weathers[mcl_weather.state].light_factor - end -end - --- Returns true if pos is outdoor. --- Outdoor is defined as any node in the Overworld under open sky. --- FIXME: Nodes below glass also count as “outdoor”, this should not be the case. -mcl_weather.is_outdoor = function(pos) - local cpos = {x=pos.x, y=pos.y+1, z=pos.z} - local dim = mcl_worlds.pos_to_dimension(cpos) - if minetest.get_node_light(cpos, 0.5) == 15 and dim == "overworld" then - return true - end - return false -end - --- checks if player is undewater. This is needed in order to --- turn off weather particles generation. -mcl_weather.is_underwater = function(player) - local ppos = player:get_pos() - local offset = player:get_eye_offset() - local player_eye_pos = {x = ppos.x + offset.x, - y = ppos.y + offset.y + 1.5, - z = ppos.z + offset.z} - local node_level = minetest.get_node_level(player_eye_pos) - if node_level == 8 or node_level == 7 then - return true - end - return false -end - --- trying to locate position for particles by player look direction for performance reason. --- it is costly to generate many particles around player so goal is focus mainly on front view. -mcl_weather.get_random_pos_by_player_look_dir = function(player) - local look_dir = player:get_look_dir() - local player_pos = player:get_pos() - - local random_pos_x = 0 - local random_pos_y = 0 - local random_pos_z = 0 - - if look_dir.x > 0 then - if look_dir.z > 0 then - random_pos_x = math.random() + math.random(player_pos.x - 2.5, player_pos.x + 5) - random_pos_z = math.random() + math.random(player_pos.z - 2.5, player_pos.z + 5) - else - random_pos_x = math.random() + math.random(player_pos.x - 2.5, player_pos.x + 5) - random_pos_z = math.random() + math.random(player_pos.z - 5, player_pos.z + 2.5) - end - else - if look_dir.z > 0 then - random_pos_x = math.random() + math.random(player_pos.x - 5, player_pos.x + 2.5) - random_pos_z = math.random() + math.random(player_pos.z - 2.5, player_pos.z + 5) - else - random_pos_x = math.random() + math.random(player_pos.x - 5, player_pos.x + 2.5) - random_pos_z = math.random() + math.random(player_pos.z - 5, player_pos.z + 2.5) - end - end - - random_pos_y = math.random() + math.random(player_pos.y + 10, player_pos.y + 15) - return random_pos_x, random_pos_y, random_pos_z -end - -local t, wci = 0, mcl_weather.check_interval -minetest.register_globalstep(function(dtime) - t = t + dtime - if t < wci then return end - t = 0 - - if mcl_weather.end_time == nil then - mcl_weather.end_time = mcl_weather.get_rand_end_time() - end - -- recalculate weather - if mcl_weather.end_time <= minetest.get_gametime() then - local changeWeather = minetest.settings:get_bool("mcl_doWeatherCycle") - if changeWeather == nil then - changeWeather = true - end - if changeWeather then - mcl_weather.set_random_weather(mcl_weather.state, mcl_weather.reg_weathers[mcl_weather.state]) - else - mcl_weather.end_time = mcl_weather.get_rand_end_time() - end - end -end) - --- Sets random weather (which could be 'none' (no weather)). -mcl_weather.set_random_weather = function(weather_name, weather_meta) - if weather_meta == nil then return end - local transitions = weather_meta.transitions - local random_roll = math.random(0,100) - local new_weather - for v, weather in pairs(transitions) do - if random_roll < v then - new_weather = weather - break - end - end - if new_weather then - mcl_weather.change_weather(new_weather) - end -end - --- Change weather to new_weather. --- * explicit_end_time is OPTIONAL. If specified, explicitly set the --- gametime (minetest.get_gametime) in which the weather ends. --- * changer is OPTIONAL, for logging purposes. -mcl_weather.change_weather = function(new_weather, explicit_end_time, changer_name) - local changer_name = changer_name or debug.getinfo(2).name.."()" - - if (mcl_weather.reg_weathers ~= nil and mcl_weather.reg_weathers[new_weather] ~= nil) then - if (mcl_weather.state ~= nil and mcl_weather.reg_weathers[mcl_weather.state] ~= nil) then - mcl_weather.reg_weathers[mcl_weather.state].clear() - end - - local old_weather = mcl_weather.state - - mcl_weather.state = new_weather - - if old_weather == "none" then - old_weather = "clear" - end - if new_weather == "none" then - new_weather = "clear" - end - minetest.log("action", "[mcl_weather] " .. changer_name .. " changed the weather from " .. old_weather .. " to " .. new_weather) - - local weather_meta = mcl_weather.reg_weathers[mcl_weather.state] - if explicit_end_time then - mcl_weather.end_time = explicit_end_time - else - mcl_weather.end_time = mcl_weather.get_rand_end_time(weather_meta.min_duration, weather_meta.max_duration) - end - mcl_weather.skycolor.update_sky_color() - save_weather() - return true - end - return false -end - -mcl_weather.get_weather = function() - return mcl_weather.state -end - -minetest.register_privilege("weather_manager", { - description = S("Gives ability to control weather"), - give_to_singleplayer = false -}) - --- Weather command definition. Set -minetest.register_chatcommand("weather", { - params = "(clear | rain | snow | thunder) []", - description = S("Changes the weather to the specified parameter."), - privs = {weather_manager = true}, - func = function(name, param) - if (param == "") then - return false, S("Error: No weather specified.") - end - local new_weather, end_time - local parse1, parse2 = string.match(param, "(%w+) ?(%d*)") - if parse1 then - if parse1 == "clear" then - new_weather = "none" - else - new_weather = parse1 - end - else - return false, S("Error: Invalid parameters.") - end - if parse2 then - if type(tonumber(parse2)) == "number" then - local duration = tonumber(parse2) - if duration < 1 then - return false, S("Error: Duration can't be less than 1 second.") - end - end_time = minetest.get_gametime() + duration - end - end - - local success = mcl_weather.change_weather(new_weather, end_time, name) - if success then - return true - else - return false, S("Error: Invalid weather specified. Use “clear”, “rain”, “snow” or “thunder”.") - end - end -}) - -minetest.register_chatcommand("toggledownfall", { - params = "", - description = S("Toggles between clear weather and weather with downfall (randomly rain, thunderstorm or snow)"), - privs = {weather_manager = true}, - func = function(name, param) - -- Currently rain/thunder/snow: Set weather to clear - if mcl_weather.state ~= "none" then - return mcl_weather.change_weather("none", nil, name) - - -- Currently clear: Set weather randomly to rain/thunder/snow - else - local new = { "rain", "thunder", "snow" } - local r = math.random(1, #new) - return mcl_weather.change_weather(new[r], nil, name) - end - end -}) - --- Configuration setting which allows user to disable ABM for weathers (if they use it). --- Weather mods expected to be use this flag before registering ABM. -local weather_allow_abm = minetest.settings:get_bool("weather_allow_abm") -if weather_allow_abm ~= nil and weather_allow_abm == false then - mcl_weather.allow_abm = false -end - - -local load_weather = function() - local weather = storage:get_string("mcl_weather_state") - if weather and weather ~= "" then - mcl_weather.state = weather - mcl_weather.end_time = storage:get_int("mcl_weather_end_time") - mcl_weather.change_weather(weather, mcl_weather.end_time) - if type(mcl_weather.end_time) ~= "number" then - -- Fallback in case of corrupted end time - mcl_weather.end_time = mcl_weather.min_duration - end - minetest.log("action", "[mcl_weather] Weather restored.") - else - minetest.log("action", "[mcl_weather] No weather data found. Starting with clear weather.") - end -end - -load_weather()