Complete rewrite

This commit is contained in:
Till Affeldt
2020-04-13 01:55:39 +02:00
parent e188015be1
commit 49242573f2
58 changed files with 550 additions and 865 deletions

39
lib/api.lua Normal file
View File

@ -0,0 +1,39 @@
local api = {}
api.SHORT_CYCLE = 0 -- for particles and fast animations
api.DEFAULT_CYCLE = 0 -- for most effect types
api.LONG_CYCLE = 0 -- for write operations and skybox changes
climate_mod.weathers = {}
climate_mod.effects = {}
climate_mod.cycles = {}
function api.register_weather(name, conditions, effects)
-- TODO: check and sanitize
climate_mod.weathers[name] = {
conditions = conditions,
effects = effects,
active_players = {}
}
end
function api.register_effect(name, handler, htype)
-- check for valid handler types
if htype ~= "start" and htype ~= "tick" and htype ~= "stop" then
minetest.log("warn", "[Climate API] Invalid effect handler type: " .. htype)
return
end
-- create effect handler registry if not existent yet
if type(climate_mod.effects[name]) == "nil" then
climate_mod.effects[name] = { start = {}, tick = {}, stop = {} }
climate_mod.cycles[name] = { timespan = DEFAULT_CYCLE, timer = 0 }
end
-- store effect handler
table.insert(climate_mod.effects[name][htype], handler)
end
function api.set_effect_cycle(name, cycle)
climate_mod.cycles[name].timespan = cycle
end
return api

63
lib/api_utility.lua Normal file
View File

@ -0,0 +1,63 @@
local mod_player_monoids = minetest.get_modpath("player_monoids") ~= nil
local mod_playerphysics = minetest.get_modpath("playerphysics") ~= nil
local utility = {}
function utility.rangelim(value, min, max)
return math.min(math.max(value, min), max)
end
-- from https://stackoverflow.com/a/29133654
-- merges two tables together
-- if in conflict, b will override values of a
function utility.merge_tables(a, b)
if type(a) == "table" and type(b) == "table" then
for k,v in pairs(b) do
if type(v)=="table" and type(a[k] or false)=="table" then
merge(a[k],v)
else a[k]=v end
end
end
return a
end
-- see https://en.wikipedia.org/wiki/Logistic_function
function utility.logistic_growth(value, max, growth, midpoint)
return max / (1 + math.exp(-growth * (value - midpoint)))
end
-- generates a wave of cycle length 1
-- maps parameters to values between 0 and 1
-- 0 is mapped to 0 and 0.5 to 1
function utility.normalized_cycle(value)
return math.cos((2 * value + 1) * math.pi) / 2 + 0.5
end
-- override player physics
-- use utility mod if possible to avoid conflict
function utility.add_physics(id, player, effect, value)
if mod_player_monoids then
player_monoids[effect]:add_change(player, value, id)
elseif mod_playerphysics then
playerphysics.add_physics_factor(player, effect, id, value)
else
local override = {}
override[effect] = value
player:set_physics_override(override)
end
end
-- reset player phsysics to normal
function utility.remove_physics(id, player, effect)
if mod_player_monoids then
player_monoids[effect]:del_change(player, id)
elseif mod_playerphysics then
playerphysics.remove_physics_factor(player, effect, id)
else
local override = {}
override[effect] = 1
player:set_physics_override(override)
end
end
return utility

View File

@ -1,18 +0,0 @@
function weather_mod.get_time_heat()
local time = minetest.get_timeofday()
return math.cos((2 * time + 1) * math.pi) / 3 + 1
end
function weather_mod.get_calendar_heat()
-- European heat center in August instead of June
local progression = (weather_mod.state.time.day + 61) / 365
return math.cos((2 * progression + 1) * math.pi) / 3 + 1
end
function weather_mod.handle_time_progression()
local time = minetest.get_timeofday()
if time < weather_mod.state.time.last_check then
weather_mod.state.time.day = weather_mod.state.time.day + 1
end
weather_mod.state.time.last_check = time
end

View File

