start using happy_weather_api, fix #8 sky reseting issue

This commit is contained in:
Arturas Norkus 2017-05-22 13:40:16 +03:00
parent dcde7bdd2d
commit 9ec1790b16
30 changed files with 1420 additions and 680 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

@ -4,44 +4,46 @@ Weather mod for Minetest (http://minetest.net/)
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>`
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+
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
* `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
* `happy_weather_heavy_rain_drops.png` - CC-0
* `happy_weather_light_rain_raindrop_1.png` - CC-0
* `happy_weather_light_rain_raindrop_2.png` - CC-0
* `happy_weather_light_rain_raindrop_3.png` - CC-0
* `happy_weather_light_snow_snowflake_1.png` - CC-0
* `happy_weather_light_snow_snowflake_2.png` - CC-0
* `happy_weather_light_snow_snowflake_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/)
* `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
})

View File

@ -0,0 +1,271 @@
---------------------------
-- 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 = {}
---------------------------
-- 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
------------------------------------
-- 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
-- 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
-----------------------------------------------------------------------------
-- 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 remove_player 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
-- 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)
else
weather_remove_player(weather_obj, player)
remove_player(weather_obj.affected_players, player:get_player_name())
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(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)

265
embedded_sky_layer_api.lua Normal file
View File

@ -0,0 +1,265 @@
-------------------------
-- Sky Layers: API
-- License: MIT
-- Credits: xeranas
-------------------------
skylayer = {}
-- flag for enable / disable skylayer temporally if needed
skylayer.enabled = true
-- supported skylayer types
skylayer.SKY_PLAIN = "plain"
skylayer.SKY_SOLID_COLOR = "solid_color"
skylayer.SKY_SKYBOX = "skybox"
-- helps track total dtime
local timer = 0
local gradient_default_min_value = 0
local gradient_default_max_value = 1000
-- how often sky will be updated in seconds
skylayer.update_interval = 4
-- keeps player related data such as player itself and own sky layers
local sky_players = {}
-- adds player to sky layer affected players list
local add_player = function(player)
local data = {}
data.id = player:get_player_name()
data.player = player
data.skylayers = {}
table.insert(sky_players, data)
end
-- remove player from sky layer affected players list
local remove_player = function(player_name)
if #sky_players == 0 then
return
end
for k, player_data in ipairs(sky_players) do
if player_data.id == player_name then
set_default_sky(player_data.player)
table.remove(sky_players, k)
return
end
end
end
local 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
local get_player_data = function(player_name)
if #sky_players == 0 then
return nil
end
for k, player_data in ipairs(sky_players) do
if player_data.id == player_name then
return player_data
end
end
end
local create_new_player_data = function(player_name)
local player_data = get_player_data(player_name)
if player_data == nil then
local player = get_player_by_name(player_name)
if player == nil then
minetest.log("error", "Fail to resolve player '" .. player_name .. "'")
return
end
add_player(player)
return get_player_data(player_name)
end
return player_data
end
-- sets default / regular sky for player
local set_default_sky = function(player)
player:set_sky(nil, "regular", nil)
end
-- resolves latest skylayer based on added layer time
local 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
local 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 layer color in {r, g, b} format
local get_current_layer_color = function(layer_data)
-- 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 min_val = layer_data.gradient_data.min_value
if min_val == nil then
min_val = gradient_default_min_value
end
local max_val = layer_data.gradient_data.max_value
if max_val == nil then
max_val = gradient_default_max_value
end
local rounded_time = math.floor(timeofday * max_val)
local gradient_colors = layer_data.gradient_data.colors
local color = convert_to_rgb(min_val, max_val, rounded_time, gradient_colors)
return color
end
local update_plain_sky = function(player, layer_data)
local color = get_current_layer_color(layer_data)
player:set_sky(color, "plain", nil)
end
local update_solid_color_sky = function(player, layer_data)
player:set_sky(layer_data.color, "plain", nil)
end
local update_skybox_sky = function(player, layer_data)
player:set_sky(layer_data.skybox[1], layer_data.skybox[2], layer_data.skybox[3])
end
local update_sky = function(player, timer)
local player_data = get_player_data(player:get_player_name())
if player_data == nil then return end
local current_layer = get_latest_layer(player_data.skylayers)
if current_layer == nil then
return
end
if current_layer.updated == false or timer >= skylayer.update_interval then
current_layer.updated = os.time()
if current_layer.layer_type == skylayer.SKY_PLAIN then
update_plain_sky(player, current_layer.data)
return
end
if current_layer.layer_type == skylayer.SKY_SOLID_COLOR then
update_solid_color_sky(player, current_layer.data)
return
end
if current_layer.layer_type == skylayer.SKY_SKYBOX then
update_skybox_sky(player, current_layer.data)
return
end
end
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 = get_player_data(player_name)
if player_data == nil then
player_data = 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 = 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 = get_player_by_name(player_name)
if player ~= nil then
set_default_sky(player)
end
end
return
end
end
end
minetest.register_globalstep(function(dtime)
if skylayer.enabled == false then
return
end
if #minetest.get_connected_players() == 0 then
return
end
-- timer addition calculated outside of players loop
timer = timer + dtime;
for k, player in ipairs(minetest.get_connected_players()) do
update_sky(player, timer)
end
-- reset timer outside of loop to make sure that all players sky will be updated
if timer >= skylayer.update_interval then
timer = 0
end
end)

