367 lines
8.7 KiB
Lua
367 lines
8.7 KiB
Lua
-- Game Sounds `gen_music'
|
|
-- By James Stevenson (C) 2017
|
|
-- GPL3+
|
|
gen_music = {}
|
|
local se = minetest.request_insecure_environment()
|
|
local settings = minetest.settings
|
|
local show_st = settings:get_bool("show_sound_travelers")
|
|
|
|
gen_music.songs = {}
|
|
local mod_name = minetest.get_current_modname()
|
|
local mod_path = minetest.get_modpath(mod_name)
|
|
local world_path = minetest.get_worldpath()
|
|
|
|
local function load_songs()
|
|
local input = io.open(mod_path .. "/songs.txt", "r")
|
|
local input_wd = io.open(mod_path .. "/songs.txt", "r")
|
|
if not input and
|
|
not input_wd then
|
|
return
|
|
end
|
|
|
|
local songs = {}
|
|
local fn = input:read("*l")
|
|
while fn do
|
|
songs[#songs + 1] = fn
|
|
fn = input:read("*l")
|
|
end
|
|
io.close(input)
|
|
local fn_wd = input_wd:read("*l")
|
|
while fn_wd do
|
|
songs[#songs + 1] = fn_wd
|
|
fn_wd = input_wd:read("*l")
|
|
end
|
|
io.close(input_wd)
|
|
|
|
for i = 1, #songs do
|
|
local song_name = songs[i]
|
|
local song = io.open(mod_path .. "/" .. song_name, "r") or
|
|
io.open(world_path .. "/" .. song_name, "r")
|
|
if not song then
|
|
print("[" .. mod_name .. "] " .. song_name .. " not found.")
|
|
else
|
|
print("[" .. mod_name .. "] " .. "Loading song " .. song_name .. ".")
|
|
local ins = song:read("*l"):split()
|
|
local seq = {}
|
|
local seql = song:read("*l")
|
|
while seql do
|
|
seq[#seq + 1] = tonumber(seql)
|
|
seql = song:read("*l")
|
|
end
|
|
gen_music.songs[song_name] = {
|
|
ins = ins,
|
|
seq = seq,
|
|
}
|
|
io.close(song)
|
|
end
|
|
end
|
|
end
|
|
load_songs()
|
|
|
|
local sounds = {}
|
|
for _, nodes in pairs(minetest.registered_nodes) do
|
|
for elem, val in pairs(nodes) do
|
|
if elem == "sounds" then
|
|
for _, spec in pairs(val) do
|
|
if not sounds[spec.name] and
|
|
spec.name ~= "" then
|
|
if not spec.gain then
|
|
spec.gain = 0.5
|
|
end
|
|
sounds[#sounds + 1] = spec
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if minetest.get_modpath("mobs") then
|
|
for _, def in pairs(minetest.registered_entities) do
|
|
for prop, val in pairs(def) do
|
|
if prop == "sounds" then
|
|
for sound_type, sound in pairs(val) do
|
|
if type(sound) == "string" then
|
|
if not sounds[sound] and
|
|
sounds[sound] ~= "" then
|
|
sounds[#sounds + 1] = {
|
|
name = sound,
|
|
gain = 0.5
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- TODO Erase local table, generate based on extra_sounds.txt
|
|
local extra_sounds = {
|
|
"tnt_ignite",
|
|
"tnt_explode",
|
|
"tnt_gunpower_burning",
|
|
"fire_flint_and_steel",
|
|
"fire_extinguish_flame",
|
|
"fire_fire",
|
|
"default_punch",
|
|
"default_dig_oddly_breakable_by_hand",
|
|
"default_dug_node",
|
|
"player_damage",
|
|
"doors_door_open",
|
|
"doors_door_close",
|
|
"default_chest_open",
|
|
"default_chest_close",
|
|
}
|
|
for i = 1, #extra_sounds do
|
|
local n = extra_sounds[i]
|
|
if not sounds[n] then
|
|
sounds[#sounds + 1] = {
|
|
name = extra_sounds[i],
|
|
gain = 0.5
|
|
}
|
|
end
|
|
end
|
|
|
|
local jitter = function(j, p)
|
|
local r = math.random()
|
|
local s = j * -1
|
|
if r > 0.5 then
|
|
s = j * 1
|
|
if math.random() > r then
|
|
return p + (1 * s)
|
|
else
|
|
return p - (1 * s)
|
|
end
|
|
elseif math.random() < r then
|
|
return p + (1 * s)
|
|
else
|
|
return p - (1 * s)
|
|
end
|
|
end
|
|
|
|
-- Sound Traveler
|
|
local ps = function(pos)
|
|
if math.random() < 0.8 then
|
|
return
|
|
end
|
|
local s = -1
|
|
if math.random() > 0.5 then
|
|
s = 1
|
|
end
|
|
minetest.add_particle({
|
|
pos = {
|
|
x = jitter(math.random(), math.random() * s + pos.x),
|
|
y = jitter(math.random(), math.random() * s + pos.y),
|
|
z = jitter(math.random(), math.random() * s + pos.z)
|
|
},
|
|
velocity = {
|
|
x = jitter(math.random(), math.random() * s),
|
|
y = jitter(math.random(), math.random() * s),
|
|
z = jitter(math.random(), math.random() * s)
|
|
},
|
|
acceleration = {
|
|
x = jitter(math.random(), math.random() * s),
|
|
y = jitter(math.random(), math.random() * s),
|
|
z = jitter(math.random(), math.random() * s)
|
|
},
|
|
expirationtime = 1,
|
|
size = 2,
|
|
texture = "xdecor_glyph" .. math.random(1, 18) .. ".png",
|
|
glow = 10,
|
|
})
|
|
end
|
|
local st_textures = {"empty.png"}
|
|
local st_on_activate = function(self, staticdata, dtime_s) end
|
|
local st_on_step = function(self, dtime) end
|
|
if show_st then
|
|
print("[" .. mod_name .. "] Showing Sound Travelers")
|
|
st_textures = {"default_mese_crystal.png"}
|
|
st_on_activate = function(self, staticdata, dtime_s)
|
|
self.pos = self.object:get_pos()
|
|
end
|
|
st_on_step = function(self, dtime)
|
|
ps(self.pos)
|
|
end
|
|
end -- ^ nil these after registry?
|
|
minetest.register_entity("gen_music:sound_traveler", {
|
|
visual = "sprite",
|
|
visual_size = {x = 0.5, y = 0.5},
|
|
collisionbox = {0},
|
|
physical = false,
|
|
textures = st_textures,
|
|
on_activate = st_on_activate,
|
|
on_step = st_on_step,
|
|
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
|
end,
|
|
on_death = function(self, killer)
|
|
end,
|
|
on_rightclick = function(self, clicker)
|
|
end,
|
|
get_staticdata = function(self)
|
|
end,
|
|
})
|
|
|
|
gen_music.gs = function(object, song, loop)
|
|
if type(song) == "string" then
|
|
song = gen_music.songs[song]
|
|
end
|
|
if not song then
|
|
return
|
|
end
|
|
local gain = math.random()
|
|
if gain > 0.67 then
|
|
gain = 0.67
|
|
end
|
|
local play = minetest.sound_play
|
|
local seq = {}
|
|
local obj = {}
|
|
-- r1
|
|
--local prob = math.random()
|
|
--if prob < 0.67 then
|
|
minetest.after(math.random(), function()
|
|
local p_pos = object:get_pos()
|
|
if not p_pos then
|
|
return
|
|
end
|
|
p_pos.x = jitter(math.random(), p_pos.x)
|
|
p_pos.y = jitter(math.random(), p_pos.y)
|
|
p_pos.z = jitter(math.random(), p_pos.z)
|
|
|
|
local rest = 10
|
|
local function play_variable(ensemble)
|
|
-- New object.
|
|
local obj_idx = #obj + 1
|
|
obj[obj_idx] = minetest.add_entity(p_pos,
|
|
"gen_music:sound_traveler", "")
|
|
local o_pos = obj[obj_idx]:get_pos()
|
|
local d = vector.direction(p_pos, o_pos)
|
|
d.x = jitter(math.random(), d.x)
|
|
d.y = jitter(math.random(), d.y)
|
|
d.z = jitter(math.random(), d.z)
|
|
obj[obj_idx]:set_velocity(d)
|
|
obj[obj_idx]:set_acceleration(d)
|
|
|
|
-- New sound attached to new object.
|
|
local pitch = math.random()
|
|
if math.random() > 0.5 or
|
|
pitch > 0.66 then
|
|
pitch = pitch - 0.5
|
|
end
|
|
local idx = #seq + 1
|
|
local fade = math.random(1)
|
|
local n
|
|
if not ensemble then
|
|
n = "gen_music_rest"
|
|
else
|
|
n = ensemble[math.random(1, #ensemble)]
|
|
end
|
|
seq[idx] = play(n, {
|
|
gain = gain * 0.1,
|
|
pitch = pitch * 1.33,
|
|
fade = fade,
|
|
object = obj[obj_idx],
|
|
})
|
|
--print("Starting " .. seq[idx])
|
|
if fade == 1 then
|
|
minetest.sound_fade(seq[idx], 0.1, gain)
|
|
end
|
|
end
|
|
|
|
local score = song.seq
|
|
for i = 1, #score do
|
|
local e = score[i]
|
|
if e == "0" then
|
|
minetest.after(math.random(), function()
|
|
play_variable()
|
|
end)
|
|
else
|
|
local g = song.ins[e]
|
|
if g then
|
|
minetest.after(math.random(), function()
|
|
play_variable{g}
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
|
|
minetest.after(math.random(), function()
|
|
for k, v in pairs(obj) do
|
|
v:remove()
|
|
end
|
|
end)
|
|
end)
|
|
--end
|
|
minetest.after(1 + math.random() + math.random(), function()
|
|
for idx = 1, #seq do
|
|
minetest.after(math.random(), function()
|
|
--print("Stopping " .. seq[idx])
|
|
minetest.sound_fade(seq[idx], -0.1, 0.0)
|
|
minetest.after(math.random(), function()
|
|
minetest.sound_stop(seq[idx])
|
|
end)
|
|
end)
|
|
end
|
|
if loop then
|
|
if object:is_player() then
|
|
local l = object:get_attribute("loop")
|
|
if not l or l == "" then
|
|
return
|
|
end
|
|
l = tonumber(l)
|
|
object:set_attribute("loop", l - 1)
|
|
if l == 0 then
|
|
object:set_attribute("loop", nil)
|
|
end
|
|
minetest.after(math.random() + math.random(), function()
|
|
gen_music.gs(object, song, loop)
|
|
end)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- Restrict usage until further testing has been done.
|
|
minetest.register_privilege("game_sounds", {
|
|
description = "Can use /gs command.",
|
|
give_to_singleplayer = true,
|
|
give_to_admin = true,
|
|
})
|
|
minetest.register_chatcommand("gs", {
|
|
description = "Play and load game sound sequences.",
|
|
params = "[load <file>] [list] [stop] [song <duration>]",
|
|
privs = "game_sounds",
|
|
func = function(name, param)
|
|
param = param:split(" ")
|
|
local player = minetest.get_player_by_name(name)
|
|
if param[1] == "load" then
|
|
-- TODO Mod security.
|
|
if param[2] then
|
|
-- TODO Check for existence of new songfile to load.
|
|
-- If loaded from world_dir, save songs.txt there.
|
|
-- If loaded from mod_dir, save to songs.txt there.
|
|
if not se then
|
|
return true, "`gen_music' is not a trusted mod."
|
|
end
|
|
local h = se.io.open(mod_path .. "/songs.txt", "a+")
|
|
h:write(param[2] .. "\n")
|
|
print("Loaded song " .. param[2] .. ".")
|
|
io.close(h)
|
|
end
|
|
load_songs()
|
|
return true, mod_path .. "/songs.txt loaded."
|
|
end
|
|
if param[1] == "list" then
|
|
for song, _ in pairs(gen_music.songs) do
|
|
print(song)
|
|
end
|
|
end
|
|
if param[1] == "stop" then
|
|
player:set_attribute("loop", nil)
|
|
return true, "This may take a moment."
|
|
end
|
|
|
|
local lc = param[2] or 3
|
|
player:set_attribute("loop", tonumber(lc))
|
|
gen_music.gs(minetest.get_player_by_name(name),
|
|
param[1], true)
|
|
end,
|
|
})
|