156 lines
8.1 KiB
Lua
156 lines
8.1 KiB
Lua
--- implements tools for quickly playing audio.
|
|
-- @module play_sound
|
|
|
|
local sqrt = math.sqrt
|
|
|
|
|
|
|
|
--- Guns4d soundspec
|
|
--
|
|
-- simple specification for playing a sound in relation to an action, acts as a layer of minetest.play_sound.
|
|
-- ATTENTION: read lua_api.md for more parameters! all parameters from there are valid unless otherwise stated here, these are auxillary.
|
|
-- @table guns4d_soundspec
|
|
-- @compact
|
|
-- @field min_hear_distance `float` this is useful if you wish to play a sound which has a "far" sound, such as distant gunshots. incompatible `with to_player`
|
|
-- @field sounds `table` a @{misc_helpers.weighted_randoms| weighted_randoms table} for randomly selecting sounds. The output will overwrite the `sound` field.
|
|
-- @field to_player `objRef` 4dguns changes `to_player` so it only plays positionless audio (as it is only intended for first person audio). If set to string "from_player" and player present
|
|
-- @field player `objRef` this is so to_player being set to "from_player". It's to be set to the player which fired the weapon.
|
|
-- @field delay `float` delays the playing of the sound
|
|
-- @field attenuation_rate `float` the rate of dropoff for a sound. I figure this is a bit more intuitive then jacking the gain up super high for every sound... Set the default in config.
|
|
-- @field split_audio_by_perspective `bool` [GUN CLASS SPECIFIC] tells the gun wether to split into third and first person (positionless) audio and adjust gain.
|
|
-- @field third_person_gain_multiplier `float` [GUN CLASS SPECIFIC] replaces the constant/config value "third_person_gain_multiplier/THIRD_PERSON_GAIN_MULTIPLIER".
|
|
-- @example
|
|
-- soundspec = {
|
|
-- sounds = { --weighted randoms
|
|
-- fire_fp = .5.
|
|
-- fire_fp_2 = .2.
|
|
-- fire_fp_3 = .3
|
|
-- },
|
|
-- pitch = {
|
|
-- min = .6,
|
|
-- max = 1
|
|
-- },
|
|
-- gain = 1, --format for pitch and gain is interchangable.
|
|
-- min_hear_distance = 20, --this is for distant gunshots, for example. Entirely optional. Cannot be used with to_player
|
|
-- exclude_player
|
|
-- to_player
|
|
-- --when present it automatically plays positionless audio, as this is for first person effects.
|
|
-- }
|
|
--
|
|
|
|
local function handle_min_max(tbl)
|
|
return tbl.min+(math.random()*(tbl.max-tbl.min))
|
|
end
|
|
local sound_handles = {}
|
|
local function play_sound(sound, soundspec, handle, i)
|
|
if soundspec.delay then
|
|
minetest.after(soundspec.delay, function()
|
|
if sound_handles[handle] ~= false then
|
|
sound_handles[handle][i] = minetest.sound_play(sound, soundspec, soundspec.ephemeral)
|
|
end
|
|
end)
|
|
else
|
|
sound_handles[handle][i] = minetest.sound_play(sound, soundspec)
|
|
end
|
|
end
|
|
|
|
--- allows you to play one or more sounds with more complex features, so sounds can be easily coded for guns without the need for functions.
|
|
--
|
|
-- WARNING: this function modifies the tables passed to it, use `Guns4d.table.shallow_copy()` or `table.copy` for inputted soundspecs
|
|
-- @example
|
|
-- {
|
|
-- to_player = "singeplayer",
|
|
-- min_distance = 100, --soundspec_to_play1 & soundspec_to_play2 will share this field as it is in the higher level table (as well as the above field)
|
|
-- soundspec_to_play1, --a sound parameter table
|
|
-- soundspec_to_play2
|
|
-- }
|
|
-- @tparam table soundspecs_list a list a list of soundspecs optionally accompanied with fields to be used in all of them.
|
|
-- @treturn integer guns4d sound handle, used by stop_sounds & get_sounds
|
|
function Guns4d.play_sounds(soundspecs_list)
|
|
--print(dump(soundspecs_list))
|
|
--support a list of sounds to play
|
|
if not soundspecs_list[1] then --turn into iteratable format.
|
|
soundspecs_list = {soundspecs_list}
|
|
end
|
|
local applied = {}
|
|
--all fields that aren't numbers will be copied over, allowing you to set fields across all sounds (i.e. pos, target player.), if already present it will remain the same.
|
|
for field, v in pairs(soundspecs_list) do
|
|
if type(field) ~= "number" then
|
|
for _, spec in ipairs(soundspecs_list) do
|
|
if not spec[field] then
|
|
spec[field] = v
|
|
end
|
|
end
|
|
soundspecs_list[field] = nil --so it isn't iterated
|
|
end
|
|
end
|
|
local handle = #sound_handles+1 --determine the sound handle before playing
|
|
sound_handles[handle] = {}
|
|
--local handle_object = sound_handles[handle]
|
|
for arg, soundspec in pairs(soundspecs_list) do
|
|
if soundspec.to_player == "from_player" then soundspec.to_player = soundspec.player:get_player_name() end --setter of sound may not have access to this info, so add a method to use it.
|
|
assert(not (soundspec.to_player and soundspec.min_distance), "in argument '"..tostring(arg).."' `min_distance` and `to_player` are incompatible parameters.")
|
|
local sound = soundspec.sound
|
|
for i, v in pairs(soundspec) do
|
|
if type(v) == "table" and v.min then
|
|
soundspec[i]=handle_min_max(v)
|
|
end
|
|
end
|
|
if type(sound) == "table" then
|
|
sound = Guns4d.math.weighted_randoms(sound)
|
|
end
|
|
assert(sound, "no sound provided")
|
|
if not leef.paths.media_paths[(sound or "[NIL]")..".ogg"] then
|
|
minetest.log("error", "no sound by the name `"..leef.paths.media_paths[(sound or "[NIL]")..".ogg"].."`")
|
|
end
|
|
local exclude_player_ref = soundspec.exclude_player
|
|
if type(soundspec.exclude_player)=="string" then
|
|
exclude_player_ref = minetest.get_player_by_name(soundspec.exclude_player)
|
|
elseif soundspec.exclude_player then
|
|
exclude_player_ref = soundspec.exclude_player
|
|
soundspec.exclude_player = exclude_player_ref:get_player_name()
|
|
end
|
|
--print(dump(soundspecs_list), i)
|
|
if soundspec.to_player then soundspec.pos = nil end
|
|
--play sound for all players outside min hear distance
|
|
local original_gain = soundspec.gain or 1
|
|
local attenuation_rate = soundspec.attenuation_rate or Guns4d.config.default_audio_attenuation_rate
|
|
local player_list = ((not soundspec.to_player) and minetest.get_connected_players()) or {minetest.get_player_by_name(soundspec.to_player)}
|
|
for _, player in pairs(player_list) do
|
|
soundspec.sound = nil
|
|
local pos = player:get_pos()
|
|
local dist = 0
|
|
if soundspec.pos then
|
|
dist = sqrt( sqrt((pos.x-(soundspec.pos.x))^2+(pos.y-soundspec.pos.y)^2)^2 + (pos.z-soundspec.pos.z)^2)
|
|
end
|
|
if ((not soundspec.max_hear_distance) or (dist <= soundspec.max_hear_distance)) and ((not soundspec.min_hear_distance) or (dist > soundspec.min_hear_distance)) and (player~=exclude_player_ref) then
|
|
soundspec.exclude_player = nil --not needed anyway because we can just not play it for this player.
|
|
soundspec.to_player = player:get_player_name()
|
|
soundspec.gain = original_gain/(Guns4d.math.clamp((dist-(soundspec.min_hear_distance or 0))*attenuation_rate, 1, math.huge)^2) --so i found out the hard way that it doesn't fucking reduce volume by distance if there's a to_player. Kind of pisses me off.
|
|
play_sound(sound, soundspec, handle, arg)
|
|
end
|
|
end
|
|
end
|
|
return handle
|
|
end
|
|
|
|
--- gets a list of currently playing Minetest sound handles from the Guns4d sound handle. Modification of table highly discouraged.
|
|
-- @tparam integer handle a sound handle as returned by play_sounds
|
|
-- @treturn table a list of sound handles as returned by minetest.sound_play()
|
|
function Guns4d.get_sounds(handle)
|
|
return sound_handles[handle]
|
|
end
|
|
--- stops a list of sounds
|
|
-- @tparam integer|table handle a guns4d sound handle OR list of minetest sound handles to stop
|
|
-- @treturn bool returns true if successful.
|
|
function Guns4d.stop_sounds(handle)
|
|
local handle_list = (type(handle) == "table" and handle) or sound_handles[handle]
|
|
if not handle_list then return false end
|
|
if type(handle) == "number" then
|
|
sound_handles[handle] = false --indicate to not play any delayed noises.
|
|
end
|
|
for i, v in pairs(handle_list) do
|
|
minetest.sound_stop(v)
|
|
end
|
|
return true
|
|
end |