187
heavy_rain.lua Normal file
View File

@ -0,0 +1,187 @@
------------------------------
-- Happy Weather: Heavy Rain
-- License: MIT
-- Credits: xeranas
------------------------------
local heavy_rain = {}
heavy_rain.last_check = 0
heavy_rain.check_interval = 600
-- 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()
if math.random() < 0.05 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
happy_weather.request_to_start("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.data = {gradient_data={}}
sl.data.gradient_data.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}
}
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 position.y > -10 then
return true
end
return false
end
happy_weather.register_weather(heavy_rain)

View File

@ -1,13 +1,29 @@
local modpath = minetest.get_modpath("weather_pack");
dofile(modpath.."/weather_core.lua")
dofile(modpath.."/snow.lua")
-- If skylayer mod not located then embeded version will be loaded.
if minetest.get_modpath("skylayer") == nil then
dofile(modpath.."/embedded_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.."/embedded_happy_weather_api.lua")
dofile(modpath.."/commands.lua")
end
-- Happy Weather utilities
dofile(modpath.."/utils.lua")
dofile(modpath.."/light_rain.lua")
dofile(modpath.."/rain.lua")
dofile(modpath.."/heavy_rain.lua")
dofile(modpath.."/snow.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
-- Turn off lightning mod 'auto mode'
lightning.auto = false
dofile(modpath.."/abm.lua")

165
light_rain.lua Normal file
View File

@ -0,0 +1,165 @@
------------------------------
-- Happy Weather: Light Rain
-- License: MIT
-- Credits: xeranas
------------------------------
local light_rain = {}
light_rain.last_check = 0
light_rain.check_interval = 300
-- 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() < 0.2 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.4 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.layer_type = skylayer.SKY_PLAIN
sl.name = SKYCOLOR_LAYER
sl.data = {gradient_data={}}
sl.data.gradient_data.colors = {
{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}
}
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 texture_name
local random_number = math.random()
if random_number > 0.33 then
texture_name = "happy_weather_light_rain_raindrop_1.png"
elseif random_number > 0.66 then
texture_name = "happy_weather_light_rain_raindrop_2.png"
else
texture_name = "happy_weather_light_rain_raindrop_3.png"
end
return texture_name;
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 position.y > -10 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)

296
rain.lua
View File

@ -1,186 +1,172 @@
rain = {
-- max rain particles created at time
particles_count = 35,
------------------------------
-- Happy Weather: Rain
-- flag to turn on/off extinguish fire for rain
extinguish_fire = true,
-- flag useful when mixing weathers
raining = false,
-- License: MIT
-- keeping last timeofday value (rounded).
-- Defaulted to non-existing value for initial comparing.
sky_last_update = -1,
-- Credits: xeranas
------------------------------
init_done = false,
}
local rain = {}
rain.last_check = 0
rain.check_interval = 400
rain.sound_handler = function(player)
return minetest.sound_play("weather_rain", {
object = player,
max_hear_distance = 2,
loop = true,
})
end
-- Weather identification code
rain.code = "rain"
-- 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
-- Keeps sound handler references
local sound_handlers = {}
-- creating manually parctiles instead of particles spawner because of easier to control
-- spawn position.
rain.add_rain_particles = function(player)
-- Manual triggers flags
local manual_trigger_start = false
local manual_trigger_end = false
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()
})
-- 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() < 0.1 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
-- 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"
rain.is_ending = function(dtime)
if rain.last_check + rain.check_interval < os.time() then
rain.last_check = os.time()
if math.random() < 0.3 then
happy_weather.request_to_start("light_rain")
return true
end
end
return texture_name;
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.data = {gradient_data={}}
sl.data.gradient_data.colors = {
{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}
}
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
-- 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
sound_handlers[player:get_player_name()] = set_rain_sound(player)
set_sky_box(player:get_player_name())
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
remove_rain_sound(player)
skylayer.remove_layer(player:get_player_name(), SKYCOLOR_LAYER)
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()
-- Random texture getter
local choice_random_rain_drop_texture = function()
local texture_name
local random_number = math.random()
if random_number > 0.33 then
texture_name = "happy_weather_light_rain_raindrop_1.png"
elseif random_number > 0.66 then
texture_name = "happy_weather_light_rain_raindrop_2.png"
else
texture_name = "happy_weather_light_rain_raindrop_3.png"
end
return texture_name;
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
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, 6),
collisiondetection = true,
collision_removal = true,
vertical = true,
texture = choice_random_rain_drop_texture(),
playername = player:get_player_name()
})
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
local display_rain_particles = function(player)
if hw_utils.is_underwater(player) then
return
end
add_rain_particle(player)
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
rain.in_area = function(position)
if position.y > -10 then
return true
end
return false
end
if weather.reg_weathers.rain == nil then
weather.reg_weathers.rain = {
chance = 15,
clear = rain.clear
}
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
-- 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
rain.start = function()
manual_trigger_start = true
end
rain.stop = function()
manual_trigger_end = true
end
happy_weather.register_weather(rain)