@ -1,58 +0,0 @@
weather_mod.weekdays = {
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
}
weather_mod.months = {
{ name = "January", days = 31 },
{ name = "February", days = 28 },
{ name = "March", days = 31 },
{ name = "April", days = 30 },
{ name = "May", days = 31 },
{ name = "June", days = 30 },
{ name = "July", days = 31 },
{ name = "August", days = 31 },
{ name = "September", days = 30 },
{ name = "October", days = 31 },
{ name = "November", days = 30 },
{ name = "December", days = 31 }
}
weather_mod.seasons = {
{ name = "spring" },
{ name = "summer" },
{ name = "autumn" },
{ name = "winter" }
}
function weather_mod.get_weekday()
return (weather_mod.state.time.day - 1) % 7 + 1
end
function weather_mod.get_month()
local day = (weather_mod.state.time.day - 1) % 365 + 1
local sum = 0
for i, month in ipairs(weather_mod.months) do
sum = sum + month.days
if sum >= day then
return i
end
end
end
function weather_mod.get_season()
local month = weather_mod.get_month()
return math.floor((month - 1) / 3 + 1)
end
function weather_mod.print_date()
local weekday = weather_mod.weekdays[weather_mod.get_weekday()]
local date = (weather_mod.state.time.day - 1) % 365 + 1
local month = weather_mod.months[weather_mod.get_month()].name
return weekday .. ", " .. date .. ". " .. month
end

View File

@ -1,94 +0,0 @@
minetest.register_privilege("weather", {
description = "Change the weather",
give_to_singleplayer = false
})
minetest.register_chatcommand("date", {
func = function(playername, param)
local date = weather_mod.print_date()
minetest.chat_send_player(playername, date)
end
})
-- Force a weather effect to override environment
minetest.register_chatcommand("set_weather", {
params = "<weather>",
description = "Set weather to a registered type of effect\
show all types when no parameters are given", -- full description
privs = {weather = true},
func = function(name, param)
if param == nil or param == "" or param == "?" then
local types="auto"
for i,_ in pairs(weather_mod.weathers) do
types=types..", "..i
end
minetest.chat_send_player(name, "avalible weather types: "..types)
else
if type(weather_mod.weathers[param]) == "nil" and param ~= "auto" then
minetest.chat_send_player(name, "This type of weather is not registered.\n"..
"To list all types of weather run the command without parameters.")
else
weather_mod.state.current_weather = param
end
end
end
})
-- Set wind speed and direction
minetest.register_chatcommand("set_wind", {
params = "<wind>",
description = "Set wind to the given x,z direction", -- full description
privs = {weather = true},
func = function(name, param)
if param==nil or param=="" then
minetest.chat_send_player(name, "please provide two comma seperated numbers")
return
end
local x,z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)$")
x=tonumber(x)
z=tonumber(z)
if x and z then
weather_mod.state.wind = vector.new(x,0,z)
else
minetest.chat_send_player(name, param.." are not two comma seperated numbers")
end
end
})
-- Set base value of global heat level
minetest.register_chatcommand("set_heat", {
params = "<heat>",
description = "Set base value of global heat level", -- full description
privs = {weather = true},
func = function(name, param)
if param==nil or param=="" then
minetest.chat_send_player(name, "please provide a heat value")
return
end
v = tonumber(param)
if v then
weather_mod.state.heat = v
else
minetest.chat_send_player(name, param.." is not a valid heat level")
end
end
})
-- Set base value of global humidity level
minetest.register_chatcommand("set_humidity", {
params = "<humidity>",
description = "Set base value of global humidity level", -- full description
privs = {weather = true},
func = function(name, param)
if param==nil or param=="" then
minetest.chat_send_player(name, "please provide a humidity value")
return
end
v = tonumber(param)
if v then
weather_mod.state.humidity = v
else
minetest.chat_send_player(name, param.." is not a valid humidity level")
end
end
})

View File

@ -1,48 +1,16 @@
local default_state = {
heat = 1,
humidity = 1,
wind = vector.new(0, 0, -0.25),
current_weather = "auto",
time = {
last_check = 0,
day = 1
}
wind_x = 0.5,
wind_z = 0.5,
time_last_check = 0,
time_current_day = 1
}
local function use_datastorage()
local state = datastorage.get(weather_mod.modname, "weather_state")
for key, val in pairs(default_state) do
if type(state[key]) == "nil" then
state[key] = val
end
end
return state
local state = minetest.get_mod_storage()
if not state:contains("time_last_check") then
state:from_table({ fields = default_state })
end
local function use_filesystem()
local file_name = minetest.get_worldpath() .. "/" .. weather_mod.modname
minetest.register_on_shutdown(function()
local file = io.open(file_name, "w")
file:write(minetest.serialize(weather_mod.state))
file:close()
end)
local file = io.open(file_name, "r")
if file ~= nil then
local storage = minetest.deserialize(file:read("*a"))
file:close()
if type(storage) == "table" then
return storage
end
end
return default_state
end
function weather_mod.get_storage()
local mod_datastorage = minetest.get_modpath("datastorage") ~= nil
if mod_datastorage then
return use_datastorage()
else
return use_filesystem()
end
end
return state

