Compare commits

...

31 Commits

Author SHA1 Message Date
cora 37a9d268ab move weatherdef to top 2021-09-28 03:10:28 +02:00
cora cff2d445f7 don't stop weather twice 2021-09-28 03:10:02 +02:00
cora 336c188ab7 fix weather cycle 2021-09-28 03:09:26 +02:00
cora 2ed3e01179 fix sound never stopping 2021-09-28 03:01:23 +02:00
cora 8f619ab5c3 fix thunder/lightning never stopping 2021-09-27 23:03:28 +02:00
cora e854be9f0f add correct mc conditions for snow 2021-09-27 23:02:49 +02:00
cora 590abfeb0f add correct minecraft conditions for rain 2021-09-27 23:02:17 +02:00
cora f2b399cbbd clean up tick logic 2021-09-27 23:00:46 +02:00
cora 4a582ba5fb take individual weather into account when changing 2021-09-27 22:59:34 +02:00
cora 5ad46bd9a3 Update readme 2021-09-27 03:23:04 +02:00
cora 5e8d8a7e9b add /biome chatcommand (weather priv) 2021-09-27 02:32:43 +02:00
cora f98058a853 support for weathers to change according to pos 2021-09-27 02:31:46 +02:00
cora cd67354725 fix various little errors 2021-09-27 02:31:18 +02:00
cora 53603050c0 add dynamic per player change 2021-09-26 15:48:18 +02:00
cora c6ba41e6e8 tweak particle spawner values 2021-09-26 15:47:35 +02:00
cora d68acad0fd add logging on chat command 2021-09-26 15:46:35 +02:00
cora 015b29aa60 add weather depending on position/biome 2021-09-26 06:08:01 +02:00
cora 7bd4d75c3f add missing translator include 2021-09-26 04:28:44 +02:00
cora 62f950cb3c remove debug code 2021-09-26 03:45:45 +02:00
cora f6fa977664 remove newline 2021-09-26 03:45:05 +02:00
cora c6d9ac9b89 refactor pos_has_weather 2021-09-26 03:44:06 +02:00
cora 2c372684ce readd old chat commands and priv 2021-09-26 03:43:18 +02:00
cora dbbecc94d0 fix typos 2021-09-26 03:27:14 +02:00
cora b5b249b722 fix incompatibility with old mcl_weather 2021-09-26 03:26:41 +02:00
cora bb0f33e21b update readme 2021-09-26 03:07:35 +02:00
cora b8f3c1a175 add nether dust to new weather 2021-09-26 03:07:25 +02:00
cora 01ddbe06bc add thunder to new weather 2021-09-26 03:07:09 +02:00
cora a0fc6f682d add snow to new weather 2021-09-26 03:06:53 +02:00
cora 6f2aae82df add rain to new weather 2021-09-26 03:06:14 +02:00
cora d59a0101f4 add lightning to hard dependencies 2021-09-26 03:05:23 +02:00
cora 6fa1b1dd81 rewrite mcl_weather core mechanics
the old weather mod used individual particles which create lag and
unneccessary globalstep action for known intervals.

this replaces the weather core mechanics with an event based model using
particlespawners.
2021-09-26 03:03:18 +02:00
9 changed files with 770 additions and 679 deletions

View File

@ -1,5 +1,78 @@
`mcl_weather`
=======================
This was largely rewritten to use particlespawners (what were you thinking !?) and an event based code flow.
Weathers included
-----------------------
* rain
* snow
* thunder
Commands
-----------------------
`weather <weather>`, requires `weather_manager` privilege.
Dependencies
-----------------------
Lightning has been made a hard dependency since it is included in Mineclonia.
Adding new weathers:
It's as simple as calling a mcl_weather.register_weather(weatherdef):
Weatherdef
-----------------------
{
light_factor = 1,
-- 0-1
cloudcolor= ,
--hexrgb
sound,
--A sound name (loop)
min_duration ,
-- In seconds
max_duration ,
transitions,
--The chances how likely this weather is to change into another weather.
-- e.g.
-- {
-- [65] = "none",
-- [80] = "rain",
-- [100] = "thunder",
-- }
skylayer= ,
-- a skylayer defintion (see skycolor api)
particlespawners = {},
-- a table of particlespawnerdefinitions
start ,
--A function that is run when the weather is about to start
start_player,
--A function per player that is run when the weather is about to start
clear ,
--A function that is run when the weather is about to clear/change
clear_player , -- function per player that is run on change (first argument is the playername)
at_pos ,
--A function that is run for each player with current position as argument. weather is disabled for the player when it returns false
change_at_pos,
--function that is run for each player with current position as argument. return a weather name and the weather transitions into the supplied weather for that player
}
This was the original README.md from mcl2_weather:
Weather mod for MineClone 2. Forked from the `weather_pack` mod by xeranas.
Weathers included

View File

