From 6fa1b1dd813a768eb0919585197999cb578540b9 Mon Sep 17 00:00:00 2001 From: cora Date: Sun, 26 Sep 2021 03:03:18 +0200 Subject: [PATCH] rewrite mcl_weather core mechanics the old weather mod used individual particles which create lag and unneccessary globalstep action for known intervals. this replaces the weather core mechanics with an event based model using particlespawners. --- mods/ENVIRONMENT/mcl_weather/init.lua | 260 +++++++++++++++- mods/ENVIRONMENT/mcl_weather/weather_core.lua | 291 ------------------ 2 files changed, 252 insertions(+), 299 deletions(-) delete mode 100644 mods/ENVIRONMENT/mcl_weather/weather_core.lua 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()