View File

@ -1,80 +1,36 @@
local mod_lightning = minetest.get_modpath("lightning")
local environment = {}
local LIGHTNING_CHANCE = 1000
if mod_lightning then
lightning.auto = false
local function get_heat_time()
local time = minetest.get_timeofday()
return climate_api.utility.normalized_cycle(time) * 0.4 + 0.3
end
function weather_mod.get_heat(pos)
local base = weather_mod.settings.heat;
local function get_heat_calendar()
-- European heat center in August instead of June
local day = climate_mod.state:get("time_current_day")
local progression = ((day + 61) % 365) / 365
return climate_api.utility.normalized_cycle(progression) * 0.4 + 0.3
end
local function get_heat_height(y)
return climate_api.utility.rangelim(-y / 15, -10, 10)
end
function environment.get_heat(pos)
local base = climate_mod.settings.heat
local biome = minetest.get_heat(pos)
local height = math.min(math.max(-pos.y / 15, -10), 10)
local time = weather_mod.get_time_heat()
local date = weather_mod.get_calendar_heat()
local random = weather_mod.state.heat;
return (base + biome + height) * time * date + random
local height = get_heat_height(pos.y)
local time = get_heat_time()
local date = get_heat_calendar()
local random = climate_mod.state:get_int("heat_random");
return (base + biome + height) * time * date
end
function weather_mod.get_humidity(pos)
local base = weather_mod.settings.humidity
function environment.get_humidity(pos)
local base = climate_mod.settings.humidity
local biome = minetest.get_humidity(pos)
local random = weather_mod.state.humidity;
return (base + biome) + random
local random = climate_mod.state:get_int("humidity_random");
return (base + biome)
end
function weather_mod.get_climate(pos)
local climate = {pos = pos}
climate.heat = weather_mod.get_heat(pos)
climate.humidity = weather_mod.get_humidity(pos)
climate.windspeed = vector.length(weather_mod.state.wind)
return climate
end
local function is_acceptable_weather_param(value, attr, config)
local min = config.conditions["min_" .. attr] or -math.huge
local max = config.conditions["max_" .. attr] or math.huge
return value > min and value <= max
end
function weather_mod.get_effects(climate)
local forced_weather = weather_mod.state.current_weather
if type(forced_weather) ~= nil and forced_weather ~= "auto" then
return { forced_weather }
end
local params = {}
params.heat = climate.heat
params.humidity = climate.humidity
params.windspeed = vector.length(weather_mod.state.wind)
params.height = climate.pos.y
local effects = {}
local attributes = { "heat", "humidity", "windspeed", "height" }
for name, effect in pairs(weather_mod.weathers) do
if type(effect.config.conditions) == "nil" then
table.insert(effects, name)
goto continue
end
for _, attr in ipairs(attributes) do
if not is_acceptable_weather_param(params[attr], attr, effect.config) then
goto continue
end
end
table.insert(effects, name)
::continue::
end
return effects
end
function weather_mod.handle_events(player, flags)
local ppos = player:get_pos()
if mod_lightning and weather_mod.settings.lightning and type(flags["lightning"]) ~= "nil" then
local random = rng:next(1, LIGHTNING_CHANCE)
if random == 1 then
lightning.strike(ppos)
end
end
if type(flags["damage"]) ~= "nil" then
weather_mod.damage_player(player, 1)
end
end
return environment

View File

