local storage = minetest.get_mod_storage() weather.registered_downfalls = {} local function check_modname_prefix(name) if name:sub(1,1) == ":" then -- If the name starts with a colon, we can skip the modname prefix -- mechanism. return name:sub(2) else -- Enforce that the name starts with the correct mod name. local modname = minetest.get_current_modname() if modname == nil then modname=name:split(":")[1] end local expected_prefix = modname .. ":" if name:sub(1, #expected_prefix) ~= expected_prefix then error("Name " .. name .. " does not follow naming conventions: " .. "\"" .. expected_prefix .. "\" or \":\" prefix required") end -- Enforce that the name only contains letters, numbers and underscores. local subname = name:sub(#expected_prefix+1) if subname:find("[^%w_]") then error("Name " .. name .. " does not follow naming conventions: " .. "contains unallowed characters") end return name end end local function set_defaults(vt,rt) for i,v in pairs(rt) do if not vt[i] then vt[i] = v end end end local default_downfall = { --minimum starting position min_pos = {x=-15, y=10, z=-15}, --maximum starting position max_pos = {x=15, y=10, z=15}, --y falling speed falling_speed = 10, --number of textures spawned amount = 15, --the texture size size = 25, --whether lightning schould be enabled enable_lightning=false, --whether to damage the player damage_player=false, --how much wind is needed to trigger the weather min_wind = 0, --stops weather disabled = false, } local default_damage = { --how many half hearts amount = 1, --chance to damage: .5 is 50% chance = 1, --after how many steps to damage time = 100 } function weather.register_downfall(id,def) local name = check_modname_prefix(id) if name == "none" then error("\"none\" means none, thanks") end if weather.registered_downfalls[name]~=nil then error(name.." is already registered") end local ndef = table.copy(def) --what the downfall looks like if not ndef.texture then error("no texture given") end set_defaults(ndef,default_downfall) --when to delete the particles if not ndef.exptime then ndef.exptime = ndef.max_pos.y / ndef.falling_speed end if ndef.damage_player then set_defaults(ndef.damage_player,default_damage) end --actually register the downfall weather.registered_downfalls[name]=ndef end function weather.disable_downfall(id,disable) local state = disable if disable == nil then state = true end weather.registered_downfalls[id].disabled = state end --[[ if minetest.get_modpath("lightning") then lightning.auto = false end function weather_mod.handle_lightning(current_weather) if not minetest.get_modpath("lightning") then return end if not current_weather then return end lightning.auto = current_weather.enable_lightning --rawset(lightning,"auto",current_weather.enable_lightning) if current_weather.enable_lightning and math.random(1,2) == 1 then local time = math.floor(math.random(lightning.interval_low/2,lightning.interval_low)) minetest.after(time, lightning.strike) end end ]] local do_raycasts = minetest.is_yes(minetest.settings:get_bool('raycast_hitcheck')) local damage_steps = 0 local function get_player_meta(player) local p = player if type(player) == "string" then p = minetest.get_player_by_name(player) end return p:get_meta() end function weather.get_weather(player) local meta = get_player_meta(player) return meta:get_string("weather_type") end function weather.set_weather(player,type) local meta = get_player_meta(player) meta:set_string("weather_type",type) end function weather.get_wind() return { x = storage:get_float("wind_x"), y = 0, z = storage:get_float("wind_z") } end function weather.set_wind(x,z) if type(x) == "table" then storage:set_float("wind_x",x.x) storage:set_float("wind_z",x.z) else storage:set_float("wind_x",x) storage:set_float("wind_z",z) end end local function handle_damage(damage,player, downfall_origin) if not damage then return end damage_steps = damage_steps +1 if damage_steps < damage.time then return end damage_steps = 0 if do_raycasts then -- this check should be more accurate local hitpos = vector.add(player:get_pos(),vector.new(0,1,0)) local ray = minetest.raycast(downfall_origin,hitpos) local o = ray:next() if o.type~="object" then return end -- hit node or something if not o.ref:is_player() then return end -- hit different object if o.ref:get_player_name() ~= player:get_player_name() then return --hit other player end o = ray:next() if o then minetest.log("warning","[weather] raycast hit more after hitting the player\n".. dump2(o,"o")) end else --check if player is affected by downfall, if it's dark there are nodes nearby if minetest.env:get_node_light(player:getpos(), 0.5) ~= 15 then return end end if math.random() < damage.chance then player:set_hp(player:get_hp()-damage.amount) end end local function weather_step(player,meta) local weather_type = meta:get_string("weather_type") if weather_type and weather_type ~= "" and weather_type ~= "none" then local current_downfall = weather.registered_downfalls[weather_type] if current_downfall then local ppos = player:getpos() if ppos.y > 120 then return end local wind = weather.get_wind() local wind_pos = vector.multiply(wind,-1) local minp = vector.add(vector.add(ppos, current_downfall.min_pos),wind_pos) local maxp = vector.add(vector.add(ppos, current_downfall.max_pos),wind_pos) local vel = {x=wind.x,y=-current_downfall.falling_speed,z=wind.z} local acc = {x=0, y=0, z=0} local exp = current_downfall.exptime minetest.add_particlespawner({ amount=current_downfall.amount, time=0.5, minpos=minp, maxpos=maxp, minvel=vel, maxvel=vel, minacc=acc, maxacc=acc, minexptime=exp, maxexptime=exp, minsize=current_downfall.size, maxsize=current_downfall.size, collisiondetection=true, collision_removal=true, vertical=true, texture=current_downfall.texture, playername=player:get_player_name() }) local downfall_origin = vector.divide(vector.add(minp,maxp),2) handle_damage(current_downfall.damage_player,player,downfall_origin) end end end minetest.register_globalstep(function() if math.random(1, 50000) == 1 then weather.set_wind(math.random(0,10),math.random(0,10)) end for _, player in ipairs(minetest.get_connected_players()) do local meta = get_player_meta(player) if math.random(1, 10000) == 1 then meta:set_string("weather_type","none") --[[if minetest.get_modpath("lightning") then lightning.auto = false end]] else for id,w in pairs(weather.registered_downfalls) do if math.random(1, 50000) == 1 then if (not w.disabled) and vector.length(weather.get_wind()) >= w.min_wind then meta:set_string("weather_type",id) --weather_mod.handle_lightning(w) break end end end end weather_step(player,meta) end end)