298 lines
11 KiB
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,
|
|
})]]
|