Add files

master
random-geek 2020-08-19 21:55:48 -07:00
parent b32265cba2
commit b474711fb0
5 changed files with 524 additions and 1 deletions

View File

@ -1 +1,14 @@
# auroras
# Auroras
Auroras mod for Minetest. Adds northern/southern lights at night.
Since the Minetest world doesn't exactly have polar regions, auroras will appear in any sufficiently cold place.
Be patient—auroras aren't always visible!
### Features:
- Different auroras every night, with semi-realistic colors.
- Players near each other see similar colors.
- Configurable, no media/assets.
This mod works with Minetest 5.2.0 or later, and is compatible with TestificateMods' Climate API/Regional Weather mods.
It probably won't work with other mods/games that regularly change the skybox.

471
init.lua Normal file
View File

@ -0,0 +1,471 @@
auroras = {}
--[[
Various constants and defaults. Will be overridden by settings, if present.
]]
auroras.UPDATE_INTERVAL = 1.5
auroras.TIME_START = 0.82
auroras.TIME_END = 0.18
auroras.TRANSITION_TIME = 0.02
auroras.HEIGHT_MIN = -24.0
auroras.HEIGHT_TRANSITION = 8.0
auroras.HEAT_MIN = 0
auroras.HEAT_MAX = 25
auroras.HUM_MIN = 0
auroras.HUM_MAX = 100
auroras.BIOME_TRANSITION = 5
-- Warning: Changing offset, scale, octaves, or persistance could result in
-- uneven noise!
auroras.NOISE_PARAMS = {
offset = 0,
scale = 1,
spread = {x=800, y=60, z=800},
octaves = 2,
persistence = 0.5,
lacunarity = 4.0,
flags = "eased",
}
auroras.NOISE_MIN = -0.2
auroras.NOISE_TRANSITION = 0.20
auroras.DAY_NIGHT_RATIO = 0.25
auroras.COLORS = {
"#14cca1",
"#22e6b2",
"#33ffc6",
"#49f2ac",
"#5ce673",
"#82f249",
"#6fd916",
"#a0f725",
"#c3ff19",
"#a9f73b",
"#d2f230",
"#d3e043",
"#dd564b",
"#d93648",
"#b3478e",
}
-- Settings for when using Climate API.
auroras.CLIMATE_API_SKYBOX_PRIORITY = 40
-- Use interval matching Climate API's skybox update frequency.
auroras.CLIMATE_API_UPDATE_INTERVAL = 2.0
-- Default sky colors, see minetest/src/skyparams.h.
auroras.BASE_SKY = {r=0, g=107, b=255}
auroras.BASE_HORIZON = {r=64, g=144, b=255}
-- Default nighttime day-night ratio, see minetest/src/daynightratio.h.
auroras.BASE_DAY_NIGHT_RATIO = 0.175
auroras.SETTING_PREFIX = "auroras_"
--[[
End of constant definitions
]]
auroras.USE_CLIMATE_API = minetest.get_modpath("climate_api") ~= nil
auroras.player_data = {}
auroras.was_night = false
--[[
Function definitions
]]
function auroras.get_settings()
local function get_num(key)
local val = minetest.settings:get(auroras.SETTING_PREFIX .. key)
return tonumber(val) -- Will return nil if val is nil.
end
local a = auroras
if a.USE_CLIMATE_API then
a.UPDATE_INTERVAL = a.CLIMATE_API_UPDATE_INTERVAL
else
a.UPDATE_INTERVAL = get_num("update_interval") or a.UPDATE_INTERVAL
end
a.HEIGHT_MIN = get_num("height_min") or a.HEIGHT_MIN
a.HEAT_MIN = get_num("heat_min") or a.HEAT_MIN
a.HEAT_MAX = get_num("heat_max") or a.HEAT_MAX
a.HUM_MIN = get_num("heat_min") or a.HUM_MIN
a.HUM_MAX = get_num("heat_max") or a.HUM_MAX
a.DAY_NIGHT_RATIO = get_num("day_night_ratio") or a.DAY_NIGHT_RATIO
a.NOISE_PARAMS.spread.y = get_num("time_spread") or a.NOISE_PARAMS.spread.y
a.NOISE_MIN = get_num("noise_threshold") or a.NOISE_MIN
a.BIOME_TRANSITION = get_num("biome_transition") or a.BIOME_TRANSITION
end
function auroras.clamp(x, low, high)
return math.max(low, math.min(high, x))
end
function auroras.interp_colors(x, y, fac)
local invFac = 1.0 - fac
return {
r = math.floor(x.r * invFac + y.r * fac),
g = math.floor(x.g * invFac + y.g * fac),
b = math.floor(x.b * invFac + y.b * fac)
}
end
function auroras.parse_hex_color(hex)
if type(hex) == "string" and
hex:len() == 7 and
hex:sub(1, 1) == "#" then
local tab = {
r = tonumber(hex:sub(2, 3), 16),
g = tonumber(hex:sub(4, 5), 16),
b = tonumber(hex:sub(6, 7), 16)
}
if tab.r ~= nil and
tab.g ~= nil and
tab.b ~= nil then
return tab
end
end
return nil -- Invalid color
end
function auroras.init_colors()
-- Wrap in a function so we can return.
(function()
local colorList = minetest.settings:get(
auroras.SETTING_PREFIX .. "colors")
if colorList ~= nil then
local colors = {}
local idx = 0
-- Split at commas and spaces.
for hexCol in colorList:gmatch("[^%s%,]+") do
local tCol = auroras.parse_hex_color(hexCol)
if tCol == nil then
minetest.log("error", "[auroras] Invalid hex color: " ..
dump(hexCol) .. ". Using default colors.")
return
else
colors[idx] = tCol
idx = idx + 1
end
end
if idx < 2 then
minetest.log("error",
"[auroras] At least two colors are required. " ..
"Using default colors.")
return
end
auroras.COLOR_LUT = colors
end
end)()
-- Color list from settings was nonexistent or malformed.
if auroras.COLOR_LUT == nil then
auroras.COLOR_LUT = {}
local idx = 0
for i, hexCol in pairs(auroras.COLORS) do
local tCol = auroras.parse_hex_color(hexCol)
if tCol ~= nil then
auroras.COLOR_LUT[idx] = tCol
idx = idx + 1
end
end
end
end
function auroras.init_noise()
local params = auroras.NOISE_PARAMS
params.seed = os.time()
auroras.noise = PerlinNoise(params)
end
function auroras.get_noise(pos)
-- Returns noise based on x/z position and time.
return auroras.noise:get_3d({
x = pos.x,
-- Mod by 2^20 because the noise generator can't handle large numbers.
y = os.clock() % (2^20),
z = pos.z
})
end
function auroras.noise_curve(x)
-- Map values onto an s-curve similar to a smoothstep function.
-- Without this, numbers close to 1 almost never appear.
-- Equivalent to -0.5x^3 + 1.5x
return (-0.5 * x * x + 1.5) * x
end
function auroras.init_biome_params()
-- Add biome transition so that strength will still be 1 at the limits.
auroras.HEAT_MEAN = (auroras.HEAT_MIN + auroras.HEAT_MAX) * 0.5
auroras.HEAT_SPREAD = (auroras.HEAT_MAX - auroras.HEAT_MIN) * 0.5 +
auroras.BIOME_TRANSITION
auroras.HUM_MEAN = (auroras.HUM_MIN + auroras.HUM_MAX) * 0.5
auroras.HUM_SPREAD = (auroras.HUM_MAX - auroras.HUM_MIN) * 0.5 +
auroras.BIOME_TRANSITION
end
function auroras.get_local_strength(pos)
-- No auroras underground!
heightStrength = auroras.clamp(
(pos.y - auroras.HEIGHT_MIN) / auroras.HEIGHT_TRANSITION, 0.0, 1.0)
-- Avoid getting biome data if we don't have to.
if heightStrength == 0.0 then
return 0.0
end
local bioData = minetest.get_biome_data(pos)
local heatStrength = auroras.clamp(
(-math.abs(bioData.heat - auroras.HEAT_MEAN) + auroras.HEAT_SPREAD) /
auroras.BIOME_TRANSITION,
0.0, 1.0
)
local humStrength = auroras.clamp(
(-math.abs(bioData.humidity - auroras.HUM_MEAN) + auroras.HUM_SPREAD) /
auroras.BIOME_TRANSITION,
0.0, 1.0
)
return heightStrength * heatStrength * humStrength
end
function auroras.init_time_params()
auroras.TIME_MEAN = (auroras.TIME_START + auroras.TIME_END) * 0.5
auroras.TIME_SPREAD = (auroras.TIME_START - auroras.TIME_END) * 0.5
end
function auroras.get_time_strength()
local timeOfDay = minetest.get_timeofday()
return auroras.clamp(
(math.abs(timeOfDay - auroras.TIME_MEAN) - auroras.TIME_SPREAD) /
auroras.TRANSITION_TIME,
0.0, 1.0
)
end
function auroras.set_day_night_ratio(player, dnr)
player:override_day_night_ratio(dnr)
end
function auroras.set_sky(player, sky)
if auroras.USE_CLIMATE_API then
-- Just save the sky.
local pName = player:get_player_name()
auroras.player_data[pName].current_sky = sky
else
player:set_sky(sky)
end
end
function auroras.save_sky(player)
local params = {player:get_sky()}
local sky = {
base_color = params[1],
type = params[2],
textures = params[3],
clouds = params[4],
sky_color = player:get_sky_color()
}
auroras.player_data[player:get_player_name()].orig_sky = sky
end
function auroras.restore_sky(player)
local pName = player:get_player_name()
if auroras.player_data[pName] == nil or
auroras.player_data[pName].orig_sky == nil then
return
end
auroras.set_sky(player, auroras.player_data[pName].orig_sky)
auroras.player_data[pName].orig_sky = nil
end
function auroras.get_base_sky_colors(playerData)
if auroras.USE_CLIMATE_API or not playerData.orig_sky then
return auroras.BASE_SKY, auroras.BASE_HORIZON
else
local origSkyColor = playerData.orig_sky.sky_color
return origSkyColor.night_sky or auroras.BASE_SKY,
origSkyColor.night_horizon or auroras.BASE_HORIZON
end
end
function auroras.do_update()
local timeStrength = auroras.get_time_strength()
local isNight = timeStrength > 0.0
-- Don't waste time on midday calls.
if not isNight and not auroras.was_night then
return
end
for _, player in ipairs(minetest.get_connected_players()) do
local pName = player:get_player_name()
if auroras.player_data[pName] == nil then
auroras.player_data[pName] = {
was_visible = false
}
end
local isVisible = false
local pos, biomeStrength, noiseVal
-- Determine if an aurora is visible for this player.
if isNight then
pos = player:get_pos()
biomeStrength = auroras.get_local_strength(pos)
if biomeStrength > 0.0 then
noiseVal = auroras.noise_curve(auroras.get_noise(pos))
isVisible = noiseVal > auroras.NOISE_MIN
end
end
if isVisible then
-- Save sky before changing anything.
if not auroras.USE_CLIMATE_API and
auroras.player_data[pName].orig_sky == nil then
auroras.save_sky(player)
end
-- Transform noise for more or less aurora time.
noiseVal = (noiseVal - auroras.NOISE_MIN) / (1 - auroras.NOISE_MIN)
-- Determine strength of aurora based on time, biome, and natural
-- fluctuations (noise).
local noiseStrength = auroras.clamp(
noiseVal / auroras.NOISE_TRANSITION, 0.0, 1.0)
local strength = timeStrength * biomeStrength * noiseStrength
-- Get aurora color based on strength/noise.
local fIdx = math.min(noiseVal, 1.0) * #auroras.COLOR_LUT
local lowIdx = math.floor(fIdx)
local highIdx = math.ceil(fIdx)
local fac = fIdx - lowIdx
local baseSky, baseHorizon =
auroras.get_base_sky_colors(auroras.player_data[pName])
local skyColor = auroras.interp_colors(
baseSky,
auroras.interp_colors(
auroras.COLOR_LUT[lowIdx],
auroras.COLOR_LUT[highIdx],
fac
),
strength
)
-- Set all sky colors for now, since gamma, etc. affects which is used.
auroras.set_sky(player, {
type = "regular",
sky_color = {
night_sky = skyColor,
dawn_sky = skyColor,
day_sky = skyColor,
night_horizon = baseHorizon,
dawn_horizon = baseHorizon,
day_horizon = baseHorizon,
}
})
-- Set day/night ratio to lighten the sky during auroras.
local dnr = auroras.BASE_DAY_NIGHT_RATIO * (1 - strength) +
auroras.DAY_NIGHT_RATIO * strength
if dnr ~= auroras.player_data[pName].last_dnr then
auroras.set_day_night_ratio(player, dnr)
auroras.player_data[pName].last_dnr = dnr
end
elseif auroras.player_data[pName].was_visible then
-- Was visible, but not any more.
auroras.restore_sky(player)
auroras.set_day_night_ratio(player, nil)
end
auroras.player_data[pName].was_visible = isVisible
end
auroras.was_night = isNight
end
function auroras.update()
auroras.do_update()
minetest.after(auroras.UPDATE_INTERVAL, auroras.update)
end
function auroras.on_player_leave(player, timed_out)
auroras.player_data[player:get_player_name()] = nil
end
-- Functions for Climate API support
function auroras.climate_api_is_active(params)
if params.player then
pName = params.player:get_player_name()
if auroras.player_data[pName] and
auroras.player_data[pName].was_visible then
return true
end
end
return false
end
function auroras.climate_api_get_effects(params)
data = {}
if params.player then
pName = params.player:get_player_name()
if auroras.player_data[pName] and
auroras.player_data[pName].current_sky then
data["climate_api:skybox"] = {
sky_data = auroras.player_data[pName].current_sky,
priority = auroras.CLIMATE_API_SKYBOX_PRIORITY
}
end
end
return data
end
--[[
End of function definitions
]]
do
auroras.get_settings()
auroras.init_colors()
auroras.init_noise()
auroras.init_biome_params()
auroras.init_time_params()
-- TODO: Faster switching when player joins, time change, etc?
minetest.register_on_leaveplayer(auroras.on_player_leave)
-- If climate_api is enabled, register auroras as a weather.
if auroras.USE_CLIMATE_API then
climate_api.register_weather("auroras:aurora",
auroras.climate_api_is_active,
auroras.climate_api_get_effects)
end
minetest.after(auroras.UPDATE_INTERVAL, auroras.update)
end

