diff --git a/mods/MISC/lightning/README.md b/mods/MISC/lightning/README.md new file mode 100644 index 000000000..73782edba --- /dev/null +++ b/mods/MISC/lightning/README.md @@ -0,0 +1,21 @@ + +Lightning mod for minetest + + +Copyright (C) 2016 - Auke Kok + +"lightning" is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation; either version 2.1 +of the license, or (at your option) any later version. + + +Textures: CC-BY-SA-4.0 by sofar + lightning_1.png + lightning_2.png + lightning_3.png + +Sounds: + thunder.1.ogg - CC-BY-SA - hantorio - http://www.freesound.org/people/hantorio/sounds/121945/ + thunder.2.ogg - CC-BY-SA - juskiddink - http://www.freesound.org/people/juskiddink/sounds/101948/ + thunder.3.ogg - CC-BY-SA - IllusiaProductions - http://www.freesound.org/people/IllusiaProductions/sounds/249950/ diff --git a/mods/MISC/lightning/depends.txt b/mods/MISC/lightning/depends.txt new file mode 100644 index 000000000..47738767f --- /dev/null +++ b/mods/MISC/lightning/depends.txt @@ -0,0 +1 @@ +mcl_fire diff --git a/mods/MISC/lightning/description.txt b/mods/MISC/lightning/description.txt new file mode 100644 index 000000000..98fdb3626 --- /dev/null +++ b/mods/MISC/lightning/description.txt @@ -0,0 +1 @@ +A mod that adds thunder and lightning effects. diff --git a/mods/MISC/lightning/init.lua b/mods/MISC/lightning/init.lua new file mode 100644 index 000000000..99c8a7ee1 --- /dev/null +++ b/mods/MISC/lightning/init.lua @@ -0,0 +1,166 @@ + +--[[ + +Copyright (C) 2016 - Auke Kok + +"lightning" is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation; either version 2.1 +of the license, or (at your option) any later version. + +--]] + +lightning = {} + +lightning.interval_low = 17 +lightning.interval_high = 503 +lightning.range_h = 100 +lightning.range_v = 50 +lightning.size = 100 +-- disable this to stop lightning mod from striking +lightning.auto = true + +local rng = PcgRandom(32321123312123) + +local ps = {} +local ttl = 1 + +local revertsky = function() + if ttl == 0 then + return + end + ttl = ttl - 1 + if ttl > 0 then + return + end + + for key, entry in pairs(ps) do + local sky = entry.sky + entry.p:set_sky(sky.bgcolor, sky.type, sky.textures) + end + + ps = {} +end + +minetest.register_globalstep(revertsky) + +-- select a random strike point, midpoint +local function choose_pos(pos) + if not pos then + local playerlist = minetest.get_connected_players() + local playercount = table.getn(playerlist) + + -- nobody on + if playercount == 0 then + return nil, nil + end + + local r = rng:next(1, playercount) + local randomplayer = playerlist[r] + pos = randomplayer:getpos() + + -- avoid striking underground + if pos.y < -20 then + return nil, nil + end + + pos.x = math.floor(pos.x - (lightning.range_h / 2) + rng:next(1, lightning.range_h)) + pos.y = pos.y + (lightning.range_v / 2) + pos.z = math.floor(pos.z - (lightning.range_h / 2) + rng:next(1, lightning.range_h)) + end + + local b, pos2 = minetest.line_of_sight(pos, {x = pos.x, y = pos.y - lightning.range_v, z = pos.z}, 1) + + -- nothing but air found + if b then + return nil, nil + end + + local n = minetest.get_node({x = pos2.x, y = pos2.y - 1/2, z = pos2.z}) + if n.name == "air" or n.name == "ignore" then + return nil, nil + end + + return pos, pos2 +end + +-- lightning strike API +-- * pos: optional, if not given a random pos will be chosen +-- * returns: bool - success if a strike happened +lightning.strike = function(pos) + if lightning.auto then + minetest.after(rng:next(lightning.interval_low, lightning.interval_high), lightning.strike) + end + + local pos2 + pos, pos2 = choose_pos(pos) + + if not pos then + return false + end + + minetest.add_particlespawner({ + amount = 1, + time = 0.2, + -- make it hit the top of a block exactly with the bottom + minpos = {x = pos2.x, y = pos2.y + (lightning.size / 2) + 1/2, z = pos2.z }, + maxpos = {x = pos2.x, y = pos2.y + (lightning.size / 2) + 1/2, z = pos2.z }, + minvel = {x = 0, y = 0, z = 0}, + maxvel = {x = 0, y = 0, z = 0}, + minacc = {x = 0, y = 0, z = 0}, + maxacc = {x = 0, y = 0, z = 0}, + minexptime = 0.2, + maxexptime = 0.2, + minsize = lightning.size * 10, + maxsize = lightning.size * 10, + collisiondetection = true, + vertical = true, + -- to make it appear hitting the node that will get set on fire, make sure + -- to make the texture lightning bolt hit exactly in the middle of the + -- texture (e.g. 127/128 on a 256x wide texture) + texture = "lightning_lightning_" .. rng:next(1,3) .. ".png", + }) + + minetest.sound_play({ pos = pos, name = "lightning_thunder", gain = 10, max_hear_distance = 500 }) + + -- damage nearby objects, player or not + for _, obj in ipairs(minetest.get_objects_inside_radius(pos, 5)) do + -- nil as param#1 is supposed to work, but core can't handle it. + obj:punch(obj, 1.0, {full_punch_interval = 1.0, damage_groups = {fleshy=8}}, nil) + end + + local playerlist = minetest.get_connected_players() + for i = 1, #playerlist do + local player = playerlist[i] + local sky = {} + + sky.bgcolor, sky.type, sky.textures = player:get_sky() + + local name = player:get_player_name() + if ps[name] == nil then + ps[name] = {p = player, sky = sky} + player:set_sky(0xffffff, "plain", {}) + end + end + + -- trigger revert of skybox + ttl = 5 + + -- set the air node above it on fire + pos2.y = pos2.y + 1/2 + if minetest.get_item_group(minetest.get_node({x = pos2.x, y = pos2.y - 1, z = pos2.z}).name, "liquid") < 1 then + if minetest.get_node(pos2).name == "air" then + -- cause a fire + minetest.set_node(pos2, {name = "mcl_fire:fire"}) + end + end + +end + +-- if other mods disable auto lightning during initialization, don't trigger the first lightning. +minetest.after(5, function(dtime) + if lightning.auto then + minetest.after(rng:next(lightning.interval_low, + lightning.interval_high), lightning.strike) + end +end) diff --git a/mods/MISC/lightning/mod.conf b/mods/MISC/lightning/mod.conf new file mode 100644 index 000000000..948a40751 --- /dev/null +++ b/mods/MISC/lightning/mod.conf @@ -0,0 +1 @@ +name = lightning diff --git a/mods/MISC/lightning/screenshot.png b/mods/MISC/lightning/screenshot.png new file mode 100644 index 000000000..8d3a047ca Binary files /dev/null and b/mods/MISC/lightning/screenshot.png differ diff --git a/mods/MISC/lightning/sounds/lightning_thunder.1.ogg b/mods/MISC/lightning/sounds/lightning_thunder.1.ogg new file mode 100644 index 000000000..18e011957 Binary files /dev/null and b/mods/MISC/lightning/sounds/lightning_thunder.1.ogg differ diff --git a/mods/MISC/lightning/sounds/lightning_thunder.2.ogg b/mods/MISC/lightning/sounds/lightning_thunder.2.ogg new file mode 100644 index 000000000..c9e1b0d4f Binary files /dev/null and b/mods/MISC/lightning/sounds/lightning_thunder.2.ogg differ diff --git a/mods/MISC/lightning/sounds/lightning_thunder.3.ogg b/mods/MISC/lightning/sounds/lightning_thunder.3.ogg new file mode 100644 index 000000000..3690d200b Binary files /dev/null and b/mods/MISC/lightning/sounds/lightning_thunder.3.ogg differ diff --git a/mods/MISC/lightning/textures/lightning_lightning_1.png b/mods/MISC/lightning/textures/lightning_lightning_1.png new file mode 100644 index 000000000..37af59e04 Binary files /dev/null and b/mods/MISC/lightning/textures/lightning_lightning_1.png differ diff --git a/mods/MISC/lightning/textures/lightning_lightning_2.png b/mods/MISC/lightning/textures/lightning_lightning_2.png new file mode 100644 index 000000000..7bab36b74 Binary files /dev/null and b/mods/MISC/lightning/textures/lightning_lightning_2.png differ diff --git a/mods/MISC/lightning/textures/lightning_lightning_3.png b/mods/MISC/lightning/textures/lightning_lightning_3.png new file mode 100644 index 000000000..f090529ec Binary files /dev/null and b/mods/MISC/lightning/textures/lightning_lightning_3.png differ diff --git a/mods/MISC/weather_pack/README.md b/mods/MISC/weather_pack/README.md new file mode 100644 index 000000000..09d83e3bd --- /dev/null +++ b/mods/MISC/weather_pack/README.md @@ -0,0 +1,47 @@ +weather-pack +======================= +Weather mod for Minetest (http://minetest.net/) + +Weathers included +----------------------- +* rain +* snow +* thunder + +Commands +----------------------- +`set_weather ` requires `weather_manager` privilege. + +Dependencies +----------------------- +Thunder weather requres [lightning](https://github.com/minetest-mods/lightning) mod. + +Configuration properties +----------------------- +Weather mod for indoor check depends on sunlight propogation check. Some nodes (e.g. glass block) propogates sunlight and thus weather particles will go through it. To change that set `weather_allow_override_nodes=true` in `minetest.conf` file. Be aware that just few nodes will be override and these blocks needs to be re-builded to take effect. Maybe in future other 'cheap' way to check indoor will be available. + +Weather mod mostly relies on particles generation however for some small things ABM may be used. Users which do not want it can disable ABM with property `weather_allow_abm=false`. + +License of source code: +----------------------- +LGPL 2.1+ + +Authors of media files: +----------------------- + +TeddyDesTodes: +Snowflakes licensed under CC-BY-SA 3.0 by from weather branch at https://github.com/TeddyDesTodes/minetest/tree/weather + + * `weather_pack_snow_snowflake1.png` - CC-BY-SA 3.0 + * `weather_pack_snow_snowflake2.png` - CC-BY-SA 3.0 + +xeranas: + + * `weather_pack_rain_raindrop_1.png` - CC-0 + * `weather_pack_rain_raindrop_2.png` - CC-0 + * `weather_pack_rain_raindrop_3.png` - CC-0 + +inchadney (http://freesound.org/people/inchadney/): + + * `weather_rain.ogg` - CC-BY-SA 3.0 (cut from http://freesound.org/people/inchadney/sounds/58835/) + diff --git a/mods/MISC/weather_pack/depends.txt b/mods/MISC/weather_pack/depends.txt new file mode 100644 index 000000000..b12962596 --- /dev/null +++ b/mods/MISC/weather_pack/depends.txt @@ -0,0 +1 @@ +lightning? \ No newline at end of file diff --git a/mods/MISC/weather_pack/description.txt b/mods/MISC/weather_pack/description.txt new file mode 100644 index 000000000..6efa41ed9 --- /dev/null +++ b/mods/MISC/weather_pack/description.txt @@ -0,0 +1 @@ +Set of weathers for minetest. \ No newline at end of file diff --git a/mods/MISC/weather_pack/init.lua b/mods/MISC/weather_pack/init.lua new file mode 100644 index 000000000..f740cc0f1 --- /dev/null +++ b/mods/MISC/weather_pack/init.lua @@ -0,0 +1,13 @@ +local modpath = minetest.get_modpath("weather_pack"); +dofile(modpath.."/weather_core.lua") +dofile(modpath.."/snow.lua") +dofile(modpath.."/rain.lua") + +if minetest.get_modpath("lightning") ~= nil then + dofile(modpath.."/thunder.lua") +end + +-- If not located then embeded skycolor mod version will be loaded. +if minetest.get_modpath("skycolor") == nil then + dofile(modpath.."/skycolor.lua") +end diff --git a/mods/MISC/weather_pack/mod.conf b/mods/MISC/weather_pack/mod.conf new file mode 100644 index 000000000..7baa0476a --- /dev/null +++ b/mods/MISC/weather_pack/mod.conf @@ -0,0 +1 @@ +name = weather_pack diff --git a/mods/MISC/weather_pack/rain.lua b/mods/MISC/weather_pack/rain.lua new file mode 100644 index 000000000..cd2bb783a --- /dev/null +++ b/mods/MISC/weather_pack/rain.lua @@ -0,0 +1,186 @@ +rain = { + -- max rain particles created at time + particles_count = 35, + + -- flag to turn on/off extinguish fire for rain + extinguish_fire = true, + + -- flag useful when mixing weathers + raining = false, + + -- keeping last timeofday value (rounded). + -- Defaulted to non-existing value for initial comparing. + sky_last_update = -1, + + init_done = false, +} + +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 (uses skycolor api) +rain.set_sky_box = function() + skycolor.add_layer( + "weather-pack-rain-sky", + {{r=0, g=0, b=0}, + {r=85, g=86, b=98}, + {r=152, g=150, b=159}, + {r=85, g=86, b=98}, + {r=0, g=0, b=0}}) + skycolor.active = true +end + +-- creating manually parctiles instead of particles spawner because of easier to control +-- spawn position. +rain.add_rain_particles = function(player) + + rain.last_rp_count = 0 + for i=rain.particles_count, 1,-1 do + local random_pos_x, random_pos_y, random_pos_z = weather.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.2, + size = math.random(0.5, 3), + collisiondetection = true, + collision_removal = 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 = "weather_pack_rain_raindrop_1.png" + elseif random_number > 0.66 then + texture_name = "weather_pack_rain_raindrop_2.png" + else + texture_name = "weather_pack_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()} + 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() + rain.raining = false + rain.sky_last_update = -1 + rain.init_done = false + skycolor.remove_layer("weather-pack-rain-sky") + 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 + + rain.make_weather() +end) + +rain.make_weather = function() + if rain.init_done == false then + rain.raining = true + rain.set_sky_box() + end + + for _, player in ipairs(minetest.get_connected_players()) do + if (weather.is_underwater(player)) then + return false + end + rain.add_player(player) + rain.add_rain_particles(player) + rain.update_sound(player) + end +end + +if weather.reg_weathers.rain == nil then + weather.reg_weathers.rain = { + chance = 15, + clear = rain.clear + } +end + +-- ABM for extinguish fire +if weather.allow_abm then + minetest.register_abm({ + nodenames = {"fire:basic_flame"}, + interval = 4.0, + chance = 2, + action = function(pos, node, active_object_count, active_object_count_wider) + if rain.raining and rain.extinguish_fire then + if weather.is_outdoor(pos) then + minetest.remove_node(pos) + end + end + end + }) +end \ No newline at end of file diff --git a/mods/MISC/weather_pack/screenshot.png b/mods/MISC/weather_pack/screenshot.png new file mode 100644 index 000000000..1ee3ea059 Binary files /dev/null and b/mods/MISC/weather_pack/screenshot.png differ diff --git a/mods/MISC/weather_pack/skycolor.lua b/mods/MISC/weather_pack/skycolor.lua new file mode 100644 index 000000000..b5005ba31 --- /dev/null +++ b/mods/MISC/weather_pack/skycolor.lua @@ -0,0 +1,212 @@ +skycolor = { + -- Should be activated before do any effect. + active = false, + + -- To skip update interval + force_update = true, + + -- Update interval. + update_interval = 15, + + -- Main sky colors: starts from midnight to midnight. + -- Please do not set directly. Use add_layer instead. + colors = {}, + + -- min value which will be used in color gradient, usualy its first user given color in 'pure' color. + min_val = 0, + + -- number of colors while constructing gradient of user given colors + max_val = 1000, + + -- Enables smooth transition between existing sky color and target. + smooth_transitions = true, + + -- Transition between current sky color and new user given. + transition_in_progress = false, + + -- Transition colors are generated automaticly during initialization. + transition_colors = {}, + + -- Time where transition between current color and user given will be done + transition_time = 15, + + -- Tracks how much time passed during transition + transition_timer = 0, + + -- Table for tracking layer order + layer_names = {}, + + -- To layer to colors table + add_layer = function(layer_name, layer_color, instant_update) + skycolor.colors[layer_name] = layer_color + table.insert(skycolor.layer_names, layer_name) + if (instant_update ~= true) then + skycolor.init_transition() + end + skycolor.force_update = true + end, + + -- Retrieve layer from colors table + retrieve_layer = function() + local last_layer = skycolor.layer_names[#skycolor.layer_names] + return skycolor.colors[last_layer] + end, + + -- Remove layer from colors table + remove_layer = function(layer_name) + for k, name in ipairs(skycolor.layer_names) do + if name == layer_name then + table.remove(skycolor.layer_names, k) + skycolor.force_update = true + return + end + end + end, + + -- Update sky color. If players not specified update sky for all players. + update_sky_color = function(players) + local color = skycolor.current_sky_layer_color() + if (color == nil) then + skycolor.active = false + skycolor.set_default_sky() + return + end + + players = skycolor.utils.get_players(players) + for _, player in ipairs(players) do + player:set_sky(color, "plain", nil) + end + end, + + -- Returns current layer color in {r, g, b} format + current_sky_layer_color = function() + if #skycolor.layer_names == 0 then + return nil + end + + -- min timeofday value 0; max timeofday value 1. So sky color gradient range will be between 0 and 1 * skycolor.max_value. + local timeofday = minetest.get_timeofday() + local rounded_time = math.floor(timeofday * skycolor.max_val) + local color = skycolor.utils.convert_to_rgb(skycolor.min_val, skycolor.max_val, rounded_time, skycolor.retrieve_layer()) + return color + end, + + -- Initialy used only on + update_transition_sky_color = function() + if #skycolor.layer_names == 0 then + skycolor.active = false + skycolor.set_default_sky() + return + end + + local multiplier = 100 + local rounded_time = math.floor(skycolor.transition_timer * multiplier) + if rounded_time >= skycolor.transition_time * multiplier then + skycolor.stop_transition() + return + end + + local color = skycolor.utils.convert_to_rgb(0, skycolor.transition_time * multiplier, rounded_time, skycolor.transition_colors) + + local players = skycolor.utils.get_players(nil) + for _, player in ipairs(players) do + player:set_sky(color, "plain", nil) + end + end, + + -- Reset sky color to game default. If players not specified update sky for all players. + -- Could be sometimes useful but not recomended to use in general case as there may be other color layers + -- which needs to preserve. + set_default_sky = function(players) + local players = skycolor.utils.get_players(players) + for _, player in ipairs(players) do + player:set_sky(nil, "regular", nil) + end + end, + + init_transition = function() + -- sadly default sky returns unpredictible colors so transition mode becomes usable only for user defined color layers + -- Here '2' means that one color layer existed before new added and transition is posible. + if #skycolor.layer_names < 2 then + return + end + + local transition_start_color = skycolor.utils.get_current_bg_color() + if (transition_start_color == nil) then + return + end + local transition_end_color = skycolor.current_sky_layer_color() + skycolor.transition_colors = {transition_start_color, transition_end_color} + skycolor.transition_in_progress = true + end, + + stop_transition = function() + skycolor.transition_in_progress = false + skycolor.transition_colors = {} + skycolor.transition_timer = 0 + end, + + utils = { + convert_to_rgb = function(minval, maxval, current_val, colors) + local max_index = #colors - 1 + local val = (current_val-minval) / (maxval-minval) * max_index + 1.0 + local index1 = math.floor(val) + local index2 = math.min(math.floor(val)+1, max_index + 1) + local f = val - index1 + local c1 = colors[index1] + local c2 = colors[index2] + return {r=math.floor(c1.r + f*(c2.r - c1.r)), g=math.floor(c1.g + f*(c2.g-c1.g)), b=math.floor(c1.b + f*(c2.b - c1.b))} + end, + + -- Simply getter. Ether returns user given players list or get all connected players if none provided + get_players = function(players) + if players == nil or #players == 0 then + players = minetest.get_connected_players() + end + return players + end, + + -- Returns first player sky color. I assume that all players are in same color layout. + get_current_bg_color = function() + local players = skycolor.utils.get_players(nil) + for _, player in ipairs(players) do + return player:get_sky() + end + return nil + end + }, + +} + +local timer = 0 +minetest.register_globalstep(function(dtime) + if skycolor.active ~= true or #minetest.get_connected_players() == 0 then + return + end + + if skycolor.smooth_transitions and skycolor.transition_in_progress then + skycolor.transition_timer = skycolor.transition_timer + dtime + skycolor.update_transition_sky_color() + return + end + + if skycolor.force_update then + skycolor.update_sky_color() + skycolor.force_update = false + return + end + + -- regular updates based on iterval + timer = timer + dtime; + if timer >= skycolor.update_interval then + skycolor.update_sky_color() + timer = 0 + end + +end) + +minetest.register_on_joinplayer(function(player) + if (skycolor.active) then + skycolor.update_sky_color({player}) + end +end) \ No newline at end of file diff --git a/mods/MISC/weather_pack/snow.lua b/mods/MISC/weather_pack/snow.lua new file mode 100644 index 000000000..7b4ec19d1 --- /dev/null +++ b/mods/MISC/weather_pack/snow.lua @@ -0,0 +1,90 @@ +snow = {} + +snow.particles_count = 15 +snow.init_done = false + +-- calculates coordinates and draw particles for snow weather +snow.add_rain_particles = function(player) + rain.last_rp_count = 0 + for i=snow.particles_count, 1,-1 do + local random_pos_x, random_pos_y, random_pos_z = weather.get_random_pos_by_player_look_dir(player) + random_pos_y = math.random() + math.random(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 = 2.0, + size = math.random(0.5, 2), + collisiondetection = true, + collision_removal = true, + vertical = true, + texture = snow.get_texture(), + playername = player:get_player_name() + }) + end + end +end + +snow.set_sky_box = function() + skycolor.add_layer( + "weather-pack-snow-sky", + {{r=0, g=0, b=0}, + {r=241, g=244, b=249}, + {r=0, g=0, b=0}} + ) + skycolor.active = true +end + +snow.clear = function() + skycolor.remove_layer("weather-pack-snow-sky") + snow.init_done = false +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 = "weather_pack_snow_snowflake1.png" + else + texture_name = "weather_pack_snow_snowflake2.png" + end + return texture_name; +end + +local timer = 0 +minetest.register_globalstep(function(dtime) + if weather.state ~= "snow" then + return false + end + + timer = timer + dtime; + if timer >= 0.5 then + timer = 0 + else + return + end + + if snow.init_done == false then + snow.set_sky_box() + snow.init_done = true + end + + for _, player in ipairs(minetest.get_connected_players()) do + if (weather.is_underwater(player)) then + return false + end + snow.add_rain_particles(player) + end +end) + +-- register snow weather +if weather.reg_weathers.snow == nil then + weather.reg_weathers.snow = { + chance = 10, + clear = snow.clear + } +end + diff --git a/mods/MISC/weather_pack/sounds/weather_rain.ogg b/mods/MISC/weather_pack/sounds/weather_rain.ogg new file mode 100644 index 000000000..ba2ea2721 Binary files /dev/null and b/mods/MISC/weather_pack/sounds/weather_rain.ogg differ diff --git a/mods/MISC/weather_pack/textures/weather_pack_rain_raindrop_1.png b/mods/MISC/weather_pack/textures/weather_pack_rain_raindrop_1.png new file mode 100644 index 000000000..ab18333b7 Binary files /dev/null and b/mods/MISC/weather_pack/textures/weather_pack_rain_raindrop_1.png differ diff --git a/mods/MISC/weather_pack/textures/weather_pack_rain_raindrop_2.png b/mods/MISC/weather_pack/textures/weather_pack_rain_raindrop_2.png new file mode 100644 index 000000000..fb37100a3 Binary files /dev/null and b/mods/MISC/weather_pack/textures/weather_pack_rain_raindrop_2.png differ diff --git a/mods/MISC/weather_pack/textures/weather_pack_rain_raindrop_3.png b/mods/MISC/weather_pack/textures/weather_pack_rain_raindrop_3.png new file mode 100644 index 000000000..4432b355e Binary files /dev/null and b/mods/MISC/weather_pack/textures/weather_pack_rain_raindrop_3.png differ diff --git a/mods/MISC/weather_pack/textures/weather_pack_snow_snowflake1.png b/mods/MISC/weather_pack/textures/weather_pack_snow_snowflake1.png new file mode 100644 index 000000000..8604f5dce Binary files /dev/null and b/mods/MISC/weather_pack/textures/weather_pack_snow_snowflake1.png differ diff --git a/mods/MISC/weather_pack/textures/weather_pack_snow_snowflake2.png b/mods/MISC/weather_pack/textures/weather_pack_snow_snowflake2.png new file mode 100644 index 000000000..bea317ee8 Binary files /dev/null and b/mods/MISC/weather_pack/textures/weather_pack_snow_snowflake2.png differ diff --git a/mods/MISC/weather_pack/thunder.lua b/mods/MISC/weather_pack/thunder.lua new file mode 100644 index 000000000..3ff645391 --- /dev/null +++ b/mods/MISC/weather_pack/thunder.lua @@ -0,0 +1,37 @@ +-- turn off lightning mod 'auto mode' +lightning.auto = false + +thunder = { + next_strike = 0, + min_delay = 3, + max_delay = 12, +} + +minetest.register_globalstep(function(dtime) + if weather.state ~= "thunder" then + return false + end + + rain.make_weather() + + if (thunder.next_strike <= os.time()) then + lightning.strike() + local delay = math.random(thunder.min_delay, thunder.max_delay) + thunder.next_strike = os.time() + delay + end + +end) + +thunder.clear = function() + rain.clear() +end + +-- register thunderstorm weather +if weather.reg_weathers.thunder == nil then + weather.reg_weathers.thunder = { + chance = 5, + clear = thunder.clear, + min_duration = 120, + max_duration = 600, + } +end \ No newline at end of file diff --git a/mods/MISC/weather_pack/weather_core.lua b/mods/MISC/weather_pack/weather_core.lua new file mode 100644 index 000000000..66466bea0 --- /dev/null +++ b/mods/MISC/weather_pack/weather_core.lua @@ -0,0 +1,175 @@ +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 + reg_weathers = {}, + + -- automaticly calculates intervals and swap weathers + auto_mode = true, + + -- global flag to disable/enable ABM logic. + allow_abm = true, +} + +weather.get_rand_end_time = function(min_duration, max_duration) + if min_duration ~= nil and max_duration ~= nil then + return os.time() + math.random(min_duration, max_duration); + else + return os.time() + math.random(weather.min_duration, weather.max_duration); + end +end + +weather.is_outdoor = function(pos) + if minetest.get_node_light({x=pos.x, y=pos.y + 1, z=pos.z}, 0.5) == 15 then + return true + end + return false +end + +-- checks if player is undewater. This is needed in order to +-- turn off weather particles generation. +weather.is_underwater = function(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} + 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. +weather.get_random_pos_by_player_look_dir = function(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 + 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 + 1, player_pos.y + 3) + return random_pos_x, random_pos_y, random_pos_z +end + +minetest.register_globalstep(function(dtime) + if weather.auto_mode == false then + return 0 + end + + -- 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.reg_weathers[weather.state].clear() + weather.state = "none" + end + elseif (weather.next_check <= os.time()) then + for weather_name, weather_meta in pairs(weather.reg_weathers) do + weather.set_random_weather(weather_name, weather_meta) + end + -- fallback next_check set, weather 'none' will be. + weather.next_check = os.time() + weather.check_interval + end +end) + +-- sets random weather (which could be 'regular' (no weather)). +weather.set_random_weather = function(weather_name, weather_meta) + if weather.next_check > os.time() then return 0 end + + if (weather_meta ~= nil and weather_meta.chance ~= nil) then + local random_roll = math.random(0,100) + if (random_roll <= weather_meta.chance) then + weather.state = weather_name + weather.end_time = weather.get_rand_end_time(weather_meta.min_duration, weather_meta.max_duration) + weather.next_check = os.time() + weather.check_interval + end + 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 = {weather_manager = true}, + func = function(name, param) + if (param == "none") then + if (weather.state ~= nil and weather.reg_weathers[weather.state] ~= nil) then + weather.reg_weathers[weather.state].clear() + weather.state = param + end + weather.state = "none" + end + + if (weather.reg_weathers ~= nil and weather.reg_weathers[param] ~= nil) then + if (weather.state ~= nil and weather.state ~= "none" and weather.reg_weathers[weather.state] ~= nil) then + weather.reg_weathers[weather.state].clear() + end + weather.state = param + 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.setting_getbool("weather_allow_abm") +if weather_allow_abm ~= nil and weather_allow_abm == false then + weather.allow_abm = false +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