climatez/engine.lua
2021-01-26 12:19:12 +01:00

308 lines
8.6 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local modpath = ...
local climatez = {}
climatez.wind = {}
climatez.climates = {}
climatez.players = {}
climatez.settings = {}
--Settings
local settings = Settings(modpath .. "/climatez.conf")
climatez.settings.climate_min_height = tonumber(settings:get("climate_min_height"))
climatez.settings.climate_change_ratio = tonumber(settings:get("climate_change_ratio"))
climatez.settings.radius = tonumber(settings:get("climate_radius"))
climatez.settings.climate_duration = tonumber(settings:get("climate_duration"))
climatez.settings.duration_random_ratio = tonumber(settings:get("climate_duration_random_ratio"))
climatez.settings.climate_rain_sound = settings:get_bool("climate_rain_sound")
local climate_max_height = tonumber(minetest.settings:get('cloud_height', true)) or 120
local check_light = minetest.is_yes(minetest.settings:get_bool('light_roofcheck', true))
--Helper Functions
local function player_inside_climate(player_pos)
--check altitude
if (player_pos.y < climatez.settings.climate_min_height) or (player_pos.y > climate_max_height) then
return false
end
--check if on water
local node_name = minetest.get_node(player_pos).name
if minetest.registered_nodes[node_name] and (
minetest.registered_nodes[node_name]["liquidtype"] == "source" or
minetest.registered_nodes[node_name]["liquidtype"] == "flowing") then
return false
end
--If sphere's centre coordinates is (cx,cy,cz) and its radius is r,
--then point (x,y,z) is in the sphere if (xcx)2+(ycy)2+(zcz)2<r2.
for i, climate in ipairs(climatez.climates) do
local climate_center = climatez.climates[i].center
if climatez.settings.radius > math.sqrt((player_pos.x - climate_center.x)^2+
(player_pos.y - climate_center.y)^2 +
(player_pos.z - climate_center.z)^2
) then
return i
end
end
return false
end
local function has_light(minp, maxp)
local manip = minetest.get_voxel_manip()
local e1, e2 = manip:read_from_map(minp, maxp)
local area = VoxelArea:new{MinEdge=e1, MaxEdge=e2}
local data = manip:get_light_data()
local node_num = 0
local light = false
for i in area:iterp(minp, maxp) do
node_num = node_num + 1
if node_num < 5 then
if data[i] and data[i] == 15 then
light = true
break
end
else
node_num = 0
end
end
return light
end
local function array_remove(tab, idx)
tab[idx] = nil
local new_tab = {}
for _, value in pairs(tab) do
new_tab[ #new_tab+1] = value
end
return new_tab
end
--DOWNFALLS REGISTRATIONS
climatez.registered_downfalls = {}
local function register_downfall(name, def)
local new_def = table.copy(def)
climatez.registered_downfalls[name] = new_def
end
register_downfall("rain", {
min_pos = {x = -15, y = 10, z = -15},
max_pos = {x = 15, y = 10, z = 15},
falling_speed = 10,
amount = 25,
exptime = 1,
size = 1,
texture = "climatez_rain.png",
})
register_downfall("snow", {
min_pos = {x = -15, y = 10, z= -15},
max_pos = {x = 15, y = 10, z = 15},
falling_speed = 5,
amount = 15,
exptime = 7,
size = 1,
texture= "climatez_snow.png",
})
register_downfall("sand", {
min_pos = {x = -20, y = -4, z = -20},
max_pos = {x = 20, y = 4, z = 20},
falling_speed = -1,
amount = 40,
exptime = 1,
size = 1,
texture = "climatez_sand.png",
})
--WIND STUFF
local function create_wind()
local wind = {
x = math.random(0,5),
y = 0,
z = math.random(0,5)
}
return wind
end
function get_player_wind(player)
local player_pos = player:get_pos()
local climate_id = player_inside_climate(player_pos)
if climate_id then
return climatez.climates[climate_id].wind
else
return create_wind()
end
end
--CLIMATE FUNCTIONS
local rain_sound --handle for the rain sound
local function add_climate_player(player, _climate_id, _downfall)
local player_name = player:get_player_name()
climatez.players[player_name] = {
climate_id = _climate_id,
downfall = _downfall,
}
if climatez.settings.climate_rain_sound and _downfall == "rain" then
rain_sound = minetest.sound_play("climatez_rain", {
to_player = player_name,
loop = true,
gain = 1.0,
})
end
end
local function remove_climate_player(player)
local player_name = player:get_player_name()
local downfall = climatez.players[player_name].downfall
if rain_sound and climatez.settings.climate_rain_sound and downfall == "rain" then
minetest.sound_stop(rain_sound)
end
climatez.players[player_name] = nil
end
local function create_climate(player)
--get some data
local player_pos = player:get_pos()
local biome_data = minetest.get_biome_data(player_pos)
local biome_heat = biome_data.heat
local biome_humidity = biome_data.humidity
local downfall
if biome_heat >= 20 and biome_humidity >= 50 then
downfall = "rain"
elseif biome_heat >= 50 and biome_humidity <= 20 then
downfall = "sand"
elseif biome_heat < 20 then
downfall = "snow"
end
if not downfall then
return
end
--create wind
local wind = create_wind()
--create climate
local climate_id = #climatez.climates+1
climatez.climates[climate_id] = {
center = player_pos,
downfall = downfall,
wind = wind,
}
--save the player
add_climate_player(player, climate_id, downfall)
--program climate's end
local climate_duration = climatez.settings.climate_duration
local climate_duration_random_ratio = climatez.settings.duration_random_ratio
local random_end_time = (math.random(climate_duration- (climate_duration*climate_duration_random_ratio),
climate_duration+ (climate_duration*climate_duration_random_ratio)))
minetest.after(random_end_time, function()
--remove the player
for _player_name, _climate in pairs(climatez.players) do
local _climate_id = _climate.climate_id
if _climate_id == climate_id then
local _player = minetest.get_player_by_name(_player_name)
if _player then
remove_climate_player(_player)
--minetest.chat_send_all(_player_name.." removed from climate")
end
end
end
--remove the climate
climatez.climates = array_remove(climatez.climates, climate_id)
end)
end
local function apply_climate(player, climate_id)
local player_pos = player:get_pos()
local climate = climatez.climates[climate_id]
if not climate then
remove_climate_player(player)
return
end
local player_name = player:get_player_name()
local player_downfall = climatez.players[player_name].downfall
local downfall = climatez.registered_downfalls[player_downfall]
local wind = climate.wind
local wind_pos = vector.multiply(wind, -1)
local minp = vector.add(vector.add(player_pos, downfall.min_pos), wind_pos)
local maxp = vector.add(vector.add(player_pos, downfall.max_pos), wind_pos)
--Check if in player in interiors or not
if check_light and not has_light(minp, maxp) then
return
end
local vel = {x = wind.x, y = - downfall.falling_speed, z = wind.z}
local acc = {x = 0, y = 0, z = 0}
local exp = downfall.exptime
minetest.add_particlespawner({
amount = downfall.amount, time=0.5,
minpos = minp, maxpos = maxp,
minvel = vel, maxvel = vel,
minacc = acc, maxacc = acc,
minexptime = exp, maxexptime = exp,
minsize = downfall.size, maxsize= downfall.size,
collisiondetection = true, collision_removal = true,
vertical = true,
texture = downfall.texture, playername = player:get_player_name()
})
end
--CLIMATE CORE: GLOBALSTEP
local timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime;
if timer >= 0.5 then
for _, player in ipairs(minetest.get_connected_players()) do
local _player_name = player:get_player_name()
local player_pos = player:get_pos()
local climate_id = player_inside_climate(player_pos)
local _climate= climatez.players[_player_name]
if climate_id and _climate then --if already in a climate, check if still inside it
local _climate_id = _climate.climate_id
if not climate_id == _climate_id then
remove_climate_player(player)
end
elseif climate_id and not(_climate) then --another player enter into the climate
local downfall =climatez.players[_player_name].downfall
add_climate_player(player, climate_id, downfall)
--minetest.chat_send_all(_player_name.." entered into the climate")
else --chance to create a climate
local chance = math.random(climatez.settings.climate_change_ratio)
if chance == 1 then
create_climate(player)
--minetest.chat_send_all(_player_name.." created a climate")
end
end
end
timer = 0
end
--Apply the climates to the players with climate defined
for _player_name, _climate in pairs(climatez.players) do
local _climate_id = _climate.climate_id
local player = minetest.get_player_by_name(_player_name)
if player then
apply_climate(player, _climate_id)
else
--Do not use "remove_climate_player" here, because the player could
--had abandoned the game
climatez.players[_player_name] = nil
end
end
end)