3
mod.conf Normal file
View File

@ -0,0 +1,3 @@
name = auroras
description = Adds auroras (northern/southern lights) at night in cold places.
optional_depends = climate_api

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 KiB

36
settingtypes.txt Normal file
View File

@ -0,0 +1,36 @@
# Seconds between each update. Increase this number if load is too high.
# When using Climate API, update interval will default to 2.0 s.
auroras_update_interval (Update interval) float 1.5 0.5 5.0
# Minimum Y-value for auroras to appear.
auroras_height_min (Min. height) int -24 -31000 31000
# Minimum heat for auroras. Actual value will be lower to allow for smooth transitions.
auroras_heat_min (Min. heat) int 0 0 100
# Maximum heat for auroras. Actual value will be higher to allow for smooth transitions.
auroras_heat_max (Max. heat) int 25 0 100
# Minimum humidity for auroras. Actual value will be lower to allow for smooth transitions.
auroras_hum_min (Min. humidity) int 0 0 100
# Maximum humidity for auroras. Actual value will be higher to allow for smooth transitions.
auroras_hum_max (Max. humidity) int 100 0 100
# Hex colors of auroras. Colors at the towards the beginning will be blended with the default sky.
auroras_colors (Aurora colors) string #14cca1 #22e6b2 #33ffc6 #49f2ac #5ce673 #82f249 #6fd916 #a0f725 #c3ff19 #a9f73b #d2f230 #d3e043 #dd564b #d93648 #b3478e
[Advanced]
# Controls overall brightness during auroras. 0.175 is night, 1.0 is full daylight.
auroras_day_night_ratio (Day/night ratio) float 0.25 0.0 1.0
# Controls how quickly auroras change color. Higher values result in slower transitions.
auroras_time_spread (Noise time spread) int 60 20 600
# Minimum random noise value for auroras to appear, between -1 and 1.
# Lower values result in more auroras and also slower color transitions.
auroras_noise_threshold (Noise threshold) float -0.2 -1.0 0.9
# Controls how smoothly auroras start/stop at biome transitions.
auroras_biome_transition (Biome transition) int 5 1 50