@ -1,17 +1,385 @@
local modpath = minetest.get_modpath("mcl_weather")
local S = minetest.get_translator("mcl_weather")
mcl_weather = {}
-- If not located then embeded skycolor mod version will be loaded.
if minetest.get_modpath("skycolor") == nil then
dofile(modpath.."/skycolor.lua")
end
mcl_weather.allow_abm = true
mcl_weather.current = "none"
mcl_weather.state = "none"
mcl_weather.current_endtime = 0
mcl_weather.next = nil
mcl_weather.registered_weathers = {}
dofile(modpath.."/weather_core.lua")
mcl_weather.min_duration = 600
mcl_weather.max_duration = 9000
local players = {}
players.particlespawners={}
players.soundhandler={}
players.weatheractive = {}
players.weather = {}
local interval=1
local weathercycle_active=false
function mcl_weather.register_weather(name,def)
mcl_weather.registered_weathers[name] = def
end
mcl_weather.register_weather("none",{
min_duration = mcl_weather.min_duration,
max_duration = mcl_weather.max_duration,
transitions = {
[50] = "rain",
[100] = "snow"
}
})
local modpath=minetest.get_modpath(minetest.get_current_modname())
dofile(modpath.."/skycolor.lua")
dofile(modpath.."/snow.lua")
dofile(modpath.."/rain.lua")
dofile(modpath.."/nether_dust.lua")
if minetest.get_modpath("lightning") ~= nil then
dofile(modpath.."/thunder.lua")
function mcl_weather.get_weatherdef(name)
if mcl_weather.registered_weathers[name] then
return mcl_weather.registered_weathers[name]
end
return mcl_weather.registered_weathers["none"]
end
function mcl_weather.get_weather()
return mcl_weather.current
end
function mcl_weather.get_current_light_factor()
local lf = mcl_weather.get_weatherdef(mcl_weather.current).light_factor
if mcl_weather.current == "none" or not lf then
return nil
else
return lf
end
end
-- Returns true if pos is outdoor.
-- Outdoor is defined as any node in the Overworld under open sky.
-- FIXME: Nodes below glass also count as “outdoor”, this should not be the case.
function mcl_weather.is_outdoor(pos)
local cpos = {x=pos.x, y=pos.y+1, z=pos.z}
local dim = mcl_worlds.pos_to_dimension(cpos)
if minetest.get_node_light(cpos, 0.5) == 15 and dim == "overworld" then
return true
end
return false
end
-- checks if player is undewater. This is needed in order to
-- turn off weather particles generation.
function mcl_weather.is_underwater(player)
local ppos=player:get_pos()
local offset = player:get_eye_offset()
local player_eye_pos = {x = ppos.x + offset.x,
y = ppos.y + offset.y + 1.5,
z = ppos.z + offset.z}
local node_level = minetest.get_node_level(player_eye_pos)
if node_level == 8 or node_level == 7 then
return true
end
return false
end
function mcl_weather.player_has_weather(player)
local pos=player:get_pos()
if mcl_weather.is_outdoor(pos) and not mcl_weather.is_underwater(player) then
return true
end
return false
end
function mcl_weather.add_sound(name,sound)
players.soundhandler[name] = minetest.sound_play(sound, {
to_player = name,
loop = true,
})
end
function mcl_weather.remove_sound(name)
if players.soundhandler[name] then
minetest.sound_fade(players.soundhandler[name], -0.5, 0.0)
end
end
function mcl_weather.add_particlespawners_player(name,spawners)
if not spawners then return end
local player = minetest.get_player_by_name(name)
if not player then return end
if players.particlespawners[name] ~= nil then return end
players.particlespawners[name]={}
for k,v in ipairs(spawners) do
v.playername=name
v.attached=player
table.insert(players.particlespawners[name],minetest.add_particlespawner(v))
end
end
function mcl_weather.delete_particlespawners_player(name)
if players.particlespawners[name] == nil then
players.particlespawners[name]={}
end
for k,v in ipairs(players.particlespawners[name]) do
minetest.delete_particlespawner(v)
end
players.particlespawners[name]=nil
end
local function doplayers(func,players)
players = players or minetest.get_connected_players()
for k,v in ipairs(players) do
func(v:get_player_name(),v)
end
end
function mcl_weather.get_next_weather()
local current=mcl_weather.get_weatherdef(mcl_weather.current)
local random_roll = math.random(0,100)
local new_weather
if current and current.transitions then
for v, weather in pairs(current.transitions) do
if random_roll < v then
new_weather=weather
break
end
end
end
if not new_weather then new_weather = "none" end
return new_weather
end
function mcl_weather.start_weather_player(name,def)
local player=minetest.get_player_by_name(name)
mcl_weather.add_particlespawners_player(name,def.particlespawners)
if def.skycolor then
local player=minetest.get_player_by_name(name)
player:set_clouds({color=def.skycolor})
end
if def.sound then
mcl_weather.add_sound(name,def.sound)
end
players.weather[name]=def
players.weatheractive[name] = true
end
function mcl_weather.stop_weather_player(name,def)
mcl_weather.delete_particlespawners_player(name)
if def.skycolor then
local player=minetest.get_player_by_name(name)
player:set_clouds({color="#FFF0F0E5"})
end
mcl_weather.remove_sound(name,def.sound)
end
function mcl_weather.start_weather(def)
if def.start then def.start() end
if def.skylayer then
mcl_weather.skycolor.add_layer("weather",def.skylayer)
mcl_weather.skycolor.update_sky_color()
end
doplayers(function(name)
if def.start_player == nil or not def.start_player(name) then
mcl_weather.start_weather_player(name,def)
players.weather[name]=mcl_weather.current
end
end)
end
function mcl_weather.stop_weather(def)
if def.clear then def.clear() end
if def.clear_player then doplayers(def.clear_player) end
doplayers(function(name)
mcl_weather.stop_weather_player(name,def)
end)
if def.skylayer then
mcl_weather.skycolor.remove_layer("weather")
mcl_weather.skycolor.force_update=true
mcl_weather.skycolor.update_sky_color()
end
end
function mcl_weather.change(new_weather,force)
if new_weather == mcl_weather.current then return end
local def=mcl_weather.get_weatherdef(new_weather)
local old=mcl_weather.get_weatherdef(mcl_weather.current)
if not def then return end
if force or minetest.settings:get_bool("mcl_doWeatherCycle") then
mcl_weather.stop_weather(old)
mcl_weather.start_weather(def)
mcl_weather.current = new_weather
mcl_weather.state = new_weather
end
end
function mcl_weather.seasonal_change()
local new=mcl_weather.get_next_weather()
local def=mcl_weather.get_weatherdef(new)
local duration = math.random(def.min_duration,def.max_duration)
mcl_weather.current_endtime = os.time() + duration
minetest.log("action", "[mcl_weather] Weather changed from " .. mcl_weather.current .. " to " .. new .. " Duration: "..duration)
mcl_weather.change(new)
minetest.after(duration,mcl_weather.seasonal_change)
end
minetest.register_on_mods_loaded(function()
minetest.after(5,mcl_weather.seasonal_change)
end)
function mcl_weather.change_player(name,new)
if players.weather[name] ~= new then
local nd = mcl_weather.get_weatherdef(new)
local od=mcl_weather.get_weatherdef(mcl_weather.current)
if players.weather[name] ~= nil then
minetest.log("action", "[mcl_weather] Weather for ".. name .." changed from "..players.weather[name].. " to "..new)
od=mcl_weather.get_weatherdef(players.weather[name])
else
minetest.log("action", "[mcl_weather] Weather for ".. name .." changed from "..mcl_weather.current.. " to "..new)
end
if nd then
mcl_weather.stop_weather_player(name,od)
mcl_weather.start_weather_player(name,nd)
players.weather[name] = new
end
end
end
function mcl_weather.tick()
doplayers(function(name,player)
local pos=player:get_pos()
local cdef=mcl_weather.get_weatherdef(mcl_weather.current)
if type(cdef.change_at_pos) == "function" then --switch to returned weather in at_pos conditions
local cap=cdef.change_at_pos(pos)
if cap then
mcl_weather.change_player(name,cap)
else
mcl_weather.change_player(name,mcl_weather.current)
end
end
if players.weatheractive[name] then --turn off weather indoows/underwater
if not mcl_weather.player_has_weather(player) then
mcl_weather.stop_weather_player(name,cdef)
players.weatheractive[name] = false
end
if type(cdef.at_pos) == "function" and cdef.at_pos(pos) then
mcl_weather.stop_weather_player(name,cdef)
players.weatheractive[name] = false
end
else
if mcl_weather.player_has_weather(player) then
mcl_weather.start_weather_player(name,cdef)
players.weather[name]=mcl_weather.current
players.weatheractive[name] = true
end
if type(cdef.at_pos) == "function" and not cdef.at_pos(pos) then
mcl_weather.start_weather_player(name,cdef)
players.weatheractive[name] = false
end
end
end)
minetest.after(interval,mcl_weather.tick)
end
mcl_weather.tick()
mcl_worlds.register_on_dimension_change(function(player, dimension)
if mcl_worlds.has_weather(player:get_pos()) then
mcl_weather.start_weather_player(player:get_player_name(),mcl_weather.get_weatherdef(mcl_weather.current))
else
mcl_weather.stop_weather_player(player:get_player_name(),mcl_weather.get_weatherdef(mcl_weather.current))
end
end)
minetest.register_on_joinplayer(function(player)
mcl_weather.start_weather_player(player:get_player_name(),mcl_weather.get_weatherdef(mcl_weather.current))
end)
minetest.register_on_leaveplayer(function(player)
cla_weather.stop_weather_player(player:get_player_name())
end)
minetest.register_privilege("weather_manager", {
description = S("Gives ability to control weather"),
give_to_singleplayer = false
})
-- Weather command definition. Set
minetest.register_chatcommand("weather", {
params = "(clear | rain | snow | thunder) [<duration>]",
description = S("Changes the weather to the specified parameter."),
privs = {weather_manager = true},
func = function(name, param)
if (param == "") then
return false, S("Error: No weather specified.")
end
local player=minetest.get_player_by_name(name)
local new_weather, duration
local parse1, parse2 = string.match(param, "(%w+) ?(%d*)")
if parse1 then
if parse1 == "clear" then
new_weather = "none"
else
new_weather = parse1
end
else
return false, S("Error: Invalid parameters.")
end
if parse2 then
if type(tonumber(parse2)) == "number" then
duration = tonumber(parse2)
if duration < 1 then
return false, S("Error: Duration can't be less than 1 second.")
end
end
end
local def=mcl_weather.get_weatherdef(param)
local old_weather=mcl_weather.current
if def then
if duration then
def.min_duration=duration
def.max_duration=duration
end
mcl_weather.change(param,true)
minetest.log("action", "[mcl_weather] " .. name .. " changed the weather from " .. old_weather .. " to " .. param)
return true
end
return false, S("Error: Invalid weather specified. Use “clear”, “rain”, “snow” or “thunder”.")
end
})
minetest.register_chatcommand("biome", {
params = "",
description = S("Gets current biome"),
privs = {weather_manager = true},
func = function(name)
local player=minetest.get_player_by_name(name)
local biome=minetest.get_biome_data(player:get_pos())
local bname = minetest.get_biome_name(biome.biome)
minetest.chat_send_player(name,bname.. " " ..dump(biome))
end
})
minetest.register_chatcommand("toggledownfall", {
params = "",
description = S("Toggles between clear weather and weather with downfall (randomly rain, thunderstorm or snow)"),
privs = {weather_manager = true},
func = function(name, param)
-- Currently rain/thunder/snow: Set weather to clear
if mcl_weather.state ~= "none" then
mcl_weather.change("none",true)
-- Currently clear: Set weather randomly to rain/thunder/snow
else
local new = { "rain", "thunder", "snow" }
local r = math.random(1, #new)
return mcl_weather.change(new[r],true)
end
end
})

View File

@ -1,4 +1,3 @@
name = mcl_weather
description = Weather and sky handling: Rain, snow, thunderstorm, End and Nether ambience
depends = mcl_init, mcl_worlds
optional_depends = lightning
depends = mcl_init, mcl_worlds, lightning

View File

@ -1,36 +1,70 @@
mcl_weather.nether_dust = {}
mcl_weather.nether_dust.particles_count = 99
mcl_weather.nether_dust.particlespawners = {}
-- calculates coordinates and draw particles for Nether dust
mcl_weather.nether_dust.add_dust_particles = function(player)
for i=mcl_weather.nether_dust.particles_count, 1,-1 do
local rpx, rpy, rpz = mcl_weather.get_random_pos_by_player_look_dir(player)
minetest.add_particle({
pos = {x = rpx, y = rpy - math.random(6, 18), z = rpz},
velocity = {x = math.random(-30,30)*0.01, y = math.random(-15,15)*0.01, z = math.random(-30,30)*0.01},
acceleration = {x = math.random(-50,50)*0.02, y = math.random(-20,20)*0.02, z = math.random(-50,50)*0.02},
expirationtime = 3,
size = math.random(6,20)*0.01,
local psdef= {
amount = 99,
time = 0,
minpos = vector.new(-25,0,-25),
maxpos =vector.new(25,4,25),
minvel = vector.new(-0.3,-0.15,-1),
maxvel = vector.new(0.3,0.15,0.3),
minacc = vector.new(-1,-0.4,-1),
maxacc = vector.new(1,0.4,1),
minexptime = 1,
maxexptime = 1,
minsize = 0.1,
maxsize = 2,
collisiondetection = false,
collision_removal = false,
object_collision = false,
vertical = false,
glow = math.random(0,minetest.LIGHT_MAX),
texture = "mcl_particles_nether_dust"..tostring(i%3+1)..".png",
playername = player:get_player_name()
})
glow = math.random(0,minetest.LIGHT_MAX)
}
local function check_player(player)
local name=player:get_player_name(name)
if mcl_worlds.has_dust(player:get_pos()) and not mcl_weather.nether_dust.particlespawners[name] then
return true
end
end
local timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer < 0.7 then return end
timer = 0
for _, player in ipairs(minetest.get_connected_players()) do
if not mcl_worlds.has_dust(player:get_pos()) then
return false
mcl_weather.nether_dust.add_particlespawners = function(player)
if not check_player(player) then return end
local name=player:get_player_name(name)
mcl_weather.nether_dust.particlespawners[name]={}
psdef.playername = name
psdef.attached = player
for i=1,3 do
psdef.texture="mcl_particles_nether_dust"..i..".png"
mcl_weather.nether_dust.particlespawners[name][i]=minetest.add_particlespawner(psdef)
end
mcl_weather.nether_dust.add_dust_particles(player)
end
mcl_weather.nether_dust.delete_particlespawners = function(player)
local name=player:get_player_name(name)
if mcl_weather.nether_dust.particlespawners[name] then
for i=1,3 do
minetest.delete_particlespawner(mcl_weather.nether_dust.particlespawners[name][i])
end
mcl_weather.nether_dust.particlespawners[name]=nil
end
end
mcl_worlds.register_on_dimension_change(function(player, dimension)
if dimension == "nether" then
if check_player(player) then
mcl_weather.nether_dust.add_particlespawners(player)
end
else
mcl_weather.nether_dust.delete_particlespawners(player)
end
end)
minetest.register_on_joinplayer(function(player)
if check_player(player) then
mcl_weather.nether_dust.add_particlespawners(player)
end
end)
minetest.register_on_leaveplayer(function(player)
mcl_weather.nether_dust.delete_particlespawners(player)
end)

View File

@ -1,200 +1,105 @@
local PARTICLES_COUNT_RAIN = 30
local PARTICLES_COUNT_THUNDER = 45
mcl_weather.rain = {
-- max rain particles created at time
particles_count = PARTICLES_COUNT_RAIN,
-- flag to turn on/off extinguish fire for rain
extinguish_fire = true,
-- flag useful when mixing weathers
raining = false,
-- keeping last timeofday value (rounded).
-- Defaulted to non-existing value for initial comparing.
sky_last_update = -1,
init_done = false,
raining = false
}
mcl_weather.raining = false
mcl_weather.rain.sound_handler = function(player)
return minetest.sound_play("weather_rain", {
to_player = player:get_player_name(),
loop = true,
})
end
-- set skybox based on time (uses skycolor api)
mcl_weather.rain.set_sky_box = function()
if mcl_weather.state == "rain" then
mcl_weather.skycolor.add_layer(
"weather-pack-rain-sky",
{{r=0, g=0, b=0},
mcl_weather.register_weather("rain",{
light_factor = 0.6,
cloudcolor= "#5D5D5FE8" ,
sound = "weather_rain",
-- 10min - 20min
min_duration = 600,
max_duration = 1200,
transitions = {
[70] = "none",
[100] = "thunder",
},
skylayer= {{r=0, g=0, b=0},
{r=85, g=86, b=98},
{r=135, g=135, b=151},
{r=85, g=86, b=98},
{r=0, g=0, b=0}})
mcl_weather.skycolor.active = true
for _, player in ipairs(minetest.get_connected_players()) do
player:set_clouds({color="#5D5D5FE8"})
end
end
end
-- creating manually parctiles instead of particles spawner because of easier to control
-- spawn position.
mcl_weather.rain.add_rain_particles = function(player)
mcl_weather.rain.last_rp_count = 0
for i=mcl_weather.rain.particles_count, 1,-1 do
local random_pos_x, random_pos_y, random_pos_z = mcl_weather.get_random_pos_by_player_look_dir(player)
if mcl_weather.is_outdoor({x=random_pos_x, y=random_pos_y, z=random_pos_z}) then
mcl_weather.rain.last_rp_count = mcl_weather.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 = 1.0,
size = math.random(0.5, 3),
{r=0, g=0, b=0}},
particlespawners = {
{
amount = 800,
time = 0,
minpos = vector.new(-10,10,-10),
maxpos =vector.new(10,15,10),
minvel = vector.new(0,-8,0),
maxvel = vector.new(0,-12,0),
minacc = vector.new(0,-20,0),
maxacc = vector.new(0,-30,0),
minexptime = 1,
maxexptime = 2,
minsize = 0.5,
maxsize = 5,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
texture = mcl_weather.rain.get_texture(),
playername = player:get_player_name()
glow = 1,
texture="weather_pack_rain_raindrop_1.png"
},
{
amount = 200,
time = 0,
minpos = vector.new(-15,5,-15),
maxpos =vector.new(15,15,15),
minvel = vector.new(0,-8,0),
maxvel = vector.new(0,-12,0),
minacc = vector.new(0,-20,0),
maxacc = vector.new(0,-40,0),
minexptime = 1,
maxexptime = 1,
minsize = 0.5,
maxsize = 3,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_rain_raindrop_2.png"
},
{
amount = 200,
time = 0,
minpos = vector.new(-5,0,-5),
maxpos = vector.new(5,0.8,5),
minvel = vector.new(-4, 5,-4),
maxvel = vector.new(4,10,4),
minacc = vector.new(0,10,0),
maxacc = vector.new(0,25,0),
minexptime = 0.01,
maxexptime = 0.04,
minsize = 0.5,
maxsize = 3,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
texture = "weather_pack_rain_raindrop_2.png",
glow = 0
}
},
start = function() mcl_weather.rain.raining = true end,
clear = function() mcl_weather.rain.raining = false end,
at_pos = function(pos)
local biome=minetest.get_biome_data(pos)
if mcl_worlds.has_weather(pos) and biome.heat > 95 then
return true
end
end,
change_at_pos=function(pos)
local biome=minetest.get_biome_data(pos)
if mcl_worlds.has_weather(pos) and ( biome.heat < 15 or pos.y > 64 ) then
return "snow"
end
end
})
end
end
end
-- Simple random texture getter
mcl_weather.rain.get_texture = function()
local texture_name
local random_number = math.random()
if random_number > 0.33 then
texture_name = "weather_pack_rain_raindrop_1.png"
elseif random_number > 0.66 then
texture_name = "weather_pack_rain_raindrop_2.png"
else
texture_name = "weather_pack_rain_raindrop_3.png"
end
return texture_name;
end
-- register player for rain weather.
-- basically needs for origin sky reference and rain sound controls.
mcl_weather.rain.add_player = function(player)
if mcl_weather.players[player:get_player_name()] == nil then
local player_meta = {}
player_meta.origin_sky = {player:get_sky()}
mcl_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.
mcl_weather.rain.remove_player = function(player)
local player_meta = mcl_weather.players[player:get_player_name()]
if player_meta ~= nil and player_meta.origin_sky ~= nil then
player:set_clouds({color="#FFF0F0E5"})
mcl_weather.players[player:get_player_name()] = nil
end
end
mcl_worlds.register_on_dimension_change(function(player, dimension)
if dimension ~= "overworld" and dimension ~= "void" then
mcl_weather.rain.remove_sound(player)
mcl_weather.rain.remove_player(player)
elseif dimension == "overworld" then
mcl_weather.rain.update_sound(player)
if mcl_weather.rain.raining then
mcl_weather.rain.add_rain_particles(player)
mcl_weather.rain.add_player(player)
end
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.
mcl_weather.rain.update_sound = function(player)
local player_meta = mcl_weather.players[player:get_player_name()]
if player_meta ~= nil then
if player_meta.sound_updated ~= nil and player_meta.sound_updated + 5 > minetest.get_gametime() then
return false
end
if player_meta.sound_handler ~= nil then
if mcl_weather.rain.last_rp_count == 0 then
minetest.sound_fade(player_meta.sound_handler, -0.5, 0.0)
player_meta.sound_handler = nil
end
elseif mcl_weather.rain.last_rp_count > 0 then
player_meta.sound_handler = mcl_weather.rain.sound_handler(player)
end
player_meta.sound_updated = minetest.get_gametime()
end
end
-- rain sound removed from player.
mcl_weather.rain.remove_sound = function(player)
local player_meta = mcl_weather.players[player:get_player_name()]
if player_meta ~= nil and player_meta.sound_handler ~= nil then
minetest.sound_fade(player_meta.sound_handler, -0.5, 0.0)
player_meta.sound_handler = nil
player_meta.sound_updated = nil
end
end
-- callback function for removing rain
mcl_weather.rain.clear = function()
mcl_weather.rain.raining = false
mcl_weather.rain.sky_last_update = -1
mcl_weather.rain.init_done = false
mcl_weather.rain.set_particles_mode("rain")
mcl_weather.skycolor.remove_layer("weather-pack-rain-sky")
for _, player in ipairs(minetest.get_connected_players()) do
mcl_weather.rain.remove_sound(player)
mcl_weather.rain.remove_player(player)
end
end
minetest.register_globalstep(function(dtime)
if mcl_weather.state ~= "rain" then
return false
end
mcl_weather.rain.make_weather()
end)
mcl_weather.rain.make_weather = function()
if mcl_weather.rain.init_done == false then
mcl_weather.rain.raining = true
mcl_weather.rain.set_sky_box()
mcl_weather.rain.set_particles_mode(mcl_weather.mode)
mcl_weather.rain.init_done = true
end
for _, player in ipairs(minetest.get_connected_players()) do
if (mcl_weather.is_underwater(player) or not mcl_worlds.has_weather(player:get_pos())) then
mcl_weather.rain.remove_sound(player)
return false
end
mcl_weather.rain.add_player(player)
mcl_weather.rain.add_rain_particles(player)
mcl_weather.rain.update_sound(player)
end
end
-- Switch the number of raindrops: "thunder" for many raindrops, otherwise for normal raindrops
mcl_weather.rain.set_particles_mode = function(mode)
if mode == "thunder" then
mcl_weather.rain.particles_count = PARTICLES_COUNT_THUNDER
else
mcl_weather.rain.particles_count = PARTICLES_COUNT_RAIN
end
end
if mcl_weather.allow_abm then
-- ABM for extinguish fire
@ -264,18 +169,3 @@ if mcl_weather.allow_abm then
end
})
end
if mcl_weather.reg_weathers.rain == nil then
mcl_weather.reg_weathers.rain = {
clear = mcl_weather.rain.clear,
light_factor = 0.6,
-- 10min - 20min
min_duration = 600,
max_duration = 1200,
transitions = {
[65] = "none",
[70] = "snow",
[100] = "thunder",
}
}
end

