Compare commits

...

41 Commits
v0.1 ... master

Author SHA1 Message Date
Arturas Norkus fa9e15628d Decrease chance for weather flickering effect (#9) 2019-12-20 11:08:41 +02:00
Artūras d9c26aba55 Merge branch 'master' into 'master'
Update README.md to reflect current location of repo

See merge request rautars/weather_pack!11
2019-12-18 13:03:41 +00:00
Arturas Norkus 2a8c2d0566 MT 0.4.16 compatibility fixes and code clean-up. 2019-07-19 20:06:28 +03:00
Elon Satoshi 0efa6f9e44 Update README.md to reflect current location of repo 2019-07-18 03:30:29 +00:00
Arturas Norkus c6987629e5 added snow particles for snowstorm 2019-07-14 16:45:50 +03:00
Arturas Norkus 9bb9a6cecb refactor: moved weather files to separate dir 2019-07-14 13:11:14 +03:00
Arturas Norkus 5e4a9a46cb added snowstorm weather 2019-07-14 12:42:12 +03:00
Arturas Norkus bb53648a69 fix auto weather (update to use new biome env API) 2019-07-11 06:46:40 +03:00
Artūras 28744a162f
Merge pull request #10 from bell07/patch-1
fix leaked global variable
2018-05-22 23:36:13 +03:00
bell07 eecf3c5524
fix leaked global variable 2018-05-22 22:34:37 +02:00
Arturas Norkus 5d8bf1ae1c adjusted clouds number 2018-04-15 19:10:24 +03:00
Arturas Norkus 3a315928c4 added clouds for rain weathers update embeded skylayer 2018-04-15 17:48:57 +03:00
Artūras 695ef4521b
fixed typo 2018-02-17 16:44:57 +02:00
Artūras 7dbab2c033
Merge pull request #9 from bell07/patch-1
avoid undeclared global variable access
2017-12-26 19:39:10 +02:00
bell07 6d21d3ddd1
avoid undeclared global variable access
remove warning 
Undeclared global variable "hw_utils" accessed at ...ds/ambience/weather_pack/utils.lua:9
2017-12-24 15:49:53 +01:00
Artūras f8e1640b9a Update README.md 2017-06-24 22:45:49 +03:00
Artūras 4fad47c4a4 Update README.md 2017-06-24 22:45:01 +03:00
Arturas Norkus f79c3e4505 Merge branch 'master' of https://github.com/xeranas/weather_pack 2017-06-24 22:23:56 +03:00
Arturas Norkus 5d4745cb27 mapgen v6 biome check support WIP 2017-06-24 22:23:54 +03:00
Arturas Norkus da72a58293 added add heigth limit for weather visibility 2017-06-24 22:22:59 +03:00
Artūras 96bd7176b9 Update README.md 2017-06-24 19:20:20 +03:00
Arturas Norkus e90133b30a minor code cleanup 2017-06-24 19:15:33 +03:00
Arturas Norkus 1f07735c44 turn off snow weather for v6 biome 2017-06-24 18:51:39 +03:00
Arturas Norkus 892cfa8b58 opt out v6 mapgen maps from biome check 2017-06-24 18:22:57 +03:00
Arturas Norkus 829605f70d remove clouds from weather skies 2017-06-04 10:46:45 +03:00
Artūras Norkus 61a265d7cc Update README.md 2017-05-28 09:54:15 +03:00
Arturas Norkus b2f32023f5 added biome check based on noice parameters 2017-05-28 09:49:37 +03:00
Arturas Norkus d51e0e28b0 update weather api 2017-05-27 23:15:39 +03:00
Arturas Norkus 34d9615c60 lovered chance for rain appearance 2017-05-24 16:12:50 +03:00
Arturas Norkus 17a67b846e fix optional lightning mod dependency issue 2017-05-24 08:27:27 +03:00
Arturas Norkus 9ec1790b16 start using happy_weather_api, fix #8 sky reseting issue 2017-05-22 13:40:16 +03:00
Arturas Norkus dcde7bdd2d update skycolor usage and its embeded version 2016-11-12 23:00:18 +02:00
Arturas Norkus 3e56c33226 structure change - convert to mod; fix for #4, #5 2016-10-23 17:22:50 +03:00
Arturas Norkus e0de4f31e5 fix sky reset on second appearance 2016-07-05 22:42:34 +03:00
Arturas Norkus 5dec942f77 #4 add several variations of sky color and runtime update for raining 2016-07-04 22:46:00 +03:00
Arturas Norkus 268fa33638 improve underwater check #2 for particles displaying 2016-06-25 21:05:43 +03:00
Artūras Norkus da308cbac2 minor code cleanup 2016-06-24 00:36:30 +03:00
Artūras Norkus ea242fb8c3 add collision_removal for particles spawners 2016-06-24 00:33:40 +03:00
Artūras Norkus 8fa399d2ca update raindrop textures 2016-06-24 00:29:24 +03:00
Artūras Norkus 105760a39c update default raindrop textures 2016-06-16 23:18:39 +03:00
Artūras Norkus 65e7471a07 minor rain particle configuration tweak 2016-06-16 23:18:22 +03:00
46 changed files with 1991 additions and 480 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Artūras Norkus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -2,46 +2,52 @@ weather-pack
=======================
Weather mod for Minetest (http://minetest.net/)
Feedback and Improvements
-----------------------
* See newest version at https://gitlab.com/zombiebot/weather_pack
* Register bugs at https://gitlab.com/zombiebot/weather_pack/issues
* Questions / Discussion at https://forum.minetest.net/viewtopic.php?p=215869
Weathers included
-----------------------
* rain
* light_rain, rain, heavy_rain
* snow
* thunder
* thunder (works together with heavy_rain)
Commands
-----------------------
`set_weather <weather>` requires `weather_manager` privilege.
requires `weather_manager` privilege.
* `start_weather <weather_code>`
* `stop_weather <weather_code>`
Be aware that weather may not be visible for player until player is in right biome.
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`.
Thunder weather requires [lightning](https://github.com/minetest-mods/lightning) mod.
License of source code:
-----------------------
LGPL 2.1+
MIT
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
* `snow_snowflake1.png` - CC-BY-SA 3.0
* `snow_snowflake2.png` - CC-BY-SA 3.0
xeranas:
* `rain_raindrop_1.png` - CC-0
* `rain_raindrop_2.png` - CC-0
* `rain_raindrop_3.png` - CC-0
* `happy_weather_heavy_rain_drops.png` - CC-0
* `happy_weather_light_rain_raindrop_*.png` - CC-0
* `happy_weather_light_snow_snowflake_*.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/)
* `rain_drop.ogg` - CC-BY-SA 3.0 (cut from http://freesound.org/people/inchadney/sounds/58835/)
rcproductions54 (http://freesound.org/people/rcproductions54/):
* `light_rain_drop.ogg` - CC-0 (http://freesound.org/people/rcproductions54/sounds/265045/)
uberhuberman
* `heavy_rain_drop.ogg` - CC BY 3.0 (https://www.freesound.org/people/uberhuberman/sounds/21189/)

21
abm.lua Normal file
View File

@ -0,0 +1,21 @@
--------------------------------
-- Happy Weather: ABM registers
-- License: MIT
-- Credits: xeranas
--------------------------------
-- ABM for extinguish fire
minetest.register_abm({
nodenames = {"fire:basic_flame"},
interval = 4.0,
chance = 2,
action = function(pos, node, active_object_count, active_object_count_wider)
if happy_weather.is_weather_active("heavy_rain") or happy_weather.is_weather_active("rain") then
if hw_utils.is_outdoor(pos) then
minetest.remove_node(pos)
end
end
end
})

36
commands.lua Normal file
View File

@ -0,0 +1,36 @@
------------------------------------
-- Happy Weather API Chat Commands
-- License: MIT
-- Credits: xeranas
------------------------------------
minetest.register_privilege("weather_manager", {
description = "Gives ability to control weather",
give_to_singleplayer = false
})
minetest.register_chatcommand("start_weather", {
params = "<weather_cd>",
description = "Starts weather by given weather code.",
privs = {weather_manager = true},
func = function(name, param)
if param ~= nil then
happy_weather.request_to_start(param)
minetest.log("action", name .. " requested weather '" .. param .. "' from chat command")
end
end
})
minetest.register_chatcommand("stop_weather", {
params = "<weather_cd>",
description = "Ends weather by given weather code.",
privs = {weather_manager = true},
func = function(name, param)
if param ~= nil then
happy_weather.request_to_end(param)
minetest.log("action", name .. " requested weather '" .. param .. "' ending from chat command")
end
end
})

1
depends.txt Normal file
View File

@ -0,0 +1 @@
lightning?

36
init.lua Normal file
View File

@ -0,0 +1,36 @@
local modpath = minetest.get_modpath("weather_pack");
-- If skylayer mod not located then embeded version will be loaded.
if minetest.get_modpath("skylayer") == nil then
dofile(modpath.."/lib_sky_layer_api.lua")
end
-- If happy_weather_api mod not located then embeded version will be loaded.
if minetest.get_modpath("happy_weather_api") == nil then
dofile(modpath.."/lib_happy_weather_api.lua")
dofile(modpath.."/commands.lua")
end
legacy_MT_version = false
if minetest.get_humidity == nil then
minetest.log("warning", "MOD [weather_pack]: Old Minetest version detected, some mod features will not work.")
legacy_MT_version = true
end
-- Happy Weather utilities
dofile(modpath.."/utils.lua")
dofile(modpath.."/weathers/light_rain.lua")
dofile(modpath.."/weathers/rain.lua")
dofile(modpath.."/weathers/heavy_rain.lua")
dofile(modpath.."/weathers/snow.lua")
dofile(modpath.."/weathers/snowstorm.lua")
if minetest.get_modpath("lightning") ~= nil then
dofile(modpath.."/weathers/thunder.lua")
-- Turn off lightning mod 'auto mode'
lightning.auto = false
end
dofile(modpath.."/abm.lua")

358
lib_happy_weather_api.lua Normal file
View File

@ -0,0 +1,358 @@
---------------------------
-- Happy Weather API
-- License: MIT
-- Credits: xeranas
---------------------------
-- Main object which will be used in Weather API lifecycle
happy_weather = {}
-- Local variables which helps organize active and deactive weahers
local registered_weathers = {}
local active_weathers = {}
local meta_plawpos = {} -- meta about Player Last Active Weaher Position
------------------------------------
-- Local helper / utility methods --
------------------------------------
-- Adds weather to active_weathers table
local add_active_weather = function(weather_obj)
table.insert(active_weathers, weather_obj)
end
-- Remove weather from active_weathers table
local remove_active_weather = function(weather_code)
if #active_weathers == 0 then
return
end
for k, weather_ in ipairs(active_weathers) do
if weather_.code == weather_code then
table.remove(active_weathers, k)
return
end
end
end
-- Returns active weather
local get_active_weather = function(weather_code)
if #active_weathers == 0 then
return nil
end
for k, weather_ in ipairs(active_weathers) do
if weather_.code == weather_code then
return weather_
end
end
end
-- adds player to affected_players table
local add_player = function(affected_players, player)
table.insert(affected_players, player)
end
-- remove player from affected_players table
local remove_player = function(affected_players, player_name)
if #affected_players == 0 then
return
end
for k, player_ in ipairs(affected_players) do
if player_:get_player_name() == player_name then
table.remove(affected_players, k)
return
end
end
end
local is_player_affected = function(affected_players, player_name)
if #affected_players == 0 then
return false
end
for k, player_ in ipairs(affected_players) do
if player_:get_player_name() == player_name then
return true
end
end
end
local remove_meta_plawpos = function(weather_code, player_name)
if #meta_plawpos == 0 then
return
end
for k, meta_ in ipairs(meta_plawpos) do
if (meta_.name == player_name and meta_.code == weather_code) then
table.remove(meta_plawpos, k)
end
end
end
local add_meta_plawpos = function(weather_code, player)
local meta = {}
meta.code = weather_code
meta.pos = player:getpos()
meta.name = player:get_player_name()
remove_meta_plawpos(weather_code, player:get_player_name())
table.insert(meta_plawpos, meta)
end
local get_meta_plawpos = function(weather_code, player_name)
if #meta_plawpos == 0 then
return nil
end
for k, meta_ in ipairs(meta_plawpos) do
if (meta_.name == player_name and meta_.code == weather_code) then
return meta_.pos
end
end
return nil
end
---------------------------
-- Weather API functions --
---------------------------
-- Adds weather to register_weathers table
happy_weather.register_weather = function(weather_obj)
table.insert(registered_weathers, weather_obj)
end
-- Returns true if weather is active right now, false otherwise
happy_weather.is_weather_active = function(weather_code)
if #active_weathers == 0 then
return false
end
for k, weather_ in ipairs(active_weathers) do
if weather_.code == weather_code then
return true
end
end
return false
end
-- Requests weaher to start
happy_weather.request_to_start = function(weather_code, position)
if #registered_weathers == 0 then
return
end
for k, weather_ in ipairs(registered_weathers) do
if weather_.code == weather_code and weather_.start ~= nil then
weather_.start(position)
return
end
end
end
-- Requests weaher to end
happy_weather.request_to_end = function(weather_code)
if #active_weathers == 0 then
return
end
for k, weather_ in ipairs(active_weathers) do
if weather_.code == weather_code and weather_.stop ~= nil then
weather_.stop()
return
end
end
end
happy_weather.is_player_in_weather_area = function(player_name, weather_code)
if #active_weathers == 0 then
return false
end
local active_weather = get_active_weather(weather_code)
if active_weather == nil then
return false
end
return is_player_affected(active_weather.affected_players, player_name)
end
-----------------------------------------------------------------------------
-- Weather object callback wrappers to avoid issues from undefined methods --
-----------------------------------------------------------------------------
-- Weather is_starting method nil-safe wrapper
local weather_is_starting = function(weather_obj, dtime, position)
if weather_obj.is_starting == nil then
return false
end
return weather_obj.is_starting(dtime, position)
end
-- Weather is_starting method nil-safe wrapper
local weather_is_ending = function(weather_obj, dtime)
if weather_obj.is_ending == nil then
return false
end
return weather_obj.is_ending(dtime)
end
-- Weather add_player method nil-safe wrapper
local weather_add_player = function(weather_obj, player)
if weather_obj.add_player == nil then
return
end
weather_obj.add_player(player)
end
-- Weather remove_player method nil-safe wrapper
local weather_remove_player = function(weather_obj, player)
if weather_obj.remove_player == nil then
return
end
weather_obj.remove_player(player)
end
-- Weather in_area method nil-safe wrapper
local weather_in_area = function(weather_obj, position)
if weather_obj.in_area == nil then
return true
end
return weather_obj.in_area(position)
end
-- Weather render method nil-safe wrapper
local weather_render = function(weather_obj, dtime, player)
if weather_obj.render == nil then
return
end
weather_obj.render(dtime, player)
end
-- Weather start method nil-safe wrapper
local weather_start = function(weather_obj, player)
if weather_obj.start == nil then
return
end
weather_obj.start(player)
end
-- Weather stop method nil-safe wrapper
local weather_stop = function(weather_obj, player)
if weather_obj.stop == nil then
return
end
weather_obj.stop(player)
end
-- Perform clean-up callbacks calls sets flags upon weaher end
local prepare_ending = function(weather_obj)
weather_obj.active = false
remove_active_weather(weather_obj.code)
end
-- Perform weather setup for certain player
local prepare_starting = function(weather_obj)
weather_obj.active = true
weather_obj.affected_players = {}
add_active_weather(weather_obj)
end
local MAX_DISTANCE_FROM_WEATHER = 35
-- This function aims to remove weather flickering effect when player walks on biome edge.
-- To accomlish that extra distance is applied before removing player from weather affection.
local is_outside_recent_weather = function(weather_code, player)
local pos = get_meta_plawpos(weather_code, player:get_player_name())
if pos == nil then
return false
end
local ppos = player:getpos()
local d = ((ppos.x - pos.x)^2 + (ppos.y - pos.y)^2 + (ppos.z - pos.z)^2)^0.5
return MAX_DISTANCE_FROM_WEATHER - d < 0
end
-- While still active weather can or can not affect players based on area they are
local render_if_in_area = function(weather_obj, dtime, player)
if is_player_affected(weather_obj.affected_players, player:get_player_name()) then
if weather_in_area(weather_obj, player:getpos()) then
weather_render(weather_obj, dtime, player)
add_meta_plawpos(weather_obj.code, player)
else
if (is_outside_recent_weather(weather_obj.code, player)) then
weather_remove_player(weather_obj, player)
remove_player(weather_obj.affected_players, player:get_player_name())
-- render weather until player will be completely outside weather range
else
weather_render(weather_obj, dtime, player)
end
end
else
if weather_in_area(weather_obj, player:getpos()) then
add_player(weather_obj.affected_players, player)
weather_add_player(weather_obj, player)
end
end
end
--------------------------
-- Global step function --
--------------------------
minetest.register_globalstep(function(dtime)
if #registered_weathers == 0 then
-- no registered weathers, do nothing.
return
end
if #minetest.get_connected_players() == 0 then
-- no actual players, do nothing.
return
end
-- Loop through registered weathers
for i, weather_ in ipairs(registered_weathers) do
local deactivate_weather = false
local activate_weather = false
-- Loop through connected players
for ii, player in ipairs(minetest.get_connected_players()) do
-- Weaher is active checking if it about to end
if weather_.active then
if weather_is_ending(weather_, dtime) or deactivate_weather then
weather_remove_player(weather_, player)
remove_player(weather_.affected_players, player:get_player_name())
deactivate_weather = true -- should remain true until all players will be removed from weather
-- Weather still active updating it
else
render_if_in_area(weather_, dtime, player)
end
-- Weaher is not active checking if it about to start
else
if weather_is_starting(weather_, dtime, player:getpos()) then
activate_weather = true
end
end
end
if deactivate_weather then
prepare_ending(weather_)
end
if activate_weather then
prepare_starting(weather_)
end
end
end)

364
lib_sky_layer_api.lua Normal file
View File

@ -0,0 +1,364 @@
-------------------------
-- Sky Layers: Core
-- License: MIT
-- Credits: xeranas
-- Thanks: Perkovec for colorise utils (github.com/Perkovec/colorise-lua)
-------------------------
local colorise = {}
colorise.rgb2hex = function (rgb)
local hexadecimal = '#'
for key = 1, #rgb do
local value = rgb[key]
local hex = ''
while(value > 0)do
local index = math.fmod(value, 16) + 1
value = math.floor(value / 16)
hex = string.sub('0123456789ABCDEF', index, index) .. hex
end
if(string.len(hex) == 0)then
hex = '00'
elseif(string.len(hex) == 1)then
hex = '0' .. hex
end
hexadecimal = hexadecimal .. hex
end
return hexadecimal
end
local core = {}
core.settings = {}
-- flag to disable skylayer at global step
core.settings.enabled = true
-- default gradient interval values
core.settings.gradient_default_min_value = 0
core.settings.gradient_default_max_value = 1000
-- how often sky will be updated in seconds
core.settings.update_interval = 4
-- helps track total dtime
core.timer = 0
core.default_clouds = nil
-- keeps player related data such as player itself and own sky layers
core.sky_players = {}
-- adds player to sky layer affected players list
core.add_player = function(player)
local data = {}
data.id = player:get_player_name()
data.player = player
data.skylayers = {}
table.insert(core.sky_players, data)
end
-- remove player from sky layer affected players list
core.remove_player = function(player_name)
if #core.sky_players == 0 then
return
end
for k, player_data in ipairs(core.sky_players) do
if player_data.id == player_name then
reset_sky(player_data.player)
table.remove(core.sky_players, k)
return
end
end
end
core.get_player_by_name = function(player_name)
if player_name == nil then
return nil
end
if #minetest.get_connected_players() == 0 then
return nil
end
for i, player in ipairs(minetest.get_connected_players()) do
if player:get_player_name() == player_name then
return player
end
end
return nil
end
core.get_player_data = function(player_name)
if #core.sky_players == 0 then
return nil
end
for k, player_data in ipairs(core.sky_players) do
if player_data.id == player_name then
return player_data
end
end
end
core.create_new_player_data = function(player_name)
local player_data = core.get_player_data(player_name)
if player_data == nil then
local player = core.get_player_by_name(player_name)
if player == nil then
minetest.log("error", "Fail to resolve player '" .. player_name .. "'")
return
end
core.add_player(player)
return core.get_player_data(player_name)
end
return player_data
end
-- sets default / regular sky for player
core.reset_sky = function(player)
core.set_default_sky(player)
core.set_default_clouds(player)
end
core.set_default_sky = function(player)
player:set_sky(nil, "regular", nil)
end
core.set_default_clouds = function(player)
player:set_clouds(core.default_clouds)
end
-- resolves latest skylayer based on added layer time
core.get_latest_layer = function(layers)
if #layers == 0 then
return nil
end
local latest_layer = nil
for k, layer in ipairs(layers) do
if latest_layer == nil then
latest_layer = layer
else
if layer.added_time >= latest_layer.added_time then
latest_layer = layer
end
end
end
return latest_layer
end
core.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
-- Returns current gradient color in {r, g, b} format
core.calculate_current_gradient_color = function(gradient_colors, min_val, max_val)
if gradient_colors == nil then return nil end
local timeofday = minetest.get_timeofday()
if min_val == nil then
min_val = core.settings.gradient_default_min_value
end
if max_val == nil then
max_val = core.settings.gradient_default_max_value
end
local rounded_time = math.floor(timeofday * max_val)
return core.convert_to_rgb(min_val, max_val, rounded_time, gradient_colors)
end
-- Returns current sky color in {r, g, b} format
core.get_current_layer_color = function(gradient_colors, min_val, max_val)
return core.calculate_current_gradient_color(gradient_colors, min_val, max_val)
end
-- Returns current cloud color in hex format
core.get_current_cloud_color = function(gradient_colors, min_val, max_val)
local rgb_color = core.calculate_current_gradient_color(gradient_colors, min_val, max_val)
if rgb_color == nil then return nil end
return colorise.rgb2hex({rgb_color.r, rgb_color.g, rgb_color.b})
end
core.update_sky_details = function(player, sky_layer)
local sky_data = sky_layer.sky_data
if sky_data == nil then
if sky_layer.reset_defaults == true then
core.set_default_sky(player)
sky_layer.reset_defaults = false
end
return
end
local sky_color = core.get_current_layer_color(
sky_data.gradient_colors,
sky_data.gradient_min_value,
sky_data.gradient_max_value)
local bgcolor = sky_data.bgcolor
if sky_color ~= nil then
bgcolor = sky_color
end
local sky_type = "plain" -- default
if sky_data.type ~= nil then
sky_type = sky_data.type
end
local clouds = sky_layer.clouds_data ~= nil
if sky_data.clouds ~= nil then
clouds = sky_data.clouds
end
player:set_sky(bgcolor, sky_type, sky_data.textures, clouds)
end
core.update_clouds_details = function(player, sky_layer)
local clouds_data = sky_layer.clouds_data
if clouds_data == nil then
if sky_layer.reset_defaults == true then
core.set_default_clouds(player)
sky_layer.reset_defaults = false
end
return
end
local cloud_color = core.get_current_cloud_color(
clouds_data.gradient_colors,
clouds_data.gradient_min_value,
clouds_data.gradient_max_value)
if cloud_color == nil then
cloud_color = clouds_data.color
end
player:set_clouds({
color = cloud_color,
density = clouds_data.density,
ambient = clouds_data.ambient,
height = clouds_data.height,
thickness = clouds_data.thickness,
speed = clouds_data.speed})
end
core.update_sky = function(player, timer)
local player_data = core.get_player_data(player:get_player_name())
if player_data == nil then return end
local current_layer = core.get_latest_layer(player_data.skylayers)
if current_layer == nil then
return
end
if skylayer.update_interval == nil then
skylayer.update_interval = core.settings.update_interval
end
if player_data.last_active_layer == nil or player_data.last_active_layer ~= current_layer.name then
current_layer.reset_defaults = true
end
player_data.last_active_layer = current_layer.name
if current_layer.updated == false or core.timer >= skylayer.update_interval then
current_layer.updated = os.time()
core.update_sky_details(player, current_layer)
core.update_clouds_details(player, current_layer)
end
end
minetest.register_on_joinplayer(function(player)
if core.default_clouds == nil then
core.default_clouds = player:get_clouds()
end
end)
minetest.register_globalstep(function(dtime)
if core.settings.enabled == false then
return
end
if #minetest.get_connected_players() == 0 then
return
end
-- timer addition calculated outside of players loop
core.timer = core.timer + dtime;
for k, player in ipairs(minetest.get_connected_players()) do
core.update_sky(player, core.timer)
end
-- reset timer outside of loop to make sure that all players sky will be updated
if core.timer >= core.settings.update_interval then
core.timer = 0
end
end)
-------------------------
-- Sky Layers: API
-- License: MIT
-- Credits: xeranas
-------------------------
skylayer = {}
-- set flag for enable / disable skylayer
skylayer.is_enabled = function(enabled)
core.settings.enabled = enabled
end
skylayer.add_layer = function(player_name, layer)
if layer == nil or layer.name == nil then
minetest.log("error", "Incorrect skylayer definition")
return
end
local player_data = core.get_player_data(player_name)
if player_data == nil then
player_data = core.create_new_player_data(player_name)
end
if player_data == nil then
minetest.log("error", "Fail to add skylayer to player '" .. player_name .. "'")
return
end
layer.added_time = os.time()
layer.updated = false
table.insert(player_data.skylayers, layer)
end
skylayer.remove_layer = function(player_name, layer_name)
local player_data = core.get_player_data(player_name)
if player_data == nil or player_data.skylayers == nil then
return
end
if #player_data.skylayers == 0 then
return
end
for k, layer in ipairs(player_data.skylayers) do
if layer.name == layer_name then
table.remove(player_data.skylayers, k)
if #player_data.skylayers == 0 then
local player = core.get_player_by_name(player_name)
if player ~= nil then
core.reset_sky(player)
end
end
return
end
end
end

1
mod.conf Normal file
View File

@ -0,0 +1 @@
name = weather_pack

View File

View File

@ -1 +0,0 @@
weather_core

View File

@ -1,3 +0,0 @@
-- init file for rain
local modpath = minetest.get_modpath("rain");
dofile(modpath.."/rain.lua")

View File

@ -1,169 +0,0 @@
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,
}
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 = 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.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()
rain.raining = false
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()
rain.raining = true
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, dtime)
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

View File

@ -1 +0,0 @@
weather_core

View File

@ -1,3 +0,0 @@
-- init file for snow
local modpath = minetest.get_modpath("snow");
dofile(modpath.."/snow.lua")

View File

@ -1,59 +0,0 @@
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 = 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 = 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 (weather.is_underwater(player)) then
return false
end
snow.add_rain_particles(player, dtime)
end
end)
-- register snow weather
if weather.reg_weathers.snow == nil then
weather.reg_weathers.snow = {
chance = 10,
clear = function() end
}
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

BIN
sounds/heavy_rain_drop.ogg Normal file

Binary file not shown.

BIN
sounds/light_rain_drop.ogg Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@ -1,3 +0,0 @@
weather_core
rain
lightning?

View File

@ -1,5 +0,0 @@
-- init file for thunder
if minetest.get_modpath("lightning") ~= nil then
local modpath = minetest.get_modpath("thunder");
dofile(modpath.."/thunder.lua")
end

View File

@ -1,37 +0,0 @@
-- 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

128
utils.lua Normal file
View File

@ -0,0 +1,128 @@
---------------------------------------
-- Happy Weather: Utilities / Helpers
-- License: MIT
-- Credits: xeranas
---------------------------------------
if not minetest.global_exists("hw_utils") then
hw_utils = {}
end
local mg_name = minetest.get_mapgen_setting("mg_name")
-- outdoor check based on node light level
hw_utils.is_outdoor = function(pos, offset_y)
if offset_y == nil then
offset_y = 0
end
if minetest.get_node_light({x=pos.x, y=pos.y + offset_y, 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.
hw_utils.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.
hw_utils.get_random_pos = function(player, offset)
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(player_pos.x - offset.back, player_pos.x + offset.front) + math.random()
random_pos_z = math.random(player_pos.z - offset.back, player_pos.z + offset.front) + math.random()
else
random_pos_x = math.random(player_pos.x - offset.back, player_pos.x + offset.front) + math.random()
random_pos_z = math.random(player_pos.z - offset.front, player_pos.z + offset.back) + math.random()
end
else
if look_dir.z > 0 then
random_pos_x = math.random(player_pos.x - offset.front, player_pos.x + offset.back) + math.random()
random_pos_z = math.random(player_pos.z - offset.back, player_pos.z + offset.front) + math.random()
else
random_pos_x = math.random(player_pos.x - offset.front, player_pos.x + offset.back) + math.random()
random_pos_z = math.random(player_pos.z - offset.front, player_pos.z + offset.back) + math.random()
end
end
if offset.bottom ~= nil then
random_pos_y = math.random(player_pos.y - offset.bottom, player_pos.y + offset.top)
else
random_pos_y = player_pos.y + offset.top
end
return {x=random_pos_x, y=random_pos_y, z=random_pos_z}
end
local is_biome_frozen = function(position)
if legacy_MT_version then
return false;
end
local heat = minetest.get_heat(position)
-- below 35 heat biome considered to be frozen type
return heat < 35
end
hw_utils.is_biome_frozen = function(position)
if mg_name == "v6" then
return false -- v6 not supported.
end
return is_biome_frozen(position)
end
local is_biome_dry = function(position)
if legacy_MT_version then
return false;
end
local humidity = minetest.get_humidity(position)
local heat = minetest.get_heat(position)
return humidity < 50 and heat > 65
end
hw_utils.is_biome_dry = function(position)
if mg_name == "v6" then
return false
end
return is_biome_dry(position)
end
local is_biome_tropic = function(position)
if legacy_MT_version then
return false;
end
local humidity = minetest.get_humidity(position)
local heat = minetest.get_heat(position)
-- humid and temp values are taked by testing flying around world (not sure actually)
return humidity > 55 and heat > 70
end
hw_utils.is_biome_tropic = function(position)
if mg_name == "v6" then
return false -- v6 not supported yet.
end
return is_biome_tropic(position)
end

View File

@ -1,3 +0,0 @@
-- init file for weather_core
local modpath = minetest.get_modpath("weather_core");
dofile(modpath.."/weather_core.lua")

View File

@ -1,175 +0,0 @@
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}
if minetest.get_node_level(player_eye_pos) == 8 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 + 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() + math.random(player_pos.y + 1, player_pos.y + 7)
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 = "<weather>",
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

211
weathers/heavy_rain.lua Normal file
View File

@ -0,0 +1,211 @@
------------------------------
-- Happy Weather: Heavy Rain
-- License: MIT
-- Credits: xeranas
------------------------------
local heavy_rain = {}
heavy_rain.last_check = 0
heavy_rain.check_interval = 200
-- Weather identification code
heavy_rain.code = "heavy_rain"
-- Keeps sound handler references
local sound_handlers = {}
-- Manual triggers flags
local manual_trigger_start = false
local manual_trigger_end = false
-- Skycolor layer id
local SKYCOLOR_LAYER = "happy_weather_heavy_rain_sky"
heavy_rain.is_starting = function(dtime, position)
if heavy_rain.last_check + heavy_rain.check_interval < os.time() then
heavy_rain.last_check = os.time()
local heavy_rain_chance = 0.06
if hw_utils.is_biome_tropic(position) then
heavy_rain_chance = 0.4
end
if math.random() < heavy_rain_chance then
happy_weather.request_to_end("light_rain")
happy_weather.request_to_end("rain")
return true
end
end
if manual_trigger_start then
manual_trigger_start = false
return true
end
return false
end
heavy_rain.is_ending = function(dtime)
if heavy_rain.last_check + heavy_rain.check_interval < os.time() then
heavy_rain.last_check = os.time()
if math.random() < 0.7 then
if math.random() < 0.4 then
happy_weather.request_to_start("rain")
end
return true
end
end
if manual_trigger_end then
manual_trigger_end = false
return true
end
return false
end
local set_sky_box = function(player_name)
local sl = {}
sl.name = SKYCOLOR_LAYER
sl.sky_data = {
gradient_colors = {
{r=0, g=0, b=0},
{r=85, g=86, b=98},
{r=142, g=140, b=149},
{r=85, g=86, b=98},
{r=0, g=0, b=0}
},
}
sl.clouds_data = {
gradient_colors = {
{r=0, g=0, b=0},
{r=65, g=66, b=78},
{r=112, g=110, b=119},
{r=65, g=66, b=78},
{r=0, g=0, b=0}
},
speed = {z = 10, y = -40},
density = 0.6
}
skylayer.add_layer(player_name, sl)
end
local set_rain_sound = function(player)
return minetest.sound_play("heavy_rain_drop", {
object = player,
max_hear_distance = 2,
loop = true,
})
end
local remove_rain_sound = function(player)
local sound = sound_handlers[player:get_player_name()]
if sound ~= nil then
minetest.sound_stop(sound)
sound_handlers[player:get_player_name()] = nil
end
end
heavy_rain.add_player = function(player)
sound_handlers[player:get_player_name()] = set_rain_sound(player)
set_sky_box(player:get_player_name())
end
heavy_rain.remove_player = function(player)
remove_rain_sound(player)
skylayer.remove_layer(player:get_player_name(), SKYCOLOR_LAYER)
end
local rain_drop_texture = "happy_weather_heavy_rain_drops.png"
local add_close_range_rain_particle = function(player)
local offset = {
front = 1,
back = 0,
top = 6
}
local random_pos = hw_utils.get_random_pos(player, offset)
local rain_texture_size_offset_y = -1
if hw_utils.is_outdoor(random_pos, rain_texture_size_offset_y) then
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=-10, z=0},
expirationtime = 5,
size = 30,
collisiondetection = true,
collision_removal = true,
vertical = true,
texture = rain_drop_texture,
playername = player:get_player_name()
})
end
end
local add_wide_range_rain_particle = function(player)
local offset = {
front = 10,
back = 5,
top = 8
}
local random_pos = hw_utils.get_random_pos(player, offset)
if hw_utils.is_outdoor(random_pos) then
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=-15, z=0},
expirationtime = 5,
size = 30,
collisiondetection = true,
collision_removal = true,
vertical = true,
texture = rain_drop_texture,
playername = player:get_player_name()
})
end
end
local display_rain_particles = function(player)
if hw_utils.is_underwater(player) then
return
end
add_close_range_rain_particle(player)
local particles_number_per_update = 5
for i=particles_number_per_update, 1,-1 do
add_wide_range_rain_particle(player)
end
end
heavy_rain.render = function(dtime, player)
display_rain_particles(player)
end
heavy_rain.start = function()
manual_trigger_start = true
end
heavy_rain.stop = function()
manual_trigger_end = true
end
heavy_rain.in_area = function(position)
if hw_utils.is_biome_frozen(position) or
hw_utils.is_biome_dry(position) then
return false
end
if position.y > -10 and position.y < 120 then
return true
end
return false
end
happy_weather.register_weather(heavy_rain)

