Implement sound and events in API

This commit is contained in:
Treer 2019-07-21 13:06:04 +10:00 committed by SmallJoker
parent 0c7a6e95c5
commit e2666146ca
3 changed files with 109 additions and 35 deletions

View File

@ -47,8 +47,8 @@ nether.register_portal_ignition_item("default:mese_crystal_fragment")
nether.register_portal("nether_portal", { nether.register_portal("nether_portal", {
shape = nether.PortalShape_Traditional, shape = nether.PortalShape_Traditional,
frame_node_name = "default:obsidian", frame_node_name = "default:obsidian",
wormhole_node_name = "nether:portal",
wormhole_node_color = 0, -- 0 is magenta wormhole_node_color = 0, -- 0 is magenta
-- Warning: "Four per Em" spaces have been used to align the diagram in this text, rather -- Warning: "Four per Em" spaces have been used to align the diagram in this text, rather
-- than ASCII spaces. If Minetest changes font this may need to be updated. -- than ASCII spaces. If Minetest changes font this may need to be updated.
book_of_portals_pagetext = S([[ The Nether book_of_portals_pagetext = S([[ The Nether
@ -66,10 +66,6 @@ This opens to a truly hellish place, though for small mercies the air there is s
The expedition parties have found no diamonds or gold, and after an experienced search party failed to return from the trail of a missing expedition party, I must conclude this is a dangerous place. The expedition parties have found no diamonds or gold, and after an experienced search party failed to return from the trail of a missing expedition party, I must conclude this is a dangerous place.
]], 10 * nether.FASTTRAVEL_FACTOR), ]], 10 * nether.FASTTRAVEL_FACTOR),
sound_ambient = "nether_portal_hum",
sound_ignite = "",
sound_extinguish = "",
sound_teleport = "",
within_realm = function(pos) -- return true if pos is inside the Nether within_realm = function(pos) -- return true if pos is inside the Nether
return pos.y < nether.DEPTH return pos.y < nether.DEPTH

View File

@ -528,6 +528,10 @@ local function build_portal(portal_definition, anchorPos, orientation, destinati
if DEBUG then minetest.chat_send_all("Placed portal schematic at " .. minetest.pos_to_string(portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation)) .. ", orientation " .. orientation) end if DEBUG then minetest.chat_send_all("Placed portal schematic at " .. minetest.pos_to_string(portal_definition.shape.get_schematicPos_from_anchorPos(anchorPos, orientation)) .. ", orientation " .. orientation) end
set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos) set_portal_metadata(portal_definition, anchorPos, orientation, destination_wormholePos)
if portal_definition.on_created ~= nil then
portal_definition.on_created(portal_definition, anchorPos, orientation)
end
end end
@ -770,11 +774,48 @@ local function ignite_portal(ignition_pos, ignition_node_name)
-- ignition/BURN_BABY_BURN -- ignition/BURN_BABY_BURN
set_portal_metadata_and_ignite(portal_definition, anchorPos, orientation, destination_wormholePos) set_portal_metadata_and_ignite(portal_definition, anchorPos, orientation, destination_wormholePos)
if portal_definition.sounds.ignite ~= nil then
local local_wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation)
minetest.sound_play(portal_definition.sounds.ignite, {pos = local_wormholePos, max_hear_distance = 20})
end
if portal_definition.on_ignite ~= nil then
portal_definition.on_ignite(portal_definition, anchorPos, orientation)
end
return true return true
end end
end end
end end
-- the timerNode is used to keep the metadata as that node already needs to be known any time a portal is stopped or run
-- see also ambient_sound_stop()
function ambient_sound_play(portal_definition, soundPos, timerNodeMeta)
if portal_definition.sounds.ambient ~= nil then
local soundLength = portal_definition.sounds.ambient.length
if soundLength == nil then soundLength = 3 end
local lastPlayed = timerNodeMeta:get_int("ambient_sound_last_played")
-- Using "os.time() % soundLength == 0" is lightweight but means delayed starts, so trying a stored lastPlayed
if os.time() >= lastPlayed + soundLength then
local soundHandle = minetest.sound_play(portal_definition.sounds.ambient, {pos = soundPos, max_hear_distance = 6})
if timerNodeMeta ~= nil then
timerNodeMeta:set_int("ambient_sound_handle", soundHandle)
timerNodeMeta:set_int("ambient_sound_last_played", os.time())
end
end
end
end
-- the timerNode is used to keep the metadata as that node already needs to be known any time a portal is stopped or run
-- see also ambient_sound_play()
function ambient_sound_stop(timerNodeMeta)
if timerNodeMeta ~= nil then
local soundHandle = timerNodeMeta:get_int("ambient_sound_handle")
minetest.sound_stop(soundHandle)
end
end
-- WARNING - this is invoked by on_destruct, so you can't assume there's an accesible node at pos -- WARNING - this is invoked by on_destruct, so you can't assume there's an accesible node at pos
extinguish_portal = function(pos, node_name, frame_was_destroyed) -- assigned rather than declared because extinguish_portal is already declared, for use by earlier functions in the file. extinguish_portal = function(pos, node_name, frame_was_destroyed) -- assigned rather than declared because extinguish_portal is already declared, for use by earlier functions in the file.
@ -797,6 +838,15 @@ extinguish_portal = function(pos, node_name, frame_was_destroyed) -- assigned ra
return -- no portal frames are made from this type of node return -- no portal frames are made from this type of node
end end
if portal_definition.sounds.extinguish ~= nil then
minetest.sound_play(portal_definition.sounds.extinguish, {pos = p1})
end
-- stop timer and ambient sound
local timerPos = get_timerPos_from_p1_and_p2(p1, p2)
minetest.get_node_timer(timerPos):stop()
ambient_sound_stop(minetest.get_meta(timerPos))
-- update the ignition state in the portal location info -- update the ignition state in the portal location info
local anchorPos, orientation = portal_definition.shape.get_anchorPos_and_orientation_from_p1_and_p2(p1, p2) local anchorPos, orientation = portal_definition.shape.get_anchorPos_and_orientation_from_p1_and_p2(p1, p2)
if frame_was_destroyed then if frame_was_destroyed then
@ -808,7 +858,6 @@ extinguish_portal = function(pos, node_name, frame_was_destroyed) -- assigned ra
local frame_node_name = portal_definition.frame_node_name local frame_node_name = portal_definition.frame_node_name
local wormhole_node_name = portal_definition.wormhole_node_name local wormhole_node_name = portal_definition.wormhole_node_name
minetest.get_node_timer(get_timerPos_from_p1_and_p2(p1, p2)):stop()
for x = p1.x, p2.x do for x = p1.x, p2.x do
for y = p1.y, p2.y do for y = p1.y, p2.y do
@ -891,12 +940,22 @@ local function ensure_remote_portal_then_teleport(player, portal_definition, loc
if DEBUG then minetest.chat_send_all("Teleporting player from wormholePos" .. minetest.pos_to_string(local_wormholePos) .. " to wormholePos" .. minetest.pos_to_string(destination_wormholePos)) end if DEBUG then minetest.chat_send_all("Teleporting player from wormholePos" .. minetest.pos_to_string(local_wormholePos) .. " to wormholePos" .. minetest.pos_to_string(destination_wormholePos)) end
-- play the teleport sound
if portal_definition.sounds.teleport ~= nil then
minetest.sound_play(portal_definition.sounds.teleport, {to_player = player.name})
end
-- rotate the player if the destination portal is a different orientation -- rotate the player if the destination portal is a different orientation
local rotation_angle = math.rad(destination_orientation - local_orientation) local rotation_angle = math.rad(destination_orientation - local_orientation)
local offset = vector.subtract(playerPos, local_wormholePos) -- preserve player's position in the portal local offset = vector.subtract(playerPos, local_wormholePos) -- preserve player's position in the portal
local rotated_offset = {x = math.cos(rotation_angle) * offset.x - math.sin(rotation_angle) * offset.z, y = offset.y, z = math.sin(rotation_angle) * offset.x + math.cos(rotation_angle) * offset.z} local rotated_offset = {x = math.cos(rotation_angle) * offset.x - math.sin(rotation_angle) * offset.z, y = offset.y, z = math.sin(rotation_angle) * offset.x + math.cos(rotation_angle) * offset.z}
player:setpos(vector.add(destination_wormholePos, rotated_offset)) local new_playerPos = vector.add(destination_wormholePos, rotated_offset)
player:setpos(new_playerPos)
player:set_look_horizontal(player:get_look_horizontal() + rotation_angle) player:set_look_horizontal(player:get_look_horizontal() + rotation_angle)
if portal_definition.on_player_teleported ~= nil then
portal_definition.on_player_teleported(portal_definition, player, playerPos, new_playerPos)
end
else else
-- no wormhole node at destination - destination portal either needs to be built or ignited -- no wormhole node at destination - destination portal either needs to be built or ignited
-- A very rare edge-case that takes time to set up: -- A very rare edge-case that takes time to set up:
@ -930,8 +989,8 @@ end
-- run_wormhole() is invoked once per second per portal, handling teleportation and particle effects. -- run_wormhole() is invoked once per second per portal, handling teleportation and particle effects.
-- See get_timerPos_from_p1_and_p2() for an explanation of where pos will be -- See get_timerPos_from_p1_and_p2() for an explanation of the timerPos location
function run_wormhole(pos, time_elapsed) function run_wormhole(timerPos, time_elapsed)
local portal_definition -- will be used inside run_wormhole_node_func() local portal_definition -- will be used inside run_wormhole_node_func()
@ -989,7 +1048,7 @@ function run_wormhole(pos, time_elapsed)
end end
local p1, p2, frame_node_name local p1, p2, frame_node_name
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(timerPos)
if meta ~= nil then if meta ~= nil then
p1 = minetest.string_to_pos(meta:get_string("p1")) p1 = minetest.string_to_pos(meta:get_string("p1"))
p2 = minetest.string_to_pos(meta:get_string("p2")) p2 = minetest.string_to_pos(meta:get_string("p2"))
@ -997,10 +1056,10 @@ function run_wormhole(pos, time_elapsed)
end end
if p1 ~= nil and p2 ~= nil then if p1 ~= nil and p2 ~= nil then
-- look up the portal shape by what it's built from, so we know where the wormhole nodes will be located -- look up the portal shape by what it's built from, so we know where the wormhole nodes will be located
if frame_node_name == nil then frame_node_name = minetest.get_node(pos).name end -- pos should be a frame node if frame_node_name == nil then frame_node_name = minetest.get_node(timerPos).name end -- timerPos should be a frame node if the shape is traditionalPortalShape
portal_definition = get_portal_definition(frame_node_name, p1, p2) portal_definition = get_portal_definition(frame_node_name, p1, p2)
if portal_definition == nil then if portal_definition == nil then
minetest.log("error", "No portal with a \"" .. frame_node_name .. "\" frame is registered. run_wormhole" .. minetest.pos_to_string(pos) .. " was invoked but that location contains \"" .. frame_node_name .. "\"") minetest.log("error", "No portal with a \"" .. frame_node_name .. "\" frame is registered. run_wormhole" .. minetest.pos_to_string(timerPos) .. " was invoked but that location contains \"" .. frame_node_name .. "\"")
else else
local anchorPos, orientation = portal_definition.shape.get_anchorPos_and_orientation_from_p1_and_p2(p1, p2) local anchorPos, orientation = portal_definition.shape.get_anchorPos_and_orientation_from_p1_and_p2(p1, p2)
portal_definition.shape.apply_func_to_wormhole_nodes(anchorPos, orientation, run_wormhole_node_func) portal_definition.shape.apply_func_to_wormhole_nodes(anchorPos, orientation, run_wormhole_node_func)
@ -1008,6 +1067,9 @@ function run_wormhole(pos, time_elapsed)
if portal_definition.on_run_wormhole ~= nil then if portal_definition.on_run_wormhole ~= nil then
portal_definition.on_run_wormhole(portal_definition, anchorPos, orientation) portal_definition.on_run_wormhole(portal_definition, anchorPos, orientation)
end end
local wormholePos = portal_definition.shape.get_wormholePos_from_anchorPos(anchorPos, orientation)
ambient_sound_play(portal_definition, wormholePos, meta)
end end
end end
end end
@ -1250,10 +1312,12 @@ local portaldef_default = {
wormhole_node_color = 0, wormhole_node_color = 0,
frame_node_name = "default:obsidian", frame_node_name = "default:obsidian",
particle_texture = "nether_particle.png", particle_texture = "nether_particle.png",
sound_ambient = "nether_portal_hum", sounds = {
sound_ignite = "", ambient = {name = "nether_portal_ambient", gain = 0.6, length = 3},
sound_extinguish = "", ignite = {name = "nether_portal_ignite", gain = 0.3},
sound_teleport = "", extinguish = {name = "nether_portal_extinguish", gain = 0.3},
teleport = {name = "nether_portal_teleport", gain = 0.3}
}
} }
@ -1268,7 +1332,8 @@ function nether.register_portal(name, portaldef)
portaldef.name = name portaldef.name = name
-- use portaldef_default for any values missing from portaldef -- use portaldef_default for any values missing from portaldef or portaldef.sounds
if portaldef.sounds ~= nil then setmetatable(portaldef.sounds, {__index = portaldef_default.sounds}) end
setmetatable(portaldef, {__index = portaldef_default}) setmetatable(portaldef, {__index = portaldef_default})
if portaldef.particle_color == nil then if portaldef.particle_color == nil then

View File

@ -4,6 +4,11 @@ Portal API Reference
The portal system used to get to the Nether can be used to create portals The portal system used to get to the Nether can be used to create portals
to other realms. to other realms.
Portal code is more efficient when each type of portal uses a different type
of node to build its frame out of, however it is possible to register more than
one kind of portal with the same frame material — such as obsidian — provided
the size of the PortalShape is distinct from any other type of portal that is
using the same node as its frame.
Helper functions Helper functions
@ -16,8 +21,8 @@ Helper functions
* `nether.find_surface_target_y(target_x, target_z, portal_name)`: returns a * `nether.find_surface_target_y(target_x, target_z, portal_name)`: returns a
suitable anchorPos suitable anchorPos
* Can be used to implement custom find_surface_anchorPos() functions * Can be used when implementing custom find_surface_anchorPos() functions
* portal_name is optional, and providing it allows existing portals on the * portal_name is optional, providing it allows existing portals on the
surface to be reused. surface to be reused.
* `nether.find_nearest_working_portal(portal_name, anchorPos, distance_limit, y_factor)`: returns * `nether.find_nearest_working_portal(portal_name, anchorPos, distance_limit, y_factor)`: returns
@ -33,9 +38,9 @@ Helper functions
API functions API functions
------------- -------------
Call these functions only at load time! Call these functions only at load time:
* `nether.register_portal(name, portal definition)` * `nether.register_portal(name, portal_definition)`
* Returns true on success. Can return false if the portal definition * Returns true on success. Can return false if the portal definition
clashes with a portal already registered by another mod, e.g. if the size clashes with a portal already registered by another mod, e.g. if the size
and frame node is not unique. and frame node is not unique.
@ -73,10 +78,15 @@ Used by `nether.register_portal`.
-- Required. For best results, have your portal constructed of a -- Required. For best results, have your portal constructed of a
-- material nobody else is using. -- material nobody else is using.
sound_ambient = "nether_portal_hum", sounds = {
sound_ignite = "", ambient = <SimpleSoundSpec>,
sound_extinguish = "", -- if the ambient SimpleSoundSpec is a table it can also contain a
sound_teleport = "", -- "length" int, which is the number of seconds to wait before
-- repeating the ambient sound. Default is 3.
ignite = <SimpleSoundSpec>,
extinguish = <SimpleSoundSpec>,
teleport = <SimpleSoundSpec>,
}
within_realm = function(pos), within_realm = function(pos),
-- Required. Return true if a portal at pos is in the realm, rather -- Required. Return true if a portal at pos is in the realm, rather
@ -90,19 +100,22 @@ Used by `nether.register_portal`.
-- portal will be used. -- portal will be used.
find_surface_anchorPos = function(realm_anchorPos), find_surface_anchorPos = function(realm_anchorPos),
-- Optional. If you don't use this then a position near the surface -- Optional. If you don't implement this then a position near the
-- will be picked. -- surface will be picked.
-- Return an anchorPos or anchorPos, orientation -- Return an anchorPos or (anchorPos, orientation)
-- If orientation is not specified then the orientation of the realm -- If orientation is not specified then the orientation of the realm
-- portal will be used. -- portal will be used.
on_run_wormhole = function(portalDef, anochorPos, orientation) on_run_wormhole = function(portalDef, anochorPos, orientation),
-- invoked once per second per portal -- invoked once per second per portal
on_extinguish = function(portalDef, anochorPos, orientation) on_extinguish = function(portalDef, anochorPos, orientation),
-- invoked when a portal is extinguished, including when the portal -- invoked when a portal is extinguished, including when the portal
-- it connected to was extinguished. -- it connected to was extinguished.
on_player_teleported = function(portalDef, player, oldPos, newPos),
todo: on_ignite = function(portalDef, anochorPos, orientation) -- invoked immediately after a player is teleported
todo: on_player_teleported = function(portalDef, oldPos, newPos) on_ignite = function(portalDef, anochorPos, orientation)
todo: on_created = function(portalDef, anochorPos, orientation) -- invoked when a player or mesecon ignites a portal
on_created = function(portalDef, anochorPos, orientation)
-- invoked when a portal's remote twin is created, usually when a
-- player travels through a portal for the first time.
} }