View File

@ -70,7 +70,7 @@ mcl_weather.skycolor = {
else
arg = ratio
end
player:override_day_night_ratio(arg)
if arg and arg >= 0 and arg <= 1 then player:override_day_night_ratio(arg) end
end,
-- Update sky color. If players not specified update sky for all players.

View File

@ -1,89 +1,7 @@
mcl_weather.snow = {}
mcl_weather.snow.particles_count = 15
mcl_weather.snow.init_done = false
-- calculates coordinates and draw particles for snow weather
mcl_weather.snow.add_snow_particles = function(player)
mcl_weather.rain.last_rp_count = 0
for i=mcl_weather.snow.particles_count, 1,-1 do
local random_pos_x, random_pos_y, random_pos_z = mcl_weather.get_random_pos_by_player_look_dir(player)
random_pos_y = math.random() + math.random(player:get_pos().y - 1, player:get_pos().y + 7)
if minetest.get_node_light({x=random_pos_x, y=random_pos_y, z=random_pos_z}, 0.5) == 15 then
mcl_weather.rain.last_rp_count = mcl_weather.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(-100,100)*0.001, y = math.random(-300,-100)*0.004, z = math.random(-100,100)*0.001},
acceleration = {x = 0, y=0, z = 0},
expirationtime = 8.0,
size = 1,
collisiondetection = true,
collision_removal = true,
object_collision = false,
vertical = false,
texture = mcl_weather.snow.get_texture(),
playername = player:get_player_name()
})
end
end
end
mcl_weather.snow.set_sky_box = function()
mcl_weather.skycolor.add_layer(
"weather-pack-snow-sky",
{{r=0, g=0, b=0},
{r=85, g=86, b=86},
{r=135, g=135, b=135},
{r=85, g=86, b=86},
{r=0, g=0, b=0}})
mcl_weather.skycolor.active = true
for _, player in ipairs(minetest.get_connected_players()) do
player:set_clouds({color="#ADADADE8"})
end
mcl_weather.skycolor.active = true
end
mcl_weather.snow.clear = function()
mcl_weather.skycolor.remove_layer("weather-pack-snow-sky")
mcl_weather.snow.init_done = false
end
-- Simple random texture getter
mcl_weather.snow.get_texture = function()
return "weather_pack_snow_snowflake"..math.random(1,2)..".png"
end
local timer = 0
minetest.register_globalstep(function(dtime)
if mcl_weather.state ~= "snow" then
return false
end
timer = timer + dtime;
if timer >= 0.5 then
timer = 0
else
return
end
if mcl_weather.snow.init_done == false then
mcl_weather.snow.set_sky_box()
mcl_weather.snow.init_done = true
end
for _, player in ipairs(minetest.get_connected_players()) do
if (mcl_weather.is_underwater(player) or not mcl_worlds.has_weather(player:get_pos())) then
return false
end
mcl_weather.snow.add_snow_particles(player)
end
end)
-- register snow weather
if mcl_weather.reg_weathers.snow == nil then
mcl_weather.reg_weathers.snow = {
clear = mcl_weather.snow.clear,
mcl_weather.register_weather("snow",{
light_factor = 0.6,
cloudcolor= "#ADADADE8" ,
sound = "weather_rain",
-- 10min - 20min
min_duration = 600,
max_duration = 1200,
@ -91,6 +9,57 @@ if mcl_weather.reg_weathers.snow == nil then
[65] = "none",
[80] = "rain",
[100] = "thunder",
},
skylayer= {{r=0, g=0, b=0},
{r=85, g=86, b=86},
{r=135, g=135, b=135},
{r=85, g=86, b=86},
{r=0, g=0, b=0}},
particlespawners = {
{
amount = 99,
time = 0,
minpos = vector.new(-15,-5,-15),
maxpos =vector.new(15,10,15),
minvel = vector.new(0,-1,0),
maxvel = vector.new(0,-4,0),
minacc = vector.new(0,-1,0),
maxacc = vector.new(0,-4,0),
minexptime = 2,
maxexptime = 5,
minsize = 0.5,
maxsize = 5,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_snow_snowflake1.png"
},{
amount = 30,
time = 0,
minpos = vector.new(-15,5,-15),
maxpos =vector.new(15,10,15),
minvel = vector.new(0,-1,0),
maxvel = vector.new(0,-5,0),
minacc = vector.new(0,-1,0),
maxacc = vector.new(0,-4,0),
minexptime = 0.1,
maxexptime = 0.5,
minsize = 0.5,
maxsize = 5,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_snow_snowflake2.png"
}
}
},
change_at_pos = function(pos)
local biome=minetest.get_biome_data(pos)
if pos.y > 50 or (mcl_worlds.has_weather(pos) and biome.heat > 15 ) then
return "rain"
end
end
})