166
weathers/light_rain.lua Normal file
View File

@ -0,0 +1,166 @@
------------------------------
-- Happy Weather: Light Rain
-- License: MIT
-- Credits: xeranas
------------------------------
local light_rain = {}
light_rain.last_check = 0
light_rain.check_interval = 200
light_rain.chance = 0.15
-- Weather identification code
light_rain.code = "light_rain"
-- Keeps sound handler references
local sound_handlers = {}
-- Manual triggers flags
local manual_trigger_start = false
local manual_trigger_end = false
-- Skycolor layer id
local SKYCOLOR_LAYER = "happy_weather_light_rain_sky"
light_rain.is_starting = function(dtime, position)
if light_rain.last_check + light_rain.check_interval < os.time() then
light_rain.last_check = os.time()
if math.random() < light_rain.chance then
return true
end
end
if manual_trigger_start then
manual_trigger_start = false
return true
end
return false
end
light_rain.is_ending = function(dtime)
if light_rain.last_check + light_rain.check_interval < os.time() then
light_rain.last_check = os.time()
if math.random() < 0.5 then
return true
end
end
if manual_trigger_end then
manual_trigger_end = false
return true
end
return false
end
local set_sky_box = function(player_name)
local sl = {}
sl.name = SKYCOLOR_LAYER
sl.clouds_data = {
gradient_colors = {
{r=50, g=50, b=50},
{r=120, g=120, b=120},
{r=200, g=200, b=200},
{r=120, g=120, b=120},
{r=50, g=50, b=50}
},
density = 0.6
}
skylayer.add_layer(player_name, sl)
end
local set_rain_sound = function(player)
return minetest.sound_play("light_rain_drop", {
object = player,
max_hear_distance = 2,
loop = true,
})
end
local remove_rain_sound = function(player)
local sound = sound_handlers[player:get_player_name()]
if sound ~= nil then
minetest.sound_stop(sound)
sound_handlers[player:get_player_name()] = nil
end
end
light_rain.add_player = function(player)
sound_handlers[player:get_player_name()] = set_rain_sound(player)
set_sky_box(player:get_player_name())
end
light_rain.remove_player = function(player)
remove_rain_sound(player)
skylayer.remove_layer(player:get_player_name(), SKYCOLOR_LAYER)
end
-- Random texture getter
local choice_random_rain_drop_texture = function()
local base_name = "happy_weather_light_rain_raindrop_"
local number = math.random(1, 4)
local extension = ".png"
return base_name .. number .. extension
end
local add_rain_particle = function(player)
local offset = {
front = 5,
back = 2,
top = 4
}
local random_pos = hw_utils.get_random_pos(player, offset)
if hw_utils.is_outdoor(random_pos) then
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 = 2,
size = math.random(0.5, 3),
collisiondetection = true,
collision_removal = true,
vertical = true,
texture = choice_random_rain_drop_texture(),
playername = player:get_player_name()
})
end
end
local display_rain_particles = function(player)
if hw_utils.is_underwater(player) then
return
end
add_rain_particle(player)
end
light_rain.in_area = function(position)
if hw_utils.is_biome_frozen(position) or
hw_utils.is_biome_dry(position) then
return false
end
if position.y > -10 and position.y < 120 then
return true
end
return false
end
light_rain.render = function(dtime, player)
display_rain_particles(player)
end
light_rain.start = function()
manual_trigger_start = true
end
light_rain.stop = function()
manual_trigger_end = true
end
happy_weather.register_weather(light_rain)

