Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
1057342ac6 | |||
e24b0340e5 | |||
c9d0cfca21 | |||
d1bdf92937 | |||
0c5c9f0d15 | |||
a6cfca7745 | |||
241a0a82c1 | |||
18045b1943 |
@ -8,8 +8,12 @@ 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.
|
||||
|
||||
## Assets
|
||||
- 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
|
||||
- Used texture pack: Polygonia (128px edition) *CC BY-SA (4.0)* by Lokrates. See https://forum.minetest.net/viewtopic.php?f=4&t=19043
|
24
ROADMAP.md
@ -1,30 +1,19 @@
|
||||
# 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)
|
||||
- Write helpful README
|
||||
|
||||
## 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
|
||||
- Ability to register global influences that are the same for any position
|
||||
|
||||
## 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 +41,4 @@
|
||||
- standing near fire will warm up
|
||||
- craftable warm clothes
|
||||
- metal armor will worsen heat issues
|
||||
- A flag indicating wind direction
|
@ -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
|
||||
|
@ -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()
|
||||
})
|
||||
|
@ -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)
|
@ -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)
|
8
init.lua
@ -27,10 +27,13 @@ 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
|
||||
@ -41,11 +44,14 @@ 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")
|
||||
|
22
lib/api.lua
@ -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,13 @@ function api.set_effect_cycle(name, cycle)
|
||||
climate_mod.cycles[name].timespan = cycle
|
||||
end
|
||||
|
||||
--[[function api.register_influence(name, func)
|
||||
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 +54,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
|
||||
|
@ -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
|
||||
})
|
@ -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]
|
||||
|
@ -1,30 +1,54 @@
|
||||
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_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_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_influence("time", function(_)
|
||||
return minetest.get_timeofday()
|
||||
end)
|
||||
|
||||
climate_api.register_influence("day_count", function(_)
|
||||
return minetest.get_day_count()
|
||||
end)
|
@ -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
|
||||
@ -20,7 +20,8 @@ minetest.register_globalstep(function(dtime)
|
||||
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
@ -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
@ -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
|
@ -4,7 +4,7 @@ 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")
|
||||
|
||||
local env = {}
|
||||
--[[local env = {}
|
||||
env.pos = pos
|
||||
env.height = pos.y
|
||||
env.wind = vector.new(wind_x, 0, wind_z)
|
||||
@ -13,7 +13,11 @@ function trigger.get_position_environment(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)
|
||||
env.light = minetest.get_node_light(vector.add(pos, vector.new({x=0,y=1,z=0})), 0.5)]]
|
||||
local env = {}
|
||||
for influence, func in pairs(climate_mod.influences) do
|
||||
env[influence] = func(pos)
|
||||
end
|
||||
return env
|
||||
end
|
||||
|
||||
@ -24,15 +28,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 +55,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 +155,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
|
||||
|
@ -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
|
||||
|
2
mod.conf
@ -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.
|
||||
|
BIN
screenshot.2.png
Before Width: | Height: | Size: 2.0 MiB |
BIN
screenshot.3.png
Before Width: | Height: | Size: 2.0 MiB |
BIN
screenshot.png
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.6 MiB |
@ -1,11 +1,62 @@
|
||||
[Features]
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
# 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 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
[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
|
||||
climate_api_time_spread (Regulates how quickly the weather changes) float 1 0.1 10
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
[Preferences]
|
||||
|
||||
# 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, temperature information in /weather command will be displayed in Fahrenheit.
|
||||
climate_api_fahrenheit (Show degrees in Fahrenheit instead of Celsius) bool false
|
||||
|
||||
# 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
|
||||
|
Before Width: | Height: | Size: 2.2 MiB |
BIN
textures/moon.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
textures/sun.png
Normal file
After Width: | Height: | Size: 1.2 KiB |