View File

@ -1,61 +1,110 @@
-- turn off lightning mod 'auto mode'
mcl_weather.thunder = {
min_delay = 3,
max_delay = 12
}
lightning.auto=false
mcl_weather.thunder = {
next_strike = 0,
min_delay = 3,
max_delay = 12,
init_done = false,
}
local thunderactive=false
minetest.register_globalstep(function(dtime)
if mcl_weather.get_weather() ~= "thunder" then
return false
end
mcl_weather.rain.set_particles_mode("thunder")
mcl_weather.rain.make_weather()
if mcl_weather.thunder.init_done == false then
mcl_weather.skycolor.add_layer(
"weather-pack-thunder-sky",
{{r=0, g=0, b=0},
{r=40, g=40, b=40},
{r=85, g=86, b=86},
{r=40, g=40, b=40},
{r=0, g=0, b=0}})
mcl_weather.skycolor.active = true
for _, player in ipairs(minetest.get_connected_players()) do
player:set_clouds({color="#3D3D3FE8"})
end
mcl_weather.thunder.init_done = true
end
if (mcl_weather.thunder.next_strike <= minetest.get_gametime()) then
lightning.strike()
local function do_thunder(start)
if (start and not thunderactive) or mcl_weather.current == "thunder" then
local delay = math.random(mcl_weather.thunder.min_delay, mcl_weather.thunder.max_delay)
mcl_weather.thunder.next_strike = minetest.get_gametime() + delay
minetest.after(delay,do_thunder)
thunderactive=true
else
thunderactive=false
end
minetest.chat_send_all("thunder "..mcl_weather.current)
lightning.strike()
end
end)
mcl_weather.thunder.clear = function()
mcl_weather.rain.clear()
mcl_weather.skycolor.remove_layer("weather-pack-thunder-sky")
mcl_weather.skycolor.remove_layer("lightning")
mcl_weather.thunder.init_done = false
end
-- register thunderstorm weather
if mcl_weather.reg_weathers.thunder == nil then
mcl_weather.reg_weathers.thunder = {
clear = mcl_weather.thunder.clear,
light_factor = 0.33333,
mcl_weather.register_weather("thunder",{
light_factor = 0.33,
cloudcolor= "#3D3D3FE8" ,
sound = "weather_rain",
-- 10min - 20min
min_duration = 600,
max_duration = 1200,
transitions = {
[100] = "rain",
}
[100] = "rain"
},
skylayer= {{r=0, g=0, b=0},
{r=40, g=40, b=40},
{r=85, g=86, b=86},
{r=40, g=40, b=40},
{r=0, g=0, b=0}},
particlespawners = {
{
amount = 1200,
time = 0,
minpos = vector.new(-15,5,-15),
maxpos =vector.new(15,10,15),
minvel = vector.new(0,-8,0),
maxvel = vector.new(0,-12,0),
minacc = vector.new(0,-20,0),
maxacc = vector.new(0,-30,0),
minexptime = 1,
maxexptime = 1,
minsize = 2,
maxsize = 7,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_rain_raindrop_1.png"
},
{
amount = 800,
time = 0,
minpos = vector.new(-15,5,-15),
maxpos =vector.new(15,15,15),
minvel = vector.new(0,-8,0),
maxvel = vector.new(0,-12,0),
minacc = vector.new(0,-20,0),
maxacc = vector.new(0,-40,0),
minexptime = 1,
maxexptime = 1,
minsize = 1,
maxsize = 5,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_rain_raindrop_2.png"
},
{
amount = 350,
time = 0,
minpos = vector.new(-5,0,-5),
maxpos = vector.new(5,2,5),
minvel = vector.new(-4, 5,-4),
maxvel = vector.new(4,10,4),
minacc = vector.new(0,10,0),
maxacc = vector.new(0,30,0),
minexptime = 0.01,
maxexptime = 0.04,
minsize = 1,
maxsize = 3,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
texture = "weather_pack_rain_raindrop_2.png",
glow = 1
}
},
start = function()
do_thunder(true)
end,
clear = function()
mcl_weather.skycolor.remove_layer("lightning")
end,
at_pos = function(pos)
local biome=minetest.get_biome_data(pos)
if not mcl_worlds.has_weather(pos) and biome.heat > 95 then
return true
end
end
})

View File

@ -1,291 +0,0 @@
local S = minetest.get_translator("mcl_weather")
-- weather states, 'none' is default, other states depends from active mods
mcl_weather.state = "none"
-- player list for saving player meta info
mcl_weather.players = {}
-- default weather check interval for global step
mcl_weather.check_interval = 5
-- weather min duration
mcl_weather.min_duration = 600
-- weather max duration
mcl_weather.max_duration = 9000
-- weather calculated end time
mcl_weather.end_time = nil
-- registered weathers
mcl_weather.reg_weathers = {}
-- global flag to disable/enable ABM logic.
mcl_weather.allow_abm = true
mcl_weather.reg_weathers["none"] = {
min_duration = mcl_weather.min_duration,
max_duration = mcl_weather.max_duration,
light_factor = nil,
transitions = {
[50] = "rain",
[100] = "snow",
},
clear = function() end,
}
local storage = minetest.get_mod_storage()
-- Save weather into mod storage, so it can be loaded after restarting the server
local save_weather = function()
storage:set_string("mcl_weather_state", mcl_weather.state)
storage:set_int("mcl_weather_end_time", mcl_weather.end_time)
minetest.log("verbose", "[mcl_weather] Weather data saved: state="..mcl_weather.state.." end_time="..mcl_weather.end_time)
end
minetest.register_on_shutdown(save_weather)
mcl_weather.get_rand_end_time = function(min_duration, max_duration)
local r
if min_duration ~= nil and max_duration ~= nil then
r = math.random(min_duration, max_duration)
else
r = math.random(mcl_weather.min_duration, mcl_weather.max_duration)
end
return minetest.get_gametime() + r
end
mcl_weather.get_current_light_factor = function()
if mcl_weather.state == "none" then
return nil
else
return mcl_weather.reg_weathers[mcl_weather.state].light_factor
end
end
-- Returns true if pos is outdoor.
-- Outdoor is defined as any node in the Overworld under open sky.
-- FIXME: Nodes below glass also count as “outdoor”, this should not be the case.
mcl_weather.is_outdoor = function(pos)
local cpos = {x=pos.x, y=pos.y+1, z=pos.z}
local dim = mcl_worlds.pos_to_dimension(cpos)
if minetest.get_node_light(cpos, 0.5) == 15 and dim == "overworld" then
return true
end
return false
end
-- checks if player is undewater. This is needed in order to
-- turn off weather particles generation.
mcl_weather.is_underwater = function(player)
local ppos = player:get_pos()
local offset = player:get_eye_offset()
local player_eye_pos = {x = ppos.x + offset.x,
y = ppos.y + offset.y + 1.5,
z = ppos.z + offset.z}
local node_level = minetest.get_node_level(player_eye_pos)
if node_level == 8 or node_level == 7 then
return true
end
return false
end
-- trying to locate position for particles by player look direction for performance reason.
-- it is costly to generate many particles around player so goal is focus mainly on front view.
mcl_weather.get_random_pos_by_player_look_dir = function(player)
local look_dir = player:get_look_dir()
local player_pos = player:get_pos()
local random_pos_x = 0
local random_pos_y = 0
local random_pos_z = 0
if look_dir.x > 0 then
if look_dir.z > 0 then
random_pos_x = math.random() + math.random(player_pos.x - 2.5, player_pos.x + 5)
random_pos_z = math.random() + math.random(player_pos.z - 2.5, player_pos.z + 5)
else
random_pos_x = math.random() + math.random(player_pos.x - 2.5, player_pos.x + 5)
random_pos_z = math.random() + math.random(player_pos.z - 5, player_pos.z + 2.5)
end
else
if look_dir.z > 0 then
random_pos_x = math.random() + math.random(player_pos.x - 5, player_pos.x + 2.5)
random_pos_z = math.random() + math.random(player_pos.z - 2.5, player_pos.z + 5)
else
random_pos_x = math.random() + math.random(player_pos.x - 5, player_pos.x + 2.5)
random_pos_z = math.random() + math.random(player_pos.z - 5, player_pos.z + 2.5)
end
end
random_pos_y = math.random() + math.random(player_pos.y + 10, player_pos.y + 15)
return random_pos_x, random_pos_y, random_pos_z
end
local t, wci = 0, mcl_weather.check_interval
minetest.register_globalstep(function(dtime)
t = t + dtime
if t < wci then return end
t = 0
if mcl_weather.end_time == nil then
mcl_weather.end_time = mcl_weather.get_rand_end_time()
end
-- recalculate weather
if mcl_weather.end_time <= minetest.get_gametime() then
local changeWeather = minetest.settings:get_bool("mcl_doWeatherCycle")
if changeWeather == nil then
changeWeather = true
end
if changeWeather then
mcl_weather.set_random_weather(mcl_weather.state, mcl_weather.reg_weathers[mcl_weather.state])
else
mcl_weather.end_time = mcl_weather.get_rand_end_time()
end
end
end)
-- Sets random weather (which could be 'none' (no weather)).
mcl_weather.set_random_weather = function(weather_name, weather_meta)
if weather_meta == nil then return end
local transitions = weather_meta.transitions
local random_roll = math.random(0,100)
local new_weather
for v, weather in pairs(transitions) do
if random_roll < v then
new_weather = weather
break
end
end
if new_weather then
mcl_weather.change_weather(new_weather)
end
end
-- Change weather to new_weather.
-- * explicit_end_time is OPTIONAL. If specified, explicitly set the
-- gametime (minetest.get_gametime) in which the weather ends.
-- * changer is OPTIONAL, for logging purposes.
mcl_weather.change_weather = function(new_weather, explicit_end_time, changer_name)
local changer_name = changer_name or debug.getinfo(2).name.."()"
if (mcl_weather.reg_weathers ~= nil and mcl_weather.reg_weathers[new_weather] ~= nil) then
if (mcl_weather.state ~= nil and mcl_weather.reg_weathers[mcl_weather.state] ~= nil) then
mcl_weather.reg_weathers[mcl_weather.state].clear()
end
local old_weather = mcl_weather.state
mcl_weather.state = new_weather
if old_weather == "none" then
old_weather = "clear"
end
if new_weather == "none" then
new_weather = "clear"
end
minetest.log("action", "[mcl_weather] " .. changer_name .. " changed the weather from " .. old_weather .. " to " .. new_weather)
local weather_meta = mcl_weather.reg_weathers[mcl_weather.state]
if explicit_end_time then
mcl_weather.end_time = explicit_end_time
else
mcl_weather.end_time = mcl_weather.get_rand_end_time(weather_meta.min_duration, weather_meta.max_duration)
end
mcl_weather.skycolor.update_sky_color()
save_weather()
return true
end
return false
end
mcl_weather.get_weather = function()
return mcl_weather.state
end
minetest.register_privilege("weather_manager", {
description = S("Gives ability to control weather"),
give_to_singleplayer = false
})
-- Weather command definition. Set
minetest.register_chatcommand("weather", {
params = "(clear | rain | snow | thunder) [<duration>]",
description = S("Changes the weather to the specified parameter."),
privs = {weather_manager = true},
func = function(name, param)
if (param == "") then
return false, S("Error: No weather specified.")
end
local new_weather, end_time
local parse1, parse2 = string.match(param, "(%w+) ?(%d*)")
if parse1 then
if parse1 == "clear" then
new_weather = "none"
else
new_weather = parse1
end
else
return false, S("Error: Invalid parameters.")
end
if parse2 then
if type(tonumber(parse2)) == "number" then
local duration = tonumber(parse2)
if duration < 1 then
return false, S("Error: Duration can't be less than 1 second.")
end
end_time = minetest.get_gametime() + duration
end
end
local success = mcl_weather.change_weather(new_weather, end_time, name)
if success then
return true
else
return false, S("Error: Invalid weather specified. Use “clear”, “rain”, “snow” or “thunder”.")
end
end
})
minetest.register_chatcommand("toggledownfall", {
params = "",
description = S("Toggles between clear weather and weather with downfall (randomly rain, thunderstorm or snow)"),
privs = {weather_manager = true},
func = function(name, param)
-- Currently rain/thunder/snow: Set weather to clear
if mcl_weather.state ~= "none" then
return mcl_weather.change_weather("none", nil, name)
-- Currently clear: Set weather randomly to rain/thunder/snow
else
local new = { "rain", "thunder", "snow" }
local r = math.random(1, #new)
return mcl_weather.change_weather(new[r], nil, name)
end
end
})
-- Configuration setting which allows user to disable ABM for weathers (if they use it).
-- Weather mods expected to be use this flag before registering ABM.
local weather_allow_abm = minetest.settings:get_bool("weather_allow_abm")
if weather_allow_abm ~= nil and weather_allow_abm == false then
mcl_weather.allow_abm = false
end
local load_weather = function()
local weather = storage:get_string("mcl_weather_state")
if weather and weather ~= "" then
mcl_weather.state = weather
mcl_weather.end_time = storage:get_int("mcl_weather_end_time")
mcl_weather.change_weather(weather, mcl_weather.end_time)
if type(mcl_weather.end_time) ~= "number" then
-- Fallback in case of corrupted end time
mcl_weather.end_time = mcl_weather.min_duration
end
minetest.log("action", "[mcl_weather] Weather restored.")
else
minetest.log("action", "[mcl_weather] No weather data found. Starting with clear weather.")
end
end
load_weather()