183
weathers/rain.lua Normal file
View File

@ -0,0 +1,183 @@
------------------------------
-- Happy Weather: Rain
-- License: MIT
-- Credits: xeranas
------------------------------
local rain = {}
rain.last_check = 0
rain.check_interval = 300
rain.chance = 0.1
-- Weather identification code
rain.code = "rain"
-- Keeps sound handler references
local sound_handlers = {}
-- Manual triggers flags
local manual_trigger_start = false
local manual_trigger_end = false
-- Skycolor layer id
local SKYCOLOR_LAYER = "happy_weather_rain_sky"
rain.is_starting = function(dtime, position)
if rain.last_check + rain.check_interval < os.time() then
rain.last_check = os.time()
if math.random() < rain.chance then
happy_weather.request_to_end("light_rain")
happy_weather.request_to_end("heavy_rain")
return true
end
end
if manual_trigger_start then
manual_trigger_start = false
return true
end
return false
end
rain.is_ending = function(dtime)
if rain.last_check + rain.check_interval < os.time() then
rain.last_check = os.time()
if math.random() < 0.6 then
happy_weather.request_to_start("light_rain")
return true
end
end
if manual_trigger_end then
manual_trigger_end = false
return true
end
return false
end
local set_sky_box = function(player_name)
local sl = {}
sl.layer_type = skylayer.SKY_PLAIN
sl.name = SKYCOLOR_LAYER
sl.sky_data = {
gradient_colors = {
{r=0, g=0, b=0},
{r=65, g=66, b=78},
{r=112, g=110, b=119},
{r=65, g=66, b=78},
{r=0, g=0, b=0}
},
}
sl.clouds_data = {
gradient_colors = {
{r=10, g=10, b=10},
{r=55, g=56, b=68},
{r=102, g=100, b=109},
{r=55, g=56, b=68},
{r=10, g=10, b=10}
},
density = 0.5
}
skylayer.add_layer(player_name, sl)
end
local set_rain_sound = function(player)
return minetest.sound_play("rain_drop", {
object = player,
max_hear_distance = 2,
loop = true,
})
end
local remove_rain_sound = function(player)
local sound = sound_handlers[player:get_player_name()]
if sound ~= nil then
minetest.sound_stop(sound)
sound_handlers[player:get_player_name()] = nil
end
end
rain.add_player = function(player)
sound_handlers[player:get_player_name()] = set_rain_sound(player)
set_sky_box(player:get_player_name())
end
rain.remove_player = function(player)
remove_rain_sound(player)
skylayer.remove_layer(player:get_player_name(), SKYCOLOR_LAYER)
end
-- Random texture getter
local choice_random_rain_drop_texture = function()
local base_name = "happy_weather_light_rain_raindrop_"
local number = math.random(1, 4)
local extension = ".png"
return base_name .. number .. extension
end
local add_rain_particle = function(player)
local offset = {
front = 5,
back = 2,
top = 6
}
local random_pos = hw_utils.get_random_pos(player, offset)
if hw_utils.is_outdoor(random_pos) then
minetest.add_particle({
pos = {x=random_pos.x, y=random_pos.y, z=random_pos.z},
velocity = {x=0, y=-15, z=0},
acceleration = {x=0, y=-35, z=0},
expirationtime = 2,
size = math.random(1, 4),
collisiondetection = true,
collision_removal = true,
vertical = true,
texture = choice_random_rain_drop_texture(),
playername = player:get_player_name()
})
end
end
local display_rain_particles = function(player)
if hw_utils.is_underwater(player) then
return
end
add_rain_particle(player)
end
rain.in_area = function(position)
if hw_utils.is_biome_frozen(position) or
hw_utils.is_biome_dry(position) then
return false
end
if position.y > -10 and position.y < 120 then
return true
end
return false
end
local particles_number_per_update = 10
rain.render = function(dtime, player)
for i=particles_number_per_update, 1,-1 do
display_rain_particles(player)
end
end
rain.start = function()
manual_trigger_start = true
end
rain.stop = function()
manual_trigger_end = true
end
happy_weather.register_weather(rain)

