sound_distance/init.lua

298 lines
11 KiB
Lua

--Fun fact: the "Doppler Effect" is physics law about waves, according to it, basically waves change depending on their distance.
--The Doppler Effect is what makes us able to tell if the sound origin is getting closer or further.
local sonic_boom = {
physical = false,
timer = 0,
visual = "sprite",
visual_size = {x=0.5, y=0.5},
textures = {"sonic_boom.png"},
lastpos= {},
collisionbox = {0, 0, 0, 0, 0, 0},
}
sonic_boom.on_step = function(self, dtime, pos)--Sonic boom expanding
sound_distance_play(self.object:get_pos(), "tnt_explode", 1, 32)--Sonic boom noise
self.timer = (self.timer or 0) + dtime
local size_x = self.object:get_properties().visual_size.x
local size_y = self.object:get_properties().visual_size.y
self.object:set_properties({visual_size = {x=size_x + 0.1, y=size_y + 0.1}})
if self.timer > 1 then--Fun fact: the first time this was tested, the time limit was too long, so it became a Detroit Smash
self.object:remove()
end
end
minetest.register_entity("sound_distance:sonic_boom", sonic_boom)
--Note: "a" stands for the target position, while "b" is the origin position
local get_distance = function(a, b) --This function is from mobs redo
if not a or not b then return 50 end -- nil check
local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
return math.sqrt(x * x + y * y + z * z)
end
local check_movement = function(pos, yaw, player)--For now this kind of works, further testing needed
if not yaw or not pos or not player then return false end
local player_pos = player:get_pos()
local deg = math.deg(yaw)--Try to use get_look_horizontal instead of get_look_yaw, as the deprecated function breaks this one
if deg < 360 and deg > 270 then--NE
if deg > 315 then--Closer to North
if player_pos.z > pos.z then
return true--True for getting closer, false for going away
else
return false
end
elseif deg < 315 then--Closer to East
if player_pos.x > pos.x then
return true--True for getting closer, false for going away
else
return false
end
end
elseif deg < 270 and deg > 180 then--SE
if deg > 225 then--Closer to East
if player_pos.x > pos.x then
return true
else
return false
end
elseif deg < 225 then--Closer to South
if player_pos.z < pos.z then
return true
else
return false
end
end
elseif deg < 180 and deg > 90 then--SW
if deg > 135 then--Closer to South
if player_pos.z < pos.z then
return true
else
return false
end
elseif deg < 135 then--Closer to West
if player_pos.x < pos.x then
return true
else
return false
end
end
elseif deg < 90 and deg > 0 then--NW
if deg > 45 then--Closer to West
if player_pos.x < pos.x then
return true
else
return false
end
elseif deg < 45 then--Closer to North
if player_pos.z > pos.z then
return true
else
return false
end
end
elseif deg == 0 then--N
if player_pos.z > pos.z then
return true
else
return false
end
elseif deg == 270 then--E
if player_pos.x > pos.x then
return true
else
return false
end
elseif deg == 180 then--S
if player_pos.z < pos.z then
return true
else
return false
end
elseif deg == 90 then--W
if player_pos.x < pos.x then
return true
else
return false
end
end
end
function sound_distance_play(pos, sound, sound_gain, distance, moving, yaw, vel, force_sound_speed)
if not pos or not sound or not distance then return end
if not sound_gain then sound_gain = 1 end
for _,player in ipairs(minetest.get_connected_players()) do
local dist = get_distance(player:get_pos(), pos)
if not dist then return end
if dist > distance then return end
local doppler = distance - dist
if not doppler then return end
if not player then return end--Avoid glitching incase player leaves
if moving == true and not yaw then yaw = 0 end
if not vel then vel = {x = 10, y = 0, z = 10} end
local actual_vel = math.ceil(vel.x * vel.x + vel.z * vel.z) ^ 0.5--Converts a vector into an approximate number
if force_sound_speed == true then actual_vel = 340 end
if actual_vel >= 340 and actual_vel <= 342 then minetest.add_entity(pos, "sound_distance:sonic_boom") end
if moving == true and check_movement(pos, yaw, player) == true then--Thanks to Astrobe I've noticed the mod was missing pitch changes
if actual_vel >= 340 then return end--Object is moving above or at sound speed, so only those behind the object can hear it
--minetest.chat_send_player(player:get_player_name(), "working pitch change")
if doppler == 0 then
minetest.sound_play(sound, {gain = 0.1, pitch = 2, to_player = player:get_player_name()})--Higher pitch incase the object is moving and aproaching
else
if sound_gain*(doppler/10) > sound_gain then
minetest.sound_play(sound, {gain = sound_gain, pitch = 2, to_player = player:get_player_name()})
else
minetest.sound_play(sound, {gain = sound_gain*(doppler/10), pitch = 2, to_player = player:get_player_name()})
end
end
else
if actual_vel > 340 then--If above sound speed, the sound will be delayed
local sound_timing = actual_vel - 340
if not sound_timing then return end
minetest.after(sound_timing, function()
if doppler == 0 then
minetest.sound_play(sound, {gain = 0.1, to_player = player:get_player_name()})
else
if sound_gain*(doppler/10) > sound_gain then
minetest.sound_play(sound, {gain = sound_gain, to_player = player:get_player_name()})
else
minetest.sound_play(sound, {gain = sound_gain*(doppler/10), to_player = player:get_player_name()})
end
end
end)
else
if doppler == 0 then
minetest.sound_play(sound, {gain = 0.1, to_player = player:get_player_name()})
else
if sound_gain*(doppler/10) > sound_gain then--This avoids a possible ear exploder incase the sound has a huge hear distance for example
minetest.sound_play(sound, {gain = sound_gain, to_player = player:get_player_name()})--The sound is played only for that specific player
else
minetest.sound_play(sound, {gain = sound_gain*(doppler/10), to_player = player:get_player_name()})
end
end
end
end
end
end
--Testing stuff
--Node
--[[minetest.register_node("sound_distance:test_node", {
description = "Sound distance test",
tiles = {"default_steel_block.png"},
groups = {oddly_breakable_by_hand=1, snappy=1, cracky=1},
sounds = default_stone_sounds,
paramtype = "light",
is_ground_content = false,
on_rightclick = function(pos, node, clicker)
sound_distance_play(pos, "tnt_explode", 1, 16)
minetest.after(2, function()
sound_distance_play(pos, "tnt_explode", 1, 16)
end)
minetest.after(5, function()
sound_distance_play(pos, "tnt_explode", 1, 16)
end)
end,
})
--Moving sound emitter
local test_entity = {
physical = false,
timer = 0,
visual = "sprite",
visual_size = {x=0.5, y=0.5},
textures = {"default_steel_block.png"},
lastpos= {},
collisionbox = {0, 0, 0, 0, 0, 0},
}
test_entity.on_step = function(self, dtime, pos)--Moving sound source
sound_distance_play(self.object:get_pos(), "default_place_node", 10, 100, true, self.object:get_yaw(), self.object:get_velocity())--A repeating short sound
self.timer = (self.timer or 0) + dtime
if self.timer > 40 then
self.object:remove()
end
end
minetest.register_entity("sound_distance:test_entity", test_entity)
--Below sound speed
minetest.register_craftitem("sound_distance:test_item", {
inventory_image = "default_steel_block.png",
description = "Moving sound source (TEST)",
stack_max = 1,
on_use = function (itemstack, user, pointed_thing)
local pos = user:getpos()
local dir = user:get_look_dir()
local yaw = user:get_look_horizontal()
if pos and dir and yaw then
pos.y = pos.y + 1.6
local obj = minetest.add_entity(pos, "sound_distance:test_entity")
if obj then
obj:setvelocity({x=dir.x * 12, y=dir.y * 12, z=dir.z * 12})
obj:setyaw(yaw)
local ent = obj:get_luaentity()
if ent then
ent.player = ent.player or user
itemstack = ""
end
end
end
return itemstack
end,
})
--Above sound speed (delayed sound)
minetest.register_craftitem("sound_distance:ultrasonic_item", {
inventory_image = "default_steel_block.png",
description = "Ultrasonic sound source (TEST)",
stack_max = 1,
on_use = function (itemstack, user, pointed_thing)
local pos = user:getpos()
local dir = user:get_look_dir()
local yaw = user:get_look_horizontal()
if pos and dir and yaw then
pos.y = pos.y + 1.6
local obj = minetest.add_entity(pos, "sound_distance:test_entity")
if obj then
obj:setvelocity({x=dir.x * 345, y=dir.y * 345, z=dir.z * 345})
obj:setyaw(yaw)
local ent = obj:get_luaentity()
if ent then
ent.player = ent.player or user
itemstack = ""
end
end
end
return itemstack
end,
})
--At sound speed (sonic boom and only people behind the object can hear it)
minetest.register_craftitem("sound_distance:sound_speed_item", {
inventory_image = "default_steel_block.png",
description = "Sound speed sound source (TEST)",
stack_max = 1,
on_use = function (itemstack, user, pointed_thing)
local pos = user:getpos()
local dir = user:get_look_dir()
local yaw = user:get_look_horizontal()
if pos and dir and yaw then
pos.y = pos.y + 1.6
local obj = minetest.add_entity(pos, "sound_distance:test_entity")
if obj then
obj:setvelocity({x=dir.x * 340, y=dir.y * 340, z=dir.z * 340})
obj:setyaw(yaw)
local ent = obj:get_luaentity()
if ent then
ent.player = ent.player or user
itemstack = ""
end
end
end
return itemstack
end,
})]]