@ -1,124 +1,27 @@
local GSCYCLE = 0.05
local RECALCCYCLE = 0.2
local GSCYCLE = 0
local WORLD_CYCLE = 0
weather_mod.weathers = {}
function weather_mod.register_effect(name, config, override)
-- TODO: check and sanitize
weather_mod.weathers[name] = {
config = config,
override = override,
sound_handles = {},
sound_volumes = {}
}
end
-- from https://stackoverflow.com/a/29133654
local function merge(a, b)
if type(a) == 'table' and type(b) == 'table' then
for k,v in pairs(b) do if type(v)=='table' and type(a[k] or false)=='table' then merge(a[k],v) else a[k]=v end end
end
return a
end
local function build_effect_config(weather, climate)
local config = weather.config
local override = weather.override
if type(override) == "nil" then
return config
end
local dynamic_config = override(climate)
return merge(config, dynamic_config)
end
local function get_texture(particles)
if type(particles.textures) == "nil" or next(particles.textures) == nil then
return particles.texture
end
return particles.textures[math.random(#particles.textures)]
end
local function spawn_particles(player, particles, wind)
local ppos = player:getpos()
local wind_pos = vector.multiply(weather_mod.state.wind,-1)
local wind_speed = vector.length(weather_mod.state.wind)
local texture = get_texture(particles)
local minp = vector.add(vector.add(ppos, particles.min_pos),wind_pos)
local maxp = vector.add(vector.add(ppos, particles.max_pos),wind_pos)
local vel = vector.new({
x=weather_mod.state.wind.x,
y=-particles.falling_speed,
z=weather_mod.state.wind.z
})
local acc = vector.new({x=0, y=0, z=0})
local exp = particles.exptime
local vertical = math.abs(vector.normalize(vel).y) >= 0.6
minetest.add_particlespawner({
amount=particles.amount,
time=0.5,
minpos=minp,
maxpos=maxp,
minvel=vel,
maxvel=vel,
minacc=acc,
maxacc=acc,
minexptime=exp,
maxexptime=exp,
minsize=particles.size,
maxsize=particles.size,
collisiondetection=true,
collision_removal=true,
vertical=vertical,
texture=texture,
player=player:get_player_name()
})
end
local function handle_weather_effects(player)
local ppos = player:getpos()
local climate = weather_mod.get_climate(ppos)
local active_effects = weather_mod.get_effects(climate)
local environment_flags = {}
local sounds = {}
for _, effect in ipairs(active_effects) do
local weather = weather_mod.weathers[effect]
local config = build_effect_config(weather, climate)
local outdoors = weather_mod.is_outdoors(player)
if type(config.particles) ~= "nil" and outdoors then
spawn_particles(player, config.particles, weather_mod.state.wind)
end
if type(config.sound) ~= "nil" and outdoors then
sounds[effect] = config.sound
end
if type(config.environment) ~= "nil" and outdoors then
for flag, value in pairs(config.environment) do
if value ~= false then
environment_flags[flag] = value
end
end
end
end
weather_mod.handle_sounds(player, sounds)
weather_mod.handle_events(player, environment_flags)
end
local timer = 0
local gs_timer = 0
local world_timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer < GSCYCLE then return end
for _, player in ipairs(minetest.get_connected_players()) do
handle_weather_effects(player)
if timer >= RECALCCYCLE then
weather_mod.set_clouds(player)
weather_mod.set_headwind(player)
weather_mod.handle_time_progression()
gs_timer = gs_timer + dtime
world_timer = world_timer + dtime
if gs_timer + dtime < GSCYCLE then return else gs_timer = 0 end
if world_timer >= WORLD_CYCLE then
local noise_timer = climate_mod.state:get_float("noise_timer") + world_timer
world_timer = 0
climate_mod.state:set_float("noise_timer", noise_timer)
climate_mod.world.update_status(noise_timer)
end
local effects = climate_mod.trigger.get_active_effects()
for name, effect in pairs(climate_mod.effects) do
if climate_mod.cycles[name].timespan < climate_mod.cycles[name].timer + dtime then
climate_mod.cycles[name].timer = 0
climate_mod.trigger.call_handlers(name, effects[name])
end
end
timer = 0
end)

View File

@ -1,92 +0,0 @@
local mod_player_monoids = minetest.get_modpath("player_monoids") ~= nil
local mod_playerphysics = minetest.get_modpath("playerphysics") ~= nil
function weather_mod.add_physics(player, effect, value)
local id = weather_mod.modname .. ":" .. effect
if mod_player_monoids then
player_monoids[effect]:add_change(player, value, id)
elseif mod_playerphysics then
playerphysics.add_physics_factor(player, effect, id, value)
else
local override = {}
override[effect] = value
player:set_physics_override(override)
end
end
function weather_mod.remove_physics(player, effect)
local id = weather_mod.modname .. ":" .. effect
if mod_player_monoids then
player_monoids[effect]:del_change(player, id)
elseif mod_playerphysics then
playerphysics.remove_physics_factor(player, effect, id)
else
local override = {}
override[effect] = 1
player:set_physics_override(override)
end
end
function weather_mod.is_outdoors(player)
return minetest.get_node_light(player:getpos(), 0.5) == 15
end
function weather_mod.set_clouds(player)
if not weather_mod.settings.skybox then
return
end
local ppos = player:get_pos()
local humidity = weather_mod.get_humidity(ppos) / 100
local clouds = {}
clouds.speed = vector.multiply(weather_mod.state.wind, 2)
clouds.color = "#fff0f0c5"
clouds.density = math.max(math.min(humidity, 0.8), 0.1)
player:set_clouds(clouds)
end
function weather_mod.play_sound(player, effect)
local playername = player:get_player_name()
if not effect.sound_handles[playername] then
local handle = minetest.sound_play(effect.config.sound, {
to_player = playername,
loop = true
})
if handle then
effect.sound_handles[playername] = handle
effect.sound_volumes[playername] = effect.config.sound.gain or 1
end
end
end
function weather_mod.stop_sound(player, effect)
local playername = player:get_player_name()
if effect.sound_handles[playername] then
minetest.sound_stop(effect.sound_handles[playername])
effect.sound_handles[playername] = nil
effect.sound_volumes[playername] = nil
end
end
function weather_mod.handle_sounds(player, sounds)
local playername = player:get_player_name()
for name, effect in pairs(weather_mod.weathers) do
if type(effect.sound_handles[playername]) == "nil" and type(sounds[name]) ~= "nil" then
weather_mod.play_sound(player, effect)
elseif type(effect.sound_handles[playername]) ~= "nil" and type(sounds[name]) == "nil" then
weather_mod.stop_sound(player, effect)
elseif type(effect.sound_handles[playername]) ~= "nil" and type(sounds[name]) ~= "nil" then
local volume = sounds[name].gain or 1
if effect.sound_volumes[playername] ~= volume then
minetest.sound_fade(effect.sound_handles[playername], 1, volume)
end
end
end
end
function weather_mod.damage_player(player, amount, reason)
if not weather_mod.settings.damage then
return
end
local hp = player:get_hp()
player:set_hp(hp - amount, reason)
end

89
lib/trigger.lua Normal file
View File

@ -0,0 +1,89 @@
local trigger = {}
local function get_player_environment(player)
local ppos = player:get_pos()
local wind_x = climate_mod.state:get_int("wind_x")
local wind_z = climate_mod.state:get_int("wind_z")
local env = {}
env.player = player
env.pos = pos
env.wind = vector.new(wind_x, 0, wind_z)
env.windspeed = vector.length(env.wind)
env.heat = climate_api.environment.get_heat(ppos)
env.humidity = climate_api.environment.get_humidity(ppos)
env.time = minetest.get_timeofday()
env.date = climate_mod.state:get_int("time_current_day")
return env
end
local function test_condition(condition, env, goal)
local value = env[condition:sub(5)]
if condition:sub(1, 4) == "min_" then
for _, player in ipairs(minetest.get_connected_players()) do
minetest.chat_send_player(player:get_player_name(), dump2(value, goal))
end
return type(value) ~= "nil" and goal <= value
elseif condition:sub(1, 4) == "max_" then
return type(value) ~= "nil" and goal > value
else
Minetest.log("warning", "[Climate API] Invalid effect condition")
return false
end
end
local function is_weather_active(player, weather_config, env)
if type(weather_config.conditions) == "function" then
return weather_config.conditions(env)
end
for condition, goal in pairs(weather_config.conditions) do
if not test_condition(condition, env, goal) then
return false
end
end
return true
end
local function get_weather_effects(player, weather_config, env)
if type(weather_config.effects) == "function" then
return weather_config.effects(env)
end
return weather_config.effects
end
function trigger.get_active_effects()
local environments = {}
for _, player in ipairs(minetest.get_connected_players()) do
environments[player:get_player_name()] = get_player_environment(player)
end
local effects = {}
for wname, wconfig in pairs(climate_mod.weathers) do
for _, player in ipairs(minetest.get_connected_players()) do
local pname = player:get_player_name()
local env = environments[pname]
if is_weather_active(player, wconfig, env) then
local player_effects = get_weather_effects(player, wconfig, env)
for effect, value in pairs(player_effects) do
if type(effects[effect]) == "nil" then
effects[effect] = {}
end
if type(effects[effect][pname]) == "nil" then
effects[effect][pname] = {}
end
effects[effect][pname][wname] = value
end
end
end
end
return effects
end
function trigger.call_handlers(name, effect)
if type(effect) == "nil" then return end
for _, handler in ipairs(climate_mod.effects[name]["tick"]) do
handler(effect)
end
end
return trigger

View File

@ -1,12 +0,0 @@
function weather_mod.set_headwind(player)
local movement = vector.normalize(player:get_player_velocity())
local product = vector.dot(movement, weather_mod.state.wind)
-- logistic function, scales between 0 and 2
-- see https://en.wikipedia.org/wiki/Logistic_function
local L = 1.6 -- maximum value
local k = 0.15 -- growth rate
local z = 0.8 -- midpoint
local o = 0.1 -- y offset
local factor = L / (1 + math.exp(-k * (product - z))) + o
weather_mod.add_physics(player, "speed", factor)
end

92
lib/world.lua Normal file
View File

@ -0,0 +1,92 @@
local world = {}
local WIND_SPREAD = 600
local WIND_SCALE = 3
local HEAT_SPREAD = 2400
local HEAT_SCALE = 0.2
local HUMIDITY_SPREAD = 1200
local HUMIDITY_SCALE = 0.18
local nobj_wind_x
local nobj_wind_z
local nobj_heat
local nobj_humidity
local pn_wind_speed_x = {
offset = 0,
scale = WIND_SCALE,
spread = {x = WIND_SPREAD, y = WIND_SPREAD, z = WIND_SPREAD},
seed = 31441,
octaves = 2,
persist = 0.5,
lacunarity = 2
}
local pn_wind_speed_z = {
offset = 0,
scale = WIND_SCALE,
spread = {x = WIND_SPREAD, y = WIND_SPREAD, z = WIND_SPREAD},
seed = 938402,
octaves = 2,
persist = 0.5,
lacunarity = 2
}
local pn_heat = {
offset = 0,
scale = HEAT_SCALE,
spread = {x = HEAT_SPREAD, y = HEAT_SPREAD, z = HEAT_SPREAD},
seed = 24,
octaves = 2,
persist = 0.5,
lacunarity = 2
}
local pn_humidity = {
offset = 0,
scale = HUMIDITY_SCALE,
spread = {x = HUMIDITY_SPREAD, y = HUMIDITY_SPREAD, z = HUMIDITY_SPREAD},
seed = 8374061,
octaves = 3,
persist = 0.5,
lacunarity = 2
}
local function update_wind(timer)
nobj_wind_x = nobj_wind_x or minetest.get_perlin(pn_wind_speed_x)
nobj_wind_z = nobj_wind_z or minetest.get_perlin(pn_wind_speed_z)
local n_wind_x = nobj_wind_x:get_2d({x = timer, y = 0})
local n_wind_z = nobj_wind_z:get_2d({x = timer, y = 0})
climate_mod.state:set_int("wind_x", n_wind_x)
climate_mod.state:set_int("wind_z", n_wind_z)
end
local function update_heat(timer)
nobj_heat = nobj_heat or minetest.get_perlin(pn_heat)
local n_heat = nobj_heat:get_2d({x = timer, y = 0})
climate_mod.state:set_int("heat_random", n_heat)
end
local function update_humidity(timer)
nobj_humidity = nobj_humidity or minetest.get_perlin(pn_humidity)
local n_humidity = nobj_humidity:get_2d({x = timer, y = 0})
climate_mod.state:set_int("humidity_random", n_humidity)
end
local function update_date()
local time = minetest.get_timeofday()
if time < climate_mod.state:get_int("time_last_check") then
local day = climate_mod.state:get_int("time_current_day")
climate_mod.state:set_int("time_current_day ", day + 1)
end
climate_mod.state:set_int("time_last_check", time)
end
function world.update_status(timer)
update_date()
update_wind(timer)
update_heat(timer)
update_humidity(timer)
end
return world