144
weathers/snow.lua Normal file
View File

@ -0,0 +1,144 @@
------------------------------
-- Happy Weather: Light Rain
-- License: MIT
-- Credits: xeranas
------------------------------
local snow = {}
snow.last_check = 0
snow.check_interval = 200
snow.chance = 0.2
-- Weather identification code
snow.code = "snow"
-- Manual triggers flags
local manual_trigger_start = false
local manual_trigger_end = false
-- Skycolor layer id
local SKYCOLOR_LAYER = "happy_weather_snow_sky"
snow.is_starting = function(dtime, position)
if snow.last_check + snow.check_interval < os.time() then
snow.last_check = os.time()
if math.random() < snow.chance then
return true
end
end
if manual_trigger_start then
manual_trigger_start = false
return true
end
return false
end
snow.is_ending = function(dtime)
if snow.last_check + snow.check_interval < os.time() then
snow.last_check = os.time()
if math.random() < 0.5 then
return true
end
end
if manual_trigger_end then
manual_trigger_end = false
return true
end
return false
end
local set_sky_box = function(player_name)
local sl = {}
sl.name = SKYCOLOR_LAYER
sl.sky_data = {
gradient_colors = {
{r=0, g=0, b=0},
{r=231, g=234, b=239},
{r=0, g=0, b=0}
}
}
skylayer.add_layer(player_name, sl)
end
snow.add_player = function(player)
set_sky_box(player:get_player_name())
end
snow.remove_player = function(player)
skylayer.remove_layer(player:get_player_name(), SKYCOLOR_LAYER)
end
-- Random texture getter
local choice_random_rain_drop_texture = function()
local base_name = "happy_weather_light_snow_snowflake_"
local number = math.random(1, 3)
local extension = ".png"
return base_name .. number .. extension
end
local add_particle = function(player)
local offset = {
front = 5,
back = 2,
top = 4
}
local random_pos = hw_utils.get_random_pos(player, offset)
if hw_utils.is_outdoor(random_pos) then
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 = choice_random_rain_drop_texture(),
playername = player:get_player_name()
})
end
end
local display_particles = function(player)
if hw_utils.is_underwater(player) then
return
end
add_particle(player)
end
local particles_number_per_update = 10
snow.render = function(dtime, player)
for i=particles_number_per_update, 1,-1 do
display_particles(player)
end
end
snow.in_area = function(position)
if hw_utils.is_biome_frozen(position) == false then
return false
end
if position.y > -10 and position.y < 120 then
return true
end
return false
end
snow.start = function()
manual_trigger_start = true
end
snow.stop = function()
manual_trigger_end = true
end
happy_weather.register_weather(snow)

