9 Commits

24 changed files with 554 additions and 182 deletions

View File

@ -1,5 +1,8 @@
# Climate API
A powerful engine for weather presets and visual effects
A powerful engine for weather presets and visual effects.
Requires a weather pack like [Regional Weather](https://github.com/t-affeldt/regional_weather).
![](https://raw.githubusercontent.com/t-affeldt/climate_api/master/screenshot.png)
Use the regional climate to set up different effects for different regions.
Control where your effects are activated based on temperature, humidity, wind,
@ -8,8 +11,98 @@ Climate API provides temperature and humidity values on a block-per-block basis
that follow the seasons, day / night cycle and random changes.
Make it rain, change the sky or poison the player - it's up to you.
## Troubleshooting
Generally speaking, most mods should be compatible.
If you notice __odd movement speeds__ or jump heights of players, you should check for mods that also modify player physics. Use a compatibility mod like [player_monoids](https://github.com/minetest-mods/player_monoids) or [playerphysics](https://forum.minetest.net/viewtopic.php?t=22172) to get rid of this problem. This requires the conflicting mod to also support the chosen compatibility layer.
Mods that __modify the sky__ (including skybox, moon, sun, stars and clouds) are sadly not fully compatible because they conflict with Climate API's sky system. You should deactivate the sky features in either mod. You can do this here using the ``Override the skybox`` setting. If you're a mod maker then you can also optionally depend on climate_api and use ``climate_api.skybox.add_layer(playername, layer_name, options)`` to register your skybox change in a compatible way. Note that you need __at least Minetest v5.2.0__ for skybox changes to have any effect.
Conflicting skybox changes include the ``weather`` mod included in vanilla __Minetest Game__. You will want to disable that mod in order to use the more advanced cloud system introduced by Climate API. Head to ``Settings → All Settings → Games → Minetest Game`` and set ``Enable weather`` to ``Disabled``. This setting will only exist if you are using Minetest Game v5.2.0 or higher.
The following mods have been created specifically with Climate API in mind:
- [Regional Weather](https://github.com/t-affeldt/regional_weather): My own weather pack for climate based weather effects
- [Moon Phases](https://github.com/t-affeldt/minetest_moon_phase): Complements weather effects with dynamic sky changes and a full moon cycle
- [Sailing Kit](https://github.com/t-affeldt/sailing_kit) (Fork): Uses Climate API's new wind system to sail across the sea.
The following mods complement Climate API particularly well:
- [Lightning](https://github.com/minetest-mods/lightning): Adds to heavy rain by enabling additional lightning effects
- [Ambience](https://notabug.org/TenPlus1/ambience): Plays some nice ambient sound effects based on where you are.
## Chat Commands
- ``/weather``: Display information on current weather effects. This command will show you current temperature and humidity, active weather presets and currently playing effects
- ``/weather_settings``: Display current mod configuration in the chat
- ``/weather_influences``: Display all different factors and how they affect you in this moment.
- ``/weather_status``: Display a list of all installed weather presets and whether they have been forced on, turned off, or are running normally (auto).
- ``/grant <playername> weather``: Enable a specified player to modify the current weather.
- ``/set_heat <value>``: Override the base heat value used to calculate local climate. Positive numbers will increase temperature by X degrees Fahrenheit, whereas negative values will lower it.
- ``/set_humidity <value>``: Override the base humidity value used to calculate local climate. Positive numbers will increase humidity by X percent, whereas negative values will lower it.
- ``/set_wind <x> <z>``: Override wind speed and direction. Higher absolute values result in stronger wind. The sign indicates direction.
- ``/set_weather <weather> <on|off|auto>``: Set a weather preset to always be applied (on), disable it completely (off), or reset it to be applied automatically (auto). Turning presets on manually might result in partially missing effects (like no sound if you enable sandstorms but no storms). Use ``/weather_status`` for a full list of installed weather presets. The prefix is important.
## Configuration Options
You can find all mod configuration options in your Minetest launcher.
Go to ``Settings → All Settings → Mods → climate_api`` to change them.
Individual weather packs may provide additional configuration options in their respective mod configuration section.
### Performance
- ``Update speed of weather effects`` (default 1.0):
This value regulates how often weather presets are recalculated.
Higher values will result in smoother transitions between effects as well as faster response times to traveling players.
Lower values will significantly increase overall performance at the cost of rougher looking effects.
- ``Multiplicator for used particles`` (default 1.0):
This value regulated how many particles will be spawned.
A value of 1 will use the recommended amount of particles.
Lower values can possible increase performance.
- ``Dynamically modify nodes`` (default true):
If set to true, weather packs are allowed to register node update handlers.
These can be used to dynamically place snow layers, melt ice, or hydrate soil.
### Visuals
- ``Show particle effects`` (default true):
If set to true, weather effects (like rain) are allowed to render particles.
Deactivating this feature will prevent some presets from being visible.
For performance considerations it is recommended to decrease the amount of particles instead.
- ``Override the skybox`` (default true):
If set to true, weather effects are allowed to modify a player's sky.
This includes skybox, sun, moon, and clouds (also used for fog effects).
Running this mod on Minetest 5.1.2 or earlier versions will automatically disable this feature.
- ``Display HUD overlays`` (default true):
If set to true, weather effects are allowed to render an image on top of the gameplay.
This is usually an optional effect used to increase immersion (like a frozen-over camera in a snow storm).
### Environment
- ``Global base temperature`` (default 0):
This value will be added to all biome's base temperatures before applying random modifiers.
Every unit here will increase the global base heat by one degree Fahrenheit.
Negative values will cool down global base heat respectively.
- ``Global base humidity`` (default 0):
This value will be added to all biome's base humidity before applying random modifiers.
Every unit here will increase the global base humidity by one percent.
Negative values will dry up global base humidity respectively.
- ``Time rate of weather changes`` (default 1.0):
This value regulates how quickly environment factors like heat, humidity and wind are changing.
A value of 2 will double the speed at which weather presets change.
A value of 0.5 will half the speed respectively.
### Preferences
- ``Show degrees in Fahrenheit instead of Celsius`` (default true):
If set to true, temperature information in /weather command will be displayed in Fahrenheit.
- ``Play ambient sound loops`` (default true):
If set to true, weather effects are allowed to play sound loops.
You can also adjust sound levels instead of deactivating this feature completely.
Setting this value to false will be slightly more performant than setting the volume to zero.
- ``Volume of sound effects`` (default 1.0):
This value regulates overall sound volume.
A value of 2 will double the volume whereas a value of 0.5 will reduce the volume by half.
## License
- Source Code: *GNU LGPL v3* by me
- Sun and moon textures: *CC BY-SA (3.0)* by Cap
## Assets in screenshots
- All screenshots and editing by me: *CC BY-SA (4.0)*
- Screenshots and editing by me: *CC BY-SA (3.0)*
- Logos and artwork: *CC BY-SA (3.0)* by Cap
- Lato Font (for the Logo): *OFL* by Łukasz Dziedzic from http://www.latofonts.com/lato-free-fonts/
- Liberation Fonts (for the text): *OFL*, see https://github.com/liberationfonts/liberation-fonts
- Source Sans Pro (for the subtitles): *OFL*, see https://fonts.google.com/specimen/Source+Sans+Pro
- Used texture pack: Polygonia (128px edition) *CC BY-SA (4.0)* by Lokrates. See https://forum.minetest.net/viewtopic.php?f=4&t=19043

View File

@ -1,30 +1,15 @@
# TODO
## Required for MVP
- Find good values for weather conditions
- Make sure all weather presets are working
## Required for Beta
- Ability to register environment conditions dynamically (like the heat)
## Planned for first release
- Improve test_condition function
- Write helpful README
- Set effects on player join
- Improve value structures of particle and skybox effects
- Make sounds adjust to changes by weather presets
- Ability to force set a wind speed via chat commands
- Improve value structures of particle effects
- Find good values for weather conditions
- Write documentation on how to add weathers and effects
## Nice to have
- Write documentation on how to add weathers and effects
- Assign meta data (like "downfall", "wind", etc.) to weather presets
- Fog effects
- Optimize performance by replacing some particles with animated texture planes
- Make switches between effects more smooth
- Adjust size of particle boxes based on player speed
- Create conditions for time of day, annual progression, biome filters
- Fork lightning so that it uses skylayer
- Support for sail boats mod
- Generate wind based on speed and yaw instead of x and z values
## Future Plans & Ideas
- Complete season system
@ -52,3 +37,4 @@
- standing near fire will warm up
- craftable warm clothes
- metal armor will worsen heat issues
- A flag indicating wind direction

View File

@ -4,9 +4,25 @@ local EFFECT_NAME = "climate_api:hud_overlay"
local handles = {}
local function apply_hud(pname, weather, hud)
if handles[pname] == nil then handles[pname] = {} end
if handles[pname][weather] ~= nil then return end
local player = minetest.get_player_by_name(pname)
if handles[pname] == nil then handles[pname] = {} end
if handles[pname][weather] ~= nil then
player:hud_remove(handles[pname][weather])
end
local file
if hud.color_correction then
local pos = vector.add(player:get_pos(), {x = 0, y = 1, z = 0})
local light = math.floor(math.max(minetest.env:get_node_light(pos) / 15, 0.2) * 256)
local shadow = 256 - light
local dark_file = hud.file .. "^[multiply:#000000ff^[opacity:" .. shadow
local light_file = hud.file .. "^[opacity:" .. light
file = "(" .. light_file .. ")^(" .. dark_file .. ")"
else
file = hud.file
end
local handle = player:hud_add({
name = weather,
hud_elem_type = "image",
@ -14,7 +30,7 @@ local function apply_hud(pname, weather, hud)
alignment = {x = 1, y = 1},
scale = { x = -100, y = -100},
z_index = hud.z_index,
text = hud.file,
text = file,
offset = {x = 0, y = 0}
})
handles[pname][weather] = handle
@ -39,7 +55,12 @@ end
local function handle_effect(player_data, prev_data)
for playername, data in pairs(player_data) do
for weather, value in pairs(data) do
if prev_data[playername][weather] == nil then
if prev_data[playername][weather] == nil
or value.color_correction == true
or prev_data[playername][weather].color_correction == true
or value.file ~= prev_data[playername][weather].file
or value.z_index ~= prev_data[playername][weather].z_index
then
apply_hud(playername, weather, value)
end
end

View File

@ -11,44 +11,58 @@ end
local function spawn_particles(player, particles)
local ppos = player:getpos()
local wind_x = climate_mod.state:get_float("wind_x")
local wind_z = climate_mod.state:get_float("wind_z")
local wind = vector.new(wind_x, 0, wind_z)
local wind_pos = vector.multiply(wind, -1)
local wind_speed = vector.length(wind)
local wind = climate_api.environment.get_wind()
local amount = particles.amount * climate_mod.settings.particle_count
local texture = get_particle_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 = wind.x,
y = -particles.falling_speed,
z = 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
if particles.acceleration == nil then
particles.acceleration = vector.new({x=0, y=0, z=0})
end
local wind_pos = vector.multiply(
vector.normalize(vel),
-vector.length(wind)
)
wind_pos.y = 0
local minp = vector.add(vector.add(ppos, particles.min_pos), wind_pos)
local maxp = vector.add(vector.add(ppos, particles.max_pos), wind_pos)
if particles.time == nil then
particles.time = 0.5
end
if particles.vertical == nil then
particles.vertical = math.abs(vector.normalize(vel).y) >= 0.6
end
if particles.size ~= nil then
particles.min_size = particles.size
particles.max_size = particles.size
end
minetest.add_particlespawner({
amount = amount,
time = 0.5,
time = particles.time,
minpos = minp,
maxpos = maxp,
minvel = vel,
maxvel = vel,
minacc = acc,
maxacc = acc,
minexptime = exp,
maxexptime = exp,
minsize = particles.size,
maxsize = particles.size,
minacc = particles.acceleration,
maxacc = particles.acceleration,
minexptime = particles.exptime,
maxexptime = particles.exptime,
minsize = particles.min_size,
maxsize = particles.max_size,
collisiondetection = true,
collision_removal = true,
vertical = vertical,
vertical = particles.vertical,
texture = texture,
player = player:get_player_name()
})

View File

@ -2,46 +2,30 @@ if not climate_mod.settings.skybox then return end
local EFFECT_NAME = "climate_api:skybox"
local function set_skybox(player, sky)
if sky.sky_data ~= nil then
player:set_sky(sky.sky_data)
end
if sky.cloud_data ~= nil then
player:set_clouds(sky.cloud_data)
end
if sky.moon_data ~= nil then
player:set_moon(sky.moon_data)
end
if sky.sun_data ~= nil then
player:set_sun(sky.sun_data)
end
if sky.stars_data ~= nil then
player:set_sun(sky.stars_data)
local function handle_effect(player_data, prev_data)
for playername, data in pairs(prev_data) do
for weather, _ in pairs(data) do
if player_data[playername] == nil or player_data[playername][weather] == nil then
climate_api.skybox.remove_layer(playername, weather)
end
end
local function remove_skybox(player)
player:set_sky({ type = "regular", clouds = true})
end
local function handle_effect(player_data)
for playername, data in pairs(player_data) do
local player = minetest.get_player_by_name(playername)
local sky = {}
for weather, value in pairs(data) do
climate_api.utility.merge_tables(sky, value)
climate_api.skybox.add_layer(playername, weather, value)
end
set_skybox(player, sky)
climate_api.skybox.update_skybox(playername)
end
end
local function remove_effect(player_data)
for playername, data in pairs(player_data) do
local player = minetest.get_player_by_name(playername)
remove_skybox(player)
for weather, _ in pairs(data) do
climate_api.skybox.remove_layer(playername, weather)
end
end
end
climate_api.register_effect(EFFECT_NAME, handle_effect, "tick")
climate_api.register_effect(EFFECT_NAME, remove_effect, "stop")
--climate_api.set_effect_cycle("climate_api:skybox", climate_api.LONG_CYCLE)
climate_api.set_effect_cycle("climate_api:skybox", climate_api.MEDIUM_CYCLE)

View File

@ -1,29 +1,23 @@
if not climate_mod.settings.sound then return end
local EFFECT_NAME = "climate_api:sound"
local FADE_DURATION = climate_api.LONG_CYCLE
local handles = {}
local function start_sound(pname, weather, sound)
if handles[pname] == nil then handles[pname] = {} end
if handles[pname][weather] ~= nil then return end
local handle = minetest.sound_play(sound, {
to_player = pname,
loop = true
})
handles[pname][weather] = handle
local modpath = minetest.get_modpath(minetest.get_current_modname())
local soundloop = dofile(modpath .. "/lib/soundloop.lua")
local function start_sound(pname, sound)
return soundloop.play(pname, sound, FADE_DURATION)
end
local function stop_sound(pname, weather, sound)
if handles[pname] == nil or handles[pname][weather] == nil then return end
local handle = handles[pname][weather]
minetest.sound_stop(handle)
handles[pname][weather] = nil
local function stop_sound(pname, sound)
return soundloop.stop(pname, sound, FADE_DURATION)
end
local function start_effect(player_data)
for playername, data in pairs(player_data) do
for weather, value in pairs(data) do
start_sound(playername, weather, value)
start_sound(playername, value)
end
end
end
@ -32,7 +26,7 @@ local function handle_effect(player_data, prev_data)
for playername, data in pairs(player_data) do
for weather, value in pairs(data) do
if prev_data[playername][weather] == nil then
start_sound(playername, weather, value)
start_sound(playername, value)
end
end
end
@ -40,17 +34,16 @@ local function handle_effect(player_data, prev_data)
for playername, data in pairs(prev_data) do
for weather, value in pairs(data) do
if player_data[playername][weather] == nil then
stop_sound(playername, weather, value)
stop_sound(playername, value)
end
end
end
end
local function stop_effect(prev_data)
minetest.log(dump2(prev_data, "stop_effect"))
for playername, data in pairs(prev_data) do
for weather, value in pairs(data) do
stop_sound(playername, weather, value)
stop_sound(playername, value)
end
end
end
@ -58,4 +51,4 @@ end
climate_api.register_effect(EFFECT_NAME, start_effect, "start")
climate_api.register_effect(EFFECT_NAME, handle_effect, "tick")
climate_api.register_effect(EFFECT_NAME, stop_effect, "stop")
climate_api.set_effect_cycle(EFFECT_NAME, climate_api.LONG_CYCLE)
climate_api.set_effect_cycle(EFFECT_NAME, climate_api.MEDIUM_CYCLE)

View File

@ -27,25 +27,33 @@ climate_mod.settings = {
wind = get_setting_bool("wind", true),
seasons = get_setting_bool("seasons", true),
fahrenheit = get_setting_bool("fahrenheit", false),
block_updates = get_setting_bool("block_updates", true),
heat = get_setting_number("heat_base", 0),
humidity = get_setting_number("humidity_base", 0),
time_spread = get_setting_number("time_spread", 1),
particle_count = get_setting_number("particle_count", 1)
particle_count = get_setting_number("particle_count", 1),
tick_speed = get_setting_number("tick_speed", 1),
volume = get_setting_number("volume", 1)
}
-- initiate empty registers
climate_mod.weathers = {}
climate_mod.effects = {}
climate_mod.cycles = {}
climate_mod.global_environment = {}
climate_mod.global_influences = {}
climate_mod.influences = {}
climate_mod.current_weather = {}
climate_mod.current_effects = {}
climate_mod.forced_weather = {}
climate_mod.forced_wind = nil
-- import core API
climate_mod.state = dofile(modpath .. "/lib/datastorage.lua")
climate_api = dofile(modpath .. "/lib/api.lua")
climate_api.utility = dofile(modpath .. "/lib/api_utility.lua")
climate_api.skybox = dofile(modpath .. "/lib/skybox_merger.lua")
dofile(modpath .. "/lib/influences.lua")
climate_api.environment = dofile(modpath .. "/lib/environment.lua")
--climate_api = dofile(modpath .. "/lib/influences.lua")
climate_mod.world = dofile(modpath .. "/lib/world.lua")

View File

@ -17,7 +17,7 @@ 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("warning", "[Climate API] Invalid effect handler type: " .. htype)
minetest.log("warning", "[Climate API] Effect " .. dump(name) .. " uses invalid callback type: " .. dump(htype))
return
end
-- create effect handler registry if not existent yet
@ -33,11 +33,17 @@ function api.set_effect_cycle(name, cycle)
climate_mod.cycles[name].timespan = cycle
end
--[[function api.register_influence(name, func)
function api.register_global_influence(name, func)
climate_mod.global_influences[name] = func
end
function api.register_influence(name, func)
climate_mod.influences[name] = func
end]]
end
function api.register_abm(config)
if not climate_mod.settings.block_updates then return end
local conditions = config.conditions
local action = config.action
local pos_override = config.pos_override
@ -52,19 +58,9 @@ function api.register_abm(config)
return action(pos, node, env)
end
minetest.log(dump2(env, "env"))
minetest.log(dump2(conditions, "conditions"))
for condition, goal in pairs(conditions) do
local value = env[condition:sub(5)]
if condition:sub(1, 4) == "min_" then
if type(value) == "nil" or goal > value then return end
elseif condition:sub(1, 4) == "max_" then
if type(value) == "nil" or goal <= value then return end
else
value = env[condition]
if type(value) == "nil" or goal ~= value then return end
end
local is_applicable = climate_mod.trigger.test_condition(condition, env, goal)
if not is_applicable then return end
end
return action(pos, node, env)
end

View File

@ -77,6 +77,32 @@ minetest.register_chatcommand("set_humidity", {
end
})
minetest.register_chatcommand("set_wind", {
params = "<wind>",
description = "Override the weather algorithm's windspeed",
privs = { weather = true },
func = function(playername, param)
if param == nil or param == "" then
minetest.chat_send_player(playername, "Provide a number to modify the base humidity")
return
end
local arguments = {}
for w in param:gmatch("%S+") do table.insert(arguments, w) end
local wind_x = arguments[1]
local wind_z = arguments[2]
if wind_x == "auto" then
climate_mod.forced_wind = nil
else
climate_mod.forced_wind = vector.new({
x = tonumber(wind_x),
y = 0,
z = tonumber(wind_z)
})
end
minetest.chat_send_player(playername, "Wind changed")
end
})
minetest.register_chatcommand("weather_settings", {
description = "Print the active Climate API configuration",
func = function(playername)
@ -130,3 +156,15 @@ minetest.register_chatcommand("weather_status", {
end
end
})
minetest.register_chatcommand("weather_influences", {
description = "Prints which weather influences cause your current weather",
func = function(playername)
minetest.chat_send_player(playername, "Current influences rules:\n================")
local player = minetest.get_player_by_name(playername)
local influences = climate_mod.trigger.get_player_environment(player)
for influence, value in pairs(influences) do
minetest.chat_send_player(playername, dump2(value, influence))
end
end
})

View File

@ -5,13 +5,6 @@ local function get_heat_time()
return climate_api.utility.normalized_cycle(time) * 0.6 + 0.7
end
local function get_heat_calendar()
-- European heat center in August instead of June
local day = minetest.get_day_count()
local progression = ((day + 61) % 365) / 365
return climate_api.utility.normalized_cycle(progression) * 0.6 + 0.7
end
local function get_heat_height(y)
return climate_api.utility.rangelim((-y + 10) / 15, -10, 10)
end
@ -21,9 +14,8 @@ function environment.get_heat(pos)
local biome = minetest.get_heat(pos)
local height = get_heat_height(pos.y)
local time = get_heat_time()
local date = get_heat_calendar()
local random = climate_mod.state:get_float("heat_random");
return (base + biome + height) * time * date * random
return (base + biome + height) * time * random
end
function environment.get_humidity(pos)
@ -31,16 +23,18 @@ function environment.get_humidity(pos)
local biome = minetest.get_humidity(pos)
local random = climate_mod.state:get_float("humidity_random");
local random_base = climate_mod.state:get_float("humidity_base");
--[[for _, player in ipairs(minetest.get_connected_players()) do
local pname = player:get_player_name()
minetest.chat_send_player(pname, dump2(biome, "biome"))
minetest.chat_send_player(pname, dump2(random_base, "random_base"))
minetest.chat_send_player(pname, dump2(random, "random"))
minetest.chat_send_player(pname, dump2((base + biome * 0.7 + random_base * 0.3) * random, "total"))
end]]
return (base + biome * 0.7 + random_base * 0.3) * random
end
function environment.get_wind()
if climate_mod.forced_wind ~= nil then
return climate_mod.forced_wind
end
local wind_x = climate_mod.state:get_float("wind_x")
local wind_z = climate_mod.state:get_float("wind_z")
return vector.new({ x = wind_x, y = 0, z = wind_z })
end
function environment.get_weather_presets(player)
local pname = player:get_player_name()
local weathers = climate_mod.current_weather[pname]

View File

@ -1,30 +1,50 @@
climate_api.register_influence("heat", function(player)
return climate_mod.get_heat(player:get_pos())
climate_api.register_influence("heat", function(pos)
return climate_api.environment.get_heat(pos)
end)
climate_api.register_influence("humidity", function(player)
return climate_mod.get_humidity(player:get_pos())
climate_api.register_influence("base_heat", function(pos)
return minetest.get_heat(pos)
end)
climate_api.register_influence("windspeed", function(player)
local wind_x = climate_mod.state:get_float("wind_x")
local wind_z = climate_mod.state:get_float("wind_z")
return vector.length({x = wind_x, y = 0, z = wind_z})
climate_api.register_influence("humidity", function(pos)
return climate_api.environment.get_humidity(pos)
end)
climate_api.register_influence("wind_x", function(player)
return climate_mod.state:get_float("wind_x")
climate_api.register_influence("base_humidity", function(pos)
return minetest.get_humidity(pos)
end)
climate_api.register_influence("wind_z", function(player)
return climate_mod.state:get_float("wind_z")
climate_api.register_influence("biome", function(pos)
local data = minetest.get_biome_data(pos)
local biome = minetest.get_biome_name(data.biome)
return biome
end)
climate_api.register_influence("height", function(player)
local ppos = player:get_pos()
return ppos.y
climate_api.register_global_influence("windspeed", function()
local wind = climate_api.environment.get_wind()
return vector.length(wind)
end)
climate_api.register_influence("light", function(player)
return minetest.env:get_node_light(player:get_pos(), 0.5)
climate_api.register_global_influence("wind_yaw", function()
local wind = climate_api.environment.get_wind()
if vector.length(wind) == 0 then return 0 end
return minetest.dir_to_yaw(wind)
end)
climate_api.register_influence("height", function(pos)
return pos.y
end)
climate_api.register_influence("light", function(pos)
pos = vector.add(pos, {x = 0, y = 1, z = 0})
return minetest.env:get_node_light(pos)
end)
climate_api.register_influence("daylight", function(pos)
pos = vector.add(pos, {x = 0, y = 1, z = 0})
return minetest.env:get_node_light(pos, 0.5)
end)
climate_api.register_global_influence("time", function()
return minetest.get_timeofday()
end)

View File

@ -1,5 +1,5 @@
local GSCYCLE = 0.03
local WORLD_CYCLE = 2
local GSCYCLE = 0.03 * climate_mod.settings.tick_speed
local WORLD_CYCLE = 2 * climate_mod.settings.tick_speed
local gs_timer = 0
local world_timer = 0
@ -14,13 +14,15 @@ minetest.register_globalstep(function(dtime)
world_timer = 0
climate_mod.state:set_float("noise_timer", noise_timer)
climate_mod.world.update_status(noise_timer)
climate_mod.global_environment = climate_mod.trigger.get_global_environment()
end
local previous_effects = table.copy(climate_mod.current_effects)
local current_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
local cycle = climate_mod.cycles[name].timespan * climate_mod.settings.tick_speed
if cycle < climate_mod.cycles[name].timer + dtime then
climate_mod.cycles[name].timer = 0
climate_mod.current_effects[name] = current_effects[name]
climate_mod.trigger.call_handlers(name, current_effects[name], previous_effects[name])

109
lib/skybox_merger.lua Normal file
View File

@ -0,0 +1,109 @@
local default_sky = {
sky_data = {
base_color = nil,
type = "regular",
textures = nil,
clouds = true,
sky_color = {
day_sky = "#8cbafa",
day_horizon = "#9bc1f0",
dawn_sky = "#b4bafa",
dawn_horizon = "#bac1f0",
night_sky = "#006aff",
night_horizon = "#4090ff",
indoors = "#646464",
fog_tint_type = "default"
}
},
cloud_data = {
density = 0.4,
color = "#fff0f0e5",
ambient = "#000000",
height = 120,
thickness = 16,
speed = {x=0, z=-2}
},
sun_data = {
visible = true,
texture = "sun.png",
tonemap = "sun_tonemap.png",
sunrise = "sunrisebg.png",
sunrise_visible = true,
scale = 1
},
moon_data = {
visible = true,
texture = "moon.png",
tonemap = "moon_tonemap.png",
scale = 1
},
star_data = {
visible = true,
count = 1000,
star_color = "#ebebff69",
scale = 1
}
}
local skybox = {}
local layers = {}
-- from https://stackoverflow.com/a/29133654
-- merges two tables together
-- if in conflict, b will override values of a
local function 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_tables(a[k],v)
else a[k]=v end
end
end
return a
end
local function set_skybox(playername, sky)
local player = minetest.get_player_by_name(playername)
if not player.get_stars then return end
player:set_sky(sky.sky_data)
player:set_clouds(sky.cloud_data)
player:set_moon(sky.moon_data)
player:set_sun(sky.sun_data)
player:set_stars(sky.star_data)
end
function skybox.update_skybox(playername)
local p_layers = layers[playername]
local sky = table.copy(default_sky)
if p_layers == nil then p_layers = {} end
local numbered_layers = {}
for layer, values in pairs(p_layers) do
table.insert(numbered_layers, values)
end
table.sort(numbered_layers, function(left, right)
if left.priority == nil then left.priority = 1 end
if right.priority == nil then right.priority = 1 end
return left.priority < right.priority
end)
for i=1,#numbered_layers do
sky = merge_tables(sky, numbered_layers[i])
end
set_skybox(playername, sky)
end
function skybox.add_layer(playername, name, sky)
if layers[playername] == nil then layers[playername] = {} end
layers[playername][name] = sky
end
function skybox.remove_layer(playername, name)
if layers[playername] == nil or layers[playername][name] == nil then return end
layers[playername][name] = nil
end
minetest.register_on_leaveplayer(function(player)
local playername = player:get_player_name()
layers[playername] = nil
end)
return skybox

57
lib/soundloop.lua Normal file
View File

@ -0,0 +1,57 @@
local soundloop = {}
local sounds = {}
local function parse_sound(sound)
if type(sound) == "string" then
return { name = sound, gain = 1, pitch = 1 }
end
if sound.gain == nil then sound.gain = 1 end
if sound.pitch == nil then sound.pitch = 1 end
return sound
end
soundloop.play = function(player, sound, fade)
sound = parse_sound(sound)
if fade == nil then fade = 1 end
local step
local handle
local start_gain
if sounds[player] == nil then sounds[player] = {} end
if sounds[player][sound.name] == nil then
step = sound.gain / fade
start_gain = 0
elseif sounds[player][sound.name] ~= sound.gain then
minetest.sound_stop(sounds[player][sound.name].handle)
start_gain = sounds[player][sound.name].gain
local change = sound.gain - start_gain
step = change / fade
else
return
end
handle = minetest.sound_play(sound.name, {
to_player = player,
loop = true,
gain = 0
})
sounds[player][sound.name] = {
gain = sound.gain,
handle = handle
}
minetest.sound_fade(handle, step, sound.gain)
return handle
end
soundloop.stop = function(player, sound, fade)
sound = parse_sound(sound)
if sounds[player] == nil or sounds[player][sound.name] == nil then
return
end
if fade == nil then fade = 1 end
local handle = sounds[player][sound.name].handle
local step = -sounds[player][sound.name].gain / fade
minetest.sound_fade(handle, step, 0)
sounds[player][sound.name].gain = 0
minetest.after(fade, minetest.sound_stop, handle)
end
return soundloop

View File

@ -1,19 +1,18 @@
local trigger = {}
function trigger.get_position_environment(pos)
local wind_x = climate_mod.state:get_float("wind_x")
local wind_z = climate_mod.state:get_float("wind_z")
function trigger.get_global_environment()
local env = {}
env.pos = pos
env.height = pos.y
env.wind = vector.new(wind_x, 0, wind_z)
env.windspeed = vector.length(env.wind)
env.heat = climate_api.environment.get_heat(pos)
env.humidity = climate_api.environment.get_humidity(pos)
env.time = minetest.get_timeofday()
env.date = minetest.get_day_count()
env.light = minetest.get_node_light(vector.add(pos, vector.new({x=0,y=1,z=0})), 0.5)
for influence, func in pairs(climate_mod.global_influences) do
env[influence] = func()
end
return env
end
function trigger.get_position_environment(pos)
local env = table.copy(climate_mod.global_environment)
for influence, func in pairs(climate_mod.influences) do
env[influence] = func(pos)
end
return env
end
@ -24,15 +23,21 @@ function trigger.get_player_environment(player)
return env
end
local function test_condition(condition, env, goal)
function trigger.test_condition(condition, env, goal)
local value = env[condition:sub(5)]
if condition:sub(1, 4) == "min_" then
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")
elseif condition:sub(1, 4) == "has_" then
if type(value) == "nil" then return false end
for _, g in ipairs(goal) do
if value == g then return true end
end
return false
else
value = env[condition]
return type(value) ~= "nil" and goal == value
end
end
@ -45,7 +50,7 @@ local function is_weather_active(player, weather, env)
return config.conditions(env)
end
for condition, goal in pairs(config.conditions) do
if not test_condition(condition, env, goal) then
if not trigger.test_condition(condition, env, goal) then
return false
end
end
@ -145,7 +150,6 @@ function trigger.call_handlers(name, effect, prev_effect)
-- remaining table lists ending effects
if has_stops then
minetest.log(dump2(name, "AAAAAAAAAAA"))
for _, handler in ipairs(climate_mod.effects[name]["stop"]) do
handler(stops)
end

View File

@ -2,11 +2,11 @@ local world = {}
local WIND_SPREAD = 600
local WIND_SCALE = 2
local HEAT_SPREAD = 200
local HEAT_SPREAD = 400
local HEAT_SCALE = 0.3
local HUMIDITY_SPREAD = 60
local HUMIDITY_SPREAD = 150
local HUMIDITY_SCALE = 0.5
local HUMIDITY_BASE_SPREAD = 600
local HUMIDITY_BASE_SPREAD = 800
local HUMIDITY_BASE_SCALE = 40
local nobj_wind_x

View File

@ -1,7 +1,7 @@
name = climate_api
title = Climate API
author = TestificateMods
release = 1
release = 2
optional_depends = skylayer, player_monoids, playerphysics
description = """
A powerful engine for weather presets and visual effects.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -1,11 +1,64 @@
climate_api_particles (Show particle effects) bool true
climate_api_skybox (Allow weather effects to modify the skybox) bool true
climate_api_sound (Allow weather presets to play ambient sounds) bool true
climate_api_hud_overlay (Allow weather presets to display a HUD overlay) bool true
climate_api_wind (Allow wind to angle rainfall) bool true
climate_api_seasons (Change global temperature based on an annual cycle) bool true
climate_api_heat_base (Global base temperature) float 0
climate_api_humidity_base (Global base humidity) float 0
climate_api_time_spread (Regulates how quickly the weather changes) float 1 0.1 10
[Performance]
# This value regulates how often weather presets are recalculated.
# Higher values will result in smoother transitions between effects as well as faster response times to traveling players.
# Lower values will significantly increase overall performance at the cost of rougher looking effects.
climate_api_tick_speed (Update speed of weather effects) float 1 0.1 10
# This value regulated how many particles will be spawned.
# A value of 1 will use the recommended amount of particles.
# Lower values can possible increase performance.
climate_api_particle_count (Multiplicator for used particles) float 1 0.1 2
# If set to true, weather packs are allowed to register node update handlers.
# These can be used to dynamically place snow layers, melt ice, or hydrate soil.
climate_api_block_updates (Dynamically modify nodes) bool true
[Visuals]
# If set to true, weather effects (like rain) are allowed to render particles.
# Deactivating this feature will prevent some presets from being visible.
# For performance considerations it is recommended to decrease the amount of particles instead.
climate_api_particles (Show particle effects) bool true
# If set to true, weather effects are allowed to modify a player's sky.
# This includes skybox, sun, moon, and clouds (also used for fog effects).
# Running this mod on Minetest 5.1.2 or earlier versions will automatically disable this feature.
climate_api_skybox (Override the skybox) bool true
# If set to true, weather effects are allowed to render an image on top of the gameplay.
# This is usually an optional effect used to increase immersion (like a frozen-over camera in a snow storm).
climate_api_hud_overlay (Display HUD overlays) bool true
[Environment]
# This value will be added to all biome's base temperatures before applying random modifiers.
# Every unit here will increase the global base heat by one degree Fahrenheit.
# Negative values will cool down global base heat respectively.
climate_api_heat_base (Global base temperature) float 0
# This value will be added to all biome's base humidity before applying random modifiers.
# Every unit here will increase the global base humidity by one percent.
# Negative values will dry up global base humidity respectively.
climate_api_humidity_base (Global base humidity) float 0
# This value regulates how quickly environment factors like heat, humidity and wind are changing.
# A value of 2 will double the speed at which weather presets change.
# A value of 0.5 will half the speed respectively.
climate_api_time_spread (Time rate of weather changes) float 1 0.1 10
[Preferences]
# If set to true, temperature information in /weather command will be displayed in Fahrenheit.
climate_api_fahrenheit (Show degrees in Fahrenheit instead of Celsius) bool false
# If set to true, weather effects are allowed to play sound loops.
# You can also adjust sound levels instead of deactivating this feature completely.
# Setting this value to false will be slightly more performant than setting the volume to zero.
climate_api_sound (Play ambient sound loops) bool true
# This value regulates overall sound volume.
# A value of 2 will double the volume whereas a value of 0.5 will reduce the volume by half.
climate_api_volume (Volume of sound effects) float 1 0 10

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

BIN
textures/moon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
textures/sun.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB