diff --git a/rain/command.lua b/rain/command.lua new file mode 100644 index 0000000..8b9af9a --- /dev/null +++ b/rain/command.lua @@ -0,0 +1,10 @@ + + +minetest.register_chatcommand("cw", { + description = "normalize weather", + privs = {rain_manager = true}, + func = function(name, param) + weather.state = 'clear' + save_weather() + end +}) \ No newline at end of file diff --git a/rain/depends.txt b/rain/depends.txt new file mode 100644 index 0000000..5be7ad4 --- /dev/null +++ b/rain/depends.txt @@ -0,0 +1 @@ +weather_core \ No newline at end of file diff --git a/rain/init.lua b/rain/init.lua new file mode 100644 index 0000000..f820f7f --- /dev/null +++ b/rain/init.lua @@ -0,0 +1,140 @@ +rain = {} + +rain.particles_count = 50 + +rain.sound_handler = function(player) + return minetest.sound_play("weather_rain", { + object = player, + max_hear_distance = 2, + loop = true, + }) +end + +-- set skybox based on time (darker if night lighter otherwise) +rain.set_sky_box = function(player) + if (minetest.get_timeofday() < 0.8) then + player:set_sky({r=65, g=80, b=100}, "plain", nil) + else + player:set_sky({r=10, g=10, b=15}, "plain", nil) + end +end + +-- creating manually parctiles instead of particles spawner because of easier to control +-- spawn position. +rain.add_rain_particles = function(player, dtime) + rain.last_rp_count = 0 + for i=rain.particles_count, 1,-1 do + local random_pos_x, random_pos_y, random_pos_z = get_random_pos_by_player_look_dir(player) + if minetest.get_node_light({x=random_pos_x, y=random_pos_y, z=random_pos_z}, 0.5) == 15 then + rain.last_rp_count = rain.last_rp_count + 1 + minetest.add_particle({ + pos = {x=random_pos_x, y=random_pos_y, z=random_pos_z}, + velocity = {x=0, y=-10, z=0}, + acceleration = {x=0, y=-30, z=0}, + expirationtime = 0.3, + size = math.random(0.5, 3), + collisiondetection = true, + vertical = true, + texture = rain.get_texture(), + playername = player:get_player_name() + }) + end + end +end + +-- Simple random texture getter +rain.get_texture = function() + local texture_name + local random_number = math.random() + if random_number > 0.33 then + texture_name = "rain_raindrop_1.png" + elseif random_number > 0.66 then + texture_name = "rain_raindrop_2.png" + else + texture_name = "rain_raindrop_3.png" + end + return texture_name; +end + +-- register player for rain weather. +-- basically needs for origin sky reference and rain sound controls. +rain.add_player = function(player) + if weather.players[player:get_player_name()] == nil then + local player_meta = {} + player_meta.origin_sky = {player:get_sky()} + rain.set_sky_box(player) + weather.players[player:get_player_name()] = player_meta + end +end + +-- remove player from player list effected by rain. +-- be sure to remove sound before removing player otherwise soundhandler reference will be lost. +rain.remove_player = function(player) + local player_meta = weather.players[player:get_player_name()] + if player_meta ~= nil and player_meta.origin_sky ~= nil then + player:set_sky(player_meta.origin_sky[1], player_meta.origin_sky[2], player_meta.origin_sky[3]) + weather.players[player:get_player_name()] = nil + end +end + +-- adds and removes rain sound depending how much rain particles around player currently exist. +-- have few seconds delay before each check to avoid on/off sound too often +-- when player stay on 'edge' where sound should play and stop depending from random raindrop appearance. +rain.update_sound = function(player) + local player_meta = weather.players[player:get_player_name()] + if player_meta ~= nil then + if player_meta.sound_updated ~= nil and player_meta.sound_updated + 5 > os.time() then + return false + end + + if player_meta.sound_handler ~= nil then + if rain.last_rp_count == 0 then + minetest.sound_stop(player_meta.sound_handler) + player_meta.sound_handler = nil + end + elseif rain.last_rp_count > 0 then + player_meta.sound_handler = rain.sound_handler(player) + end + + player_meta.sound_updated = os.time() + end +end + +-- rain sound removed from player. +rain.remove_sound = function(player) + local player_meta = weather.players[player:get_player_name()] + if player_meta ~= nil and player_meta.sound_handler ~= nil then + minetest.sound_stop(player_meta.sound_handler) + player_meta.sound_handler = nil + end +end + +-- callback function for removing rain +rain.clear = function() + for _, player in ipairs(minetest.get_connected_players()) do + rain.remove_sound(player) + rain.remove_player(player) + end +end + +minetest.register_globalstep(function(dtime) + if weather.state ~= "rain" then + return false + end + + for _, player in ipairs(minetest.get_connected_players()) do + if (is_underwater(player)) then + return false + end + rain.add_player(player) + rain.add_rain_particles(player, dtime) + rain.update_sound(player) + end +end) + +if weather.known_weathers.rain == nil then + weather.known_weathers.rain = { + chance = 15, + clear = rain.clear + } +end diff --git a/weather/sounds/weather_rain.ogg b/rain/sounds/weather_rain.ogg similarity index 100% rename from weather/sounds/weather_rain.ogg rename to rain/sounds/weather_rain.ogg diff --git a/rain/textures/rain_raindrop_1.png b/rain/textures/rain_raindrop_1.png new file mode 100644 index 0000000..798a2c9 Binary files /dev/null and b/rain/textures/rain_raindrop_1.png differ diff --git a/rain/textures/rain_raindrop_2.png b/rain/textures/rain_raindrop_2.png new file mode 100644 index 0000000..f1b2e40 Binary files /dev/null and b/rain/textures/rain_raindrop_2.png differ diff --git a/rain/textures/rain_raindrop_3.png b/rain/textures/rain_raindrop_3.png new file mode 100644 index 0000000..ef12c72 Binary files /dev/null and b/rain/textures/rain_raindrop_3.png differ diff --git a/rain/textures/rain_raindrop_debug.png b/rain/textures/rain_raindrop_debug.png new file mode 100644 index 0000000..f34e872 Binary files /dev/null and b/rain/textures/rain_raindrop_debug.png differ diff --git a/snow/depends.txt b/snow/depends.txt new file mode 100644 index 0000000..5be7ad4 --- /dev/null +++ b/snow/depends.txt @@ -0,0 +1 @@ +weather_core \ No newline at end of file diff --git a/snow/init.lua b/snow/init.lua new file mode 100644 index 0000000..d97e1a8 --- /dev/null +++ b/snow/init.lua @@ -0,0 +1,59 @@ +snow = {} + +snow.particles_count = 25 + +-- calculates coordinates and draw particles for snow weather +snow.add_rain_particles = function(player, dtime) + rain.last_rp_count = 0 + for i=snow.particles_count, 1,-1 do + local random_pos_x, random_pos_y, random_pos_z = get_random_pos_by_player_look_dir(player) + random_pos_y = math.random() + random_pos(player:getpos().y - 1, player:getpos().y + 7) + if minetest.get_node_light({x=random_pos_x, y=random_pos_y, z=random_pos_z}, 0.5) == 15 then + rain.last_rp_count = rain.last_rp_count + 1 + minetest.add_particle({ + pos = {x=random_pos_x, y=random_pos_y, z=random_pos_z}, + velocity = {x = math.random(-1,-0.5), y = math.random(-2,-1), z = math.random(-1,-0.5)}, + acceleration = {x = math.random(-1,-0.5), y=-0.5, z = math.random(-1,-0.5)}, + expirationtime = 0.6, + size = math.random(0.5, 1), + collisiondetection = true, + vertical = true, + texture = snow.get_texture(), + playername = player:get_player_name() + }) + end + end +end + +-- Simple random texture getter +snow.get_texture = function() + local texture_name + local random_number = math.random() + if random_number > 0.5 then + texture_name = "snow_snowflake1.png" + else + texture_name = "snow_snowflake2.png" + end + return texture_name; +end + +minetest.register_globalstep(function(dtime) + if weather.state ~= "snow" then + return false + end + + for _, player in ipairs(minetest.get_connected_players()) do + if (is_underwater(player)) then + return false + end + snow.add_rain_particles(player, dtime) + end +end) + +-- register snow weather +if weather.known_weathers.snow == nil then + weather.known_weathers.snow = { + chance = 10, + clear = function() end + } +end diff --git a/weather/textures/weather_snowflake1.png b/snow/textures/snow_snowflake1.png similarity index 100% rename from weather/textures/weather_snowflake1.png rename to snow/textures/snow_snowflake1.png diff --git a/weather/textures/weather_snowflake2.png b/snow/textures/snow_snowflake2.png similarity index 100% rename from weather/textures/weather_snowflake2.png rename to snow/textures/snow_snowflake2.png diff --git a/weather/command.lua b/weather/command.lua deleted file mode 100644 index 8c81aef..0000000 --- a/weather/command.lua +++ /dev/null @@ -1,15 +0,0 @@ -minetest.register_privilege("weather", { - description = "Change the weather", - give_to_singleplayer = false -}) - --- Set weather -minetest.register_chatcommand("setweather", { - params = "", - description = "Set weather to rain, snow or none", -- full description - privs = {weather = true}, - func = function(name, param) - weather.state = param - save_weather() - end -}) diff --git a/weather/init.lua b/weather/init.lua deleted file mode 100644 index 5a0ff26..0000000 --- a/weather/init.lua +++ /dev/null @@ -1,65 +0,0 @@ --- Weather: --- * rain --- * snow - -assert(minetest.add_particlespawner, "Your Minetest version is incompatible with this mod") - -weather = { - state = "none", - players = {}, -} - -weather.remove_weather = function (player_name) - local player_info = weather.players[player_name] - minetest.sound_stop(player_info.sound_handler) - local p = minetest.get_player_by_name(player_name) - if p ~= nil then - p:set_sky(player_info.sky_box[1], player_info.sky_box[2], player_info.sky_box[3]) - end -end - -save_weather = function () - for player_name, player_info in pairs(weather.players) do - if player_info ~= nil then - weather.remove_weather(player_name) - end - end - weather.players = {} - local file = io.open(minetest.get_worldpath().."/weather", "w+") - file:write(weather.state) - file:close() -end - -read_weather = function () - local file = io.open(minetest.get_worldpath().."/weather", "r") - if not file then return end - local readweather = file:read() - file:close() - return readweather -end - -weather.state = read_weather() - -minetest.register_globalstep(function(dtime) - if weather.state == "rain" or weather.state == "snow" then - if math.random(1, 10000) == 1 then - weather.state = "none" - save_weather() - end - else - if math.random(1, 50000) == 1 then - weather.state = "rain" - save_weather() - end - if math.random(1, 50000) == 2 then - weather.state = "snow" - save_weather() - end - end -end) - -dofile(minetest.get_modpath("weather").."/rain.lua") -dofile(minetest.get_modpath("weather").."/snow.lua") -dofile(minetest.get_modpath("weather").."/command.lua") - - diff --git a/weather/rain.lua b/weather/rain.lua deleted file mode 100644 index 7a1e065..0000000 --- a/weather/rain.lua +++ /dev/null @@ -1,138 +0,0 @@ --- Rain - -function getRandomRange(offset, range) - if range < 0 then - return offset + math.random() + math.random(range, 0) - elseif range > 0 then - return offset + math.random() + math.random(0, range) - else - return 0 - end -end - -rain = {} - -rain.add_short_range_particlespawner = function (player) - local ppos = player:getpos() - local short_range_pos_min = {} - short_range_pos_min.x = getRandomRange(ppos.x, -3) - short_range_pos_min.y = ppos.y + 3 - short_range_pos_min.z = getRandomRange(ppos.z, -3) - - if minetest.get_node_light(short_range_pos_min, 0.5) ~= 15 then - return false - end - - local short_range_pos_max = {} - short_range_pos_max.x = getRandomRange(ppos.x, 3) - short_range_pos_max.y = ppos.y + 3 - short_range_pos_max.z = getRandomRange(ppos.z, 3) - - if minetest.get_node_light(short_range_pos_max, 0.5) ~= 15 then - return false - end - - minetest.add_particlespawner({ - amount=15, - time=0.3, - minpos=short_range_pos_min, - maxpos=short_range_pos_max, - minvel={x=0, y=-20, z=0}, - maxvel={x=0.2, y=-20, z=0.2}, - minacc={x=0, y=-10, z=0}, - maxacc={x=0.2, y=-10, z=0.2}, - minexptime=0.2, - maxexptime=0.3, - minsize=0.5, - maxsize=2, - collisiondetection=true, - vertical=true, - texture="weather_raindrop.png", - player=player:get_player_name()}) - - return true - -end - -rain.add_long_range_particlespawner = function (player) - local ppos = player:getpos() - local long_range_pos_min = {} - long_range_pos_min.x = getRandomRange(ppos.x, -20) - long_range_pos_min.y = ppos.y + 10 - long_range_pos_min.z = getRandomRange(ppos.z, -20) - - if minetest.get_node_light(long_range_pos_min, 0.5) ~= 15 then - return false - end - - local long_range_pos_max = {} - long_range_pos_max.x = getRandomRange(ppos.x, 20) - long_range_pos_max.y = ppos.y + 10 - long_range_pos_max.z = getRandomRange(ppos.z, 20) - - if minetest.get_node_light(long_range_pos_max, 0.5) ~= 15 then - return false - end - - minetest.add_particlespawner({ - amount=40, - time=0.5, - minpos=long_range_pos_min, - maxpos=long_range_pos_max, - minvel={x=0, y=-20, z=0}, - maxvel={x=0.2, y=-20, z=0.2}, - minacc={x=0, y=-20, z=0}, - maxacc={x=0.2, y=-20, z=0.2}, - minexptime=0.2, - maxexptime=0.5, - minsize=0.5, - maxsize=2, - collisiondetection=true, - vertical=true, - texture="weather_raindrop.png", - player=player:get_player_name()}) - - return true -end - -minetest.register_on_joinplayer(function(player) - -end) - -minetest.register_globalstep(function(dtime) - if weather.state ~= "rain" then return end - for _, player in ipairs(minetest.get_connected_players()) do - local ppos = player:getpos() - 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} - - if minetest.get_node_level(player_eye_pos) == 8 then - return false - end - - local rain_nearby = rain.add_short_range_particlespawner(player) - local rain_distant = rain.add_long_range_particlespawner(player) - - if rain_nearby or rain_distant then - if weather.players[player:get_player_name()] == nil then - local player_name = player:get_player_name() - local player_info = {} - player_info.sound_handler = minetest.sound_play("weather_rain", { - object = player, - max_hear_distance = 2, - loop = true, - }) - player_info.sky_box = {player:get_sky()} - if (minetest.get_timeofday() < 0.8) then - player:set_sky({r=65, g=80, b=100}, "plain", nil) - else - player:set_sky({r=10, g=10, b=15}, "plain", nil) - end - weather.players[player_name] = player_info - end - end - end -end) - - - diff --git a/weather/snow.lua b/weather/snow.lua deleted file mode 100644 index bf36a0a..0000000 --- a/weather/snow.lua +++ /dev/null @@ -1,60 +0,0 @@ --- Snow - -minetest.register_globalstep(function(dtime) - if weather.state ~= "snow" then return end - for _, player in ipairs(minetest.get_connected_players()) do - local ppos = player:getpos() - 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} - - if minetest.get_node_level(player_eye_pos) == 8 then - return false - end - - add_long_range_particlespawner(player) - end -end) - -function add_long_range_particlespawner(player) - local ppos = player:getpos() - local long_range_pos_min = {} - long_range_pos_min.x = getRandomRange(ppos.x, -20) - long_range_pos_min.y = ppos.y + 10 - long_range_pos_min.z = getRandomRange(ppos.z, -20) - - if minetest.get_node_light(long_range_pos_min, 0.5) ~= 15 then return end - - local long_range_pos_max = {} - long_range_pos_max.x = getRandomRange(ppos.x, 20) - long_range_pos_max.y = ppos.y + 10 - long_range_pos_max.z = getRandomRange(ppos.z, 20) - - if minetest.get_node_light(long_range_pos_max, 0.5) ~= 15 then return end - - local random_texture = nil - if math.random() > 0.5 then - random_texture = "weather_snowflake1.png" - else - random_texture = "weather_snowflake2.png" - end - - minetest.add_particlespawner({ - amount=30, - time=1.5, - minpos=long_range_pos_min, - maxpos=long_range_pos_max, - minvel={x=-1, y=-2, z=-1}, - maxvel={x=1, y=-7, z=1}, - minacc={x=-1, y=-2, z=-1}, - maxacc={x=1, y=-0.3, z=1}, - minexptime=0.5, - maxexptime=1.5, - minsize=0.5, - maxsize=3, - collisiondetection=true, - vertical=false, - texture=random_texture, - player=player:get_player_name()}) -end - - diff --git a/weather/textures/weather_raindrop.png b/weather/textures/weather_raindrop.png deleted file mode 100644 index ea44a24..0000000 Binary files a/weather/textures/weather_raindrop.png and /dev/null differ diff --git a/weather_core/init.lua b/weather_core/init.lua new file mode 100644 index 0000000..be3b4c3 --- /dev/null +++ b/weather_core/init.lua @@ -0,0 +1,148 @@ +weather = { + -- weather states, 'none' is default, other states depends from active mods + state = "none", + + -- player list for saving player meta info + players = {}, + + -- time when weather should be re-calculated + next_check = 0, + + -- default weather recalculation interval + check_interval = 300, + + -- weather min duration + min_duration = 240, + + -- weather max duration + max_duration = 3600, + + -- weather calculated end time + end_time = nil, + + -- registered weathers + known_weathers = {} +} + +weather.get_rand_end_time = function() + return os.time() + math.random(weather.min_duration, weather.max_duration); +end + +-- checks if player is undewater. This is needed in order to +-- turn off weather particles generation. +function is_underwater(player) + local ppos = player:getpos() + 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} + + if minetest.get_node_level(player_eye_pos) == 8 then + return true + end + return false +end + +-- returns random number between a and b. +function random_pos(a, b) + if (a > b) then + return math.random(b, a); + end + return math.random(a, b); +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. +function get_random_pos_by_player_look_dir(player) + local look_dir = player:get_look_dir() + local player_pos = player:getpos() + + 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 + 10) + random_pos_z = math.random() + math.random(player_pos.z - 2.5, player_pos.z + 10) + else + random_pos_x = math.random() + math.random(player_pos.x - 2.5, player_pos.x + 10) + random_pos_z = math.random() + math.random(player_pos.z - 10, player_pos.z + 2.5) + end + else + if look_dir.z > 0 then + random_pos_x = math.random() + math.random(player_pos.x - 10, player_pos.x + 2.5) + random_pos_z = math.random() + math.random(player_pos.z - 2.5, player_pos.z + 10) + else + random_pos_x = math.random() + math.random(player_pos.x - 10, player_pos.x + 2.5) + random_pos_z = math.random() + math.random(player_pos.z - 10, player_pos.z + 2.5) + end + end + + random_pos_y = math.random() + random_pos(player_pos.y + 1, player_pos.y + 7) + return random_pos_x, random_pos_y, random_pos_z +end + +minetest.register_globalstep(function(dtime) + -- recalculate weather only when there aren't currently any + if (weather.state ~= "none") then + if (weather.end_time ~= nil and weather.end_time <= os.time()) then + weather.known_weathers[weather.state].clear() + weather.state = "none" + end + end + + if (weather.next_check <= os.time()) then + for reg_weather_name, reg_weather_obj in pairs(weather.known_weathers) do + if (reg_weather_obj ~= nil and reg_weather_obj.chance ~= nil) then + local random_roll = math.random(0,100) + if (random_roll <= reg_weather_obj.chance) then + weather.state = reg_weather_name + weather.end_time = weather.get_rand_end_time() + end + end + end + weather.next_check = os.time() + weather.check_interval + end +end) + +minetest.register_privilege("weather_manager", { + description = "Gives ability to control weather", + give_to_singleplayer = false +}) + +-- Weather command definition. Set +minetest.register_chatcommand("set_weather", { + params = "", + description = "Changes weather by given param, parameter none will remove weather.", + privs = {rain_manager = true}, + func = function(name, param) + if (param == "none") then + if (weather.state ~= nil and weather.known_weathers[weather.state] ~= nil) then + weather.known_weathers[weather.state].clear() + weather.state = param + end + weather.state = "none" + end + + if (weather.known_weathers ~= nil and weather.known_weathers[param] ~= nil) then + if (weather.state ~= nil and weather.state ~= "none" and weather.known_weathers[weather.state] ~= nil) then + weather.known_weathers[weather.state].clear() + end + weather.state = param + end + end +}) + +-- Overrides nodes 'sunlight_propagates' attribute for efficient indoor check (e.g. for glass roof). +-- Controlled from minetest.conf setting and by default it is disabled. +-- To enable set weather_allow_override_nodes to true. +-- Only new nodes will be effected (glass roof needs to be rebuilded). +if minetest.setting_getbool("weather_allow_override_nodes") then + if minetest.registered_nodes["default:glass"] then + minetest.override_item("default:glass", {sunlight_propagates = false}) + end + if minetest.registered_nodes["default:meselamp"] then + minetest.override_item("default:meselamp", {sunlight_propagates = false}) + end +end \ No newline at end of file