203
weathers/snowstorm.lua Normal file
View File

@ -0,0 +1,203 @@
----------------------------
-- Happy Weather: Snowfall
-- License: MIT
-- Credits: xeranas
----------------------------
local snowstorm = {}
-- Weather identification code
snowstorm.code = "snowstorm"
snowstorm.last_check = 0
snowstorm.check_interval = 300
snowstorm.chance = 0.05
-- Keeps sound handler references
local sound_handlers = {}
-- Manual triggers flags
local manual_trigger_start = false
local manual_trigger_end = false
-- Skycolor layer id
local SKYCOLOR_LAYER = "happy_weather_snowstorm_sky"
local set_weather_sound = function(player)
return minetest.sound_play("happy_weather_snowstorm", {
object = player,
max_hear_distance = 2,
loop = true,
})
end
local remove_weather_sound = function(player)
local sound = sound_handlers[player:get_player_name()]
if sound ~= nil then
minetest.sound_stop(sound)
sound_handlers[player:get_player_name()] = nil
end
end
snowstorm.is_starting = function(dtime, position)
if snowstorm.last_check + snowstorm.check_interval < os.time() then
snowstorm.last_check = os.time()
if math.random() < snowstorm.chance then
return true
end
end
if manual_trigger_start then
manual_trigger_start = false
return true
end
return false
end
snowstorm.is_ending = function(dtime)
if manual_trigger_end then
manual_trigger_end = false
return true
end
return false
end
local set_sky_box = function(player_name)
local sl = {}
sl.layer_type = skylayer.SKY_PLAIN
sl.name = SKYCOLOR_LAYER
sl.sky_data = {
gradient_colors = {
{r=0, g=0, b=0},
{r=231, g=234, b=239},
{r=0, g=0, b=0}
}
}
skylayer.add_layer(player_name, sl)
end
snowstorm.in_area = function(position)
if hw_utils.is_biome_frozen(position) == false then
return false
end
if position.y > 30 and position.y < 140 then
return true
end
return false
end
snowstorm.add_player = function(player)
sound_handlers[player:get_player_name()] = set_weather_sound(player)
set_sky_box(player:get_player_name())
end
snowstorm.remove_player = function(player)
remove_weather_sound(player)
skylayer.remove_layer(player:get_player_name(), SKYCOLOR_LAYER)
end
local rain_drop_texture = "happy_weather_snowstorm.png"
local sign = function (number)
if number >= 0 then
return 1
else
return -1
end
end
local add_wide_range_rain_particle = function(player)
local offset = {
front = 7,
back = 4,
top = 3,
bottom = 0
}
local random_pos = hw_utils.get_random_pos(player, offset)
local p_pos = player:getpos()
local look_dir = player:get_look_dir()
if hw_utils.is_outdoor(random_pos) then
minetest.add_particle({
pos = {x=random_pos.x, y=random_pos.y, z=random_pos.z},
velocity = {x = sign(look_dir.x) * -10, y = -1, z = sign(look_dir.z) * -10},
acceleration = {x = sign(look_dir.x) * -10, y = -1, z = sign(look_dir.z) * -10},
expirationtime = 0.3,
size = 30,
collisiondetection = true,
texture = "happy_weather_snowstorm.png",
playername = player:get_player_name()
})
end
end
-- Random texture getter
local choice_random_rain_drop_texture = function()
local base_name = "happy_weather_light_snow_snowflake_"
local number = math.random(1, 3)
local extension = ".png"
return base_name .. number .. extension
end
local add_snow_particle = function(player)
local offset = {
front = 5,
back = 2,
top = 4
}
local random_pos = hw_utils.get_random_pos(player, offset)
if hw_utils.is_outdoor(random_pos) then
minetest.add_particle({
pos = {x=random_pos.x, y=random_pos.y, z=random_pos.z},
velocity = {x = math.random(-5,-2.5), y = math.random(-10,-5), z = math.random(-5,-2.5)},
acceleration = {x = math.random(-5,-2.5), y=-2.5, z = math.random(-5,-2.5)},
expirationtime = 2.0,
size = math.random(1, 3),
collisiondetection = true,
collision_removal = true,
vertical = true,
texture = choice_random_rain_drop_texture(),
playername = player:get_player_name()
})
end
end
local display_particles = function(player)
if hw_utils.is_underwater(player) then
return
end
local particles_number_per_update = 3
for i=particles_number_per_update, 1,-1 do
add_wide_range_rain_particle(player)
end
local snow_particles_number_per_update = 10
for i=snow_particles_number_per_update, 1,-1 do
add_snow_particle(player)
end
end
snowstorm.render = function(dtime, player)
display_particles(player)
end
snowstorm.start = function()
manual_trigger_start = true
end
snowstorm.stop = function()
manual_trigger_end = true
end
happy_weather.register_weather(snowstorm)