View File

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

194
snow.lua
View File

@ -1,90 +1,122 @@
snow = {}
------------------------------
-- Happy Weather: Light Rain
snow.particles_count = 15
snow.init_done = false
-- License: MIT
-- 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
-- Credits: xeranas
------------------------------
local snow = {}
-- 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 manual_trigger_start then
manual_trigger_start = false
return true
end
return false
end
snow.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.data = {gradient_data={}}
sl.data.gradient_data.colors = {
{r=0, g=0, b=0},
{r=241, g=244, b=249},
{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 texture_name
local random_number = math.random()
if random_number > 0.33 then
texture_name = "happy_weather_light_snow_snowflake_1.png"
elseif random_number > 0.66 then
texture_name = "happy_weather_light_snow_snowflake_2.png"
else
texture_name = "happy_weather_light_snow_snowflake_3.png"
end
return texture_name;
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.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
snow.start = function()
manual_trigger_start = 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
}
snow.stop = function()
manual_trigger_end = true
end
happy_weather.register_weather(snow)

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: 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.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 B

View File

@ -1,37 +1,86 @@
-- turn off lightning mod 'auto mode'
lightning.auto = false
----------------------------------------------------------------
-- Happy Weather: Thunder
thunder = {
next_strike = 0,
min_delay = 3,
max_delay = 12,
}
-- License: MIT
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
-- Credits: xeranas
end)
-- See also: lightning mod for actual lightning effect, sounds.
----------------------------------------------------------------
thunder.clear = function()
rain.clear()
local thunder = {}
thunder.last_check = 0
thunder.check_interval = 100
-- 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)
checked = false
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() < 0.8 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
-- 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
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)
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)

76
utils.lua Normal file
View File

@ -0,0 +1,76 @@
---------------------------------------
-- Happy Weather: Utilities / Helpers
-- License: MIT
-- Credits: xeranas
---------------------------------------
if hw_utils == nil then
hw_utils = {}
end
-- 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

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}
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 = "<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