91
weathers/thunder.lua Normal file
View File

@ -0,0 +1,91 @@
----------------------------------------------------------------
-- Happy Weather: Thunder
-- License: MIT
-- Credits: xeranas
-- See also: lightning mod for actual lightning effect, sounds.
----------------------------------------------------------------
local thunder = {}
thunder.last_check = 0
thunder.check_interval = 100
thunder.chance = 0.8
-- Weather identification code
thunder.code = "thunder"
local thunder_target_weather_code = "heavy_rain"
-- Manual triggers flags
local manual_trigger_start = false
local manual_trigger_end = false
-- Thunder weather appearance control
local thunder_weather_chance = 5 -- 5 percent appearance during heavy rain
local thunder_weather_next_check = 0
local thunder_weather_check_delay = 600 -- to avoid checks continuously
thunder.is_starting = function(dtime)
thunder.next_strike = 0
thunder.min_delay = 5
thunder.max_delay = math.random(5, 45)
if thunder.last_check + thunder.check_interval < os.time() then
thunder.last_check = os.time()
if math.random() < thunder.chance and happy_weather.is_weather_active("heavy_rain") then
return true
end
end
if manual_trigger_start then
manual_trigger_start = false
return true
end
return false
end
thunder.is_ending = function(dtime)
if thunder.last_check + thunder.check_interval < os.time() then
thunder.last_check = os.time()
if math.random() < 0.4 or happy_weather.is_weather_active("heavy_rain") == false then
return true
end
end
if manual_trigger_end then
manual_trigger_end = false
return true
end
return false
end
local calculate_thunder_strike_delay = function()
local delay = math.random(thunder.min_delay, thunder.max_delay)
thunder.next_strike = os.time() + delay
end
thunder.render = function(dtime, player)
local player_name = player:get_player_name()
if happy_weather.is_player_in_weather_area(player_name, "heavy_rain") == false then
return
end
if thunder.next_strike <= os.time() then
lightning.strike()
calculate_thunder_strike_delay()
end
end
thunder.start = function()
manual_trigger_start = true
end
thunder.stop = function()
manual_trigger_end = true
end
happy_weather.register_weather(thunder)