~jadedctrl/gem-xwx-moe

~jadedctrl/gem-xwx-moe/gemujo_ludo/mods.niaj/fasado/subtitles/Sound.lua
 ..
0 --[[
1 Subtitles — adds subtitles to Minetest.
2
3 Copyright © 2022‒2023, Silver Sandstone <@SilverSandstone@craftodon.social>
4
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the "Software"),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included
13 in all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 DEALINGS IN THE SOFTWARE.
22 ]]
23
24
25 --- Provides the Sound class, which represents a playing sound effect.
26
27
28 local ORIGIN = vector.new(0, 0, 0);
29
30
31 --- Represents a playing sound effect.
32 -- Sound objects should be treated as immutable, as they are shared between
33 -- all players.
34 -- @type Sound
35 subtitles.Sound = subtitles.Object:extend();
36
37 --- Constructor.
38 -- @param spec The sound's SimpleSoundSpec, as a table or string.
39 -- @param parameters The sound's parameters, or nil.
40 -- @param handle The sound's numeric handle, or nil if the sound is ephemeral.
41 function subtitles.Sound:new(spec, parameters, handle)
42 if type(spec) == 'string' then
43 spec = {name = spec};
44 end;
45 self.spec = spec or {};
46 self.parameters = table.copy(parameters or {});
47 self.handle = handle;
48 self.ephemeral = not handle;
49
50 local params = subtitles.registered_parameters[self:get_name()];
51 if params then
52 for key, value in pairs(params) do
53 self.parameters[key] = self.parameters[key] or value;
54 end;
55 end;
56 end;
57
58 --- Checks which players the sound is audible to.
59 -- @return A sequence of player ObjectRefs.
60 function subtitles.Sound:get_players()
61 if self.parameters.to_player then
62 return {minetest.get_player_by_name(self.parameters.to_player)};
63 elseif self.parameters.exclude_player then
64 local players = {};
65 for __, player in ipairs(minetest.get_connected_players()) do
66 if player:get_player_name() ~= self.parameters.exclude_player then
67 table.insert(players, player);
68 end;
69 end;
70 return players;
71 else
72 return minetest.get_connected_players();
73 end;
74 end;
75
76 --- Checks if this sound is within the player's range.
77 -- @param player The player's ObjectRef.
78 -- @return true if the sound is in range.
79 function subtitles.Sound:is_in_range_of_player(player)
80 local sound_pos = self:get_pos();
81 if not sound_pos then
82 return true;
83 end;
84 local distance = vector.distance(player:get_pos(), sound_pos);
85 return distance <= self:get_max_distance();
86 end;
87
88 --- Returns the sound's maximum subtitle distance.
89 -- @return The maximum distance in metres.
90 function subtitles.Sound:get_max_distance()
91 return tonumber(self.parameters.max_subtitle_distance)
92 or tonumber(self.parameters.max_hear_distance)
93 or subtitles.DEFAULT_MAX_HEAR_DISTANCE;
94 end;
95
96 --- Returns the sound's expected duration
97 -- @return The duration in seconds.
98 function subtitles.Sound:get_duration()
99 local duration = tonumber(self.parameters.subtitle_duration)
100 or tonumber(self.parameters.duration)
101 or tonumber(self.spec.subtitle_duration)
102 or tonumber(self.spec.duration);
103 if duration then
104 return duration;
105 end;
106
107 if not self.parameters.loop then
108 return subtitles.DEFAULT_DURATION;
109 end;
110
111 return nil;
112 end;
113
114 --- Returns the sound's description.
115 -- @return A human-readable description string.
116 function subtitles.Sound:get_description()
117 -- Description specified in spec or parameters:
118 local desc = self.parameters.subtitle
119 or self.parameters.description
120 or self.spec.subtitle
121 or self.spec.description;
122 if desc then
123 return desc;
124 end;
125
126 -- Registered description associated with sound name:
127 desc = subtitles.registered_descriptions[self:get_name()];
128 if desc then
129 return desc;
130 end;
131
132 -- Fallback — just show the technical name:
133 local name = self:get_name();
134 subtitles.report_missing(name);
135 return '<' .. name .. '>';
136 end;
137
138 --- Returns the sound's technical name.
139 -- @return A sound name string.
140 function subtitles.Sound:get_name()
141 return self.spec.name or '';
142 end;
143
144 --- Calculates the sound's current position.
145 -- This may change if the sound is attached to an object.
146 -- @return An absolute position vector.
147 function subtitles.Sound:get_pos()
148 local pos;
149 if self.parameters.object then
150 local object_pos = self.parameters.object:get_pos();
151 if object_pos then
152 self.last_object_pos = object_pos;
153 else
154 object_pos = self.last_object_pos;
155 end;
156 pos = object_pos or ORIGIN;
157 end;
158 if self.parameters.pos then
159 pos = vector.add((pos or ORIGIN), self.parameters.pos);
160 end;
161 return pos;
162 end;
163
164 --- Returns the sound's position from the player's perspective.
165 -- @param player The player's ObjectRef.
166 -- @return A vector relative to the player's position and rotation.
167 function subtitles.Sound:get_relative_pos_for_player(player)
168 local pos = self:get_pos();
169 if not pos then
170 return nil;
171 end;
172
173 local player_pos = player:get_pos();
174 local player_yaw = player:get_look_horizontal() or 0.0;
175 pos = vector.subtract(pos, player_pos)
176 pos = vector.rotate(pos, vector.new(0, -player_yaw, 0));
177 return pos;
178 end;
179
180 --- Returns the key to use for merging this sound with other sounds.
181 -- Multiple sounds with the same merge key will be merged into a single
182 -- subtitle.
183 -- @return A table key, or nil to not merge.
184 function subtitles.Sound:get_merge_key()
185 local key = self.parameters.merge_subtitle
186 or self.spec.merge_subtitle;
187 if key == true then
188 return self:get_name();
189 elseif key then
190 return key;
191 end;
192
193 if self.ephemeral then
194 return self:get_name();
195 end;
196
197 return nil;
198 end;
199
200 --- Checks if this sound should be exempt from subtitles.
201 -- @return true to disable the subtitle.
202 function subtitles.Sound:is_exempt()
203 return self.spec.no_subtitle
204 or self.parameters.no_subtitle
205 or self:get_name() == ''
206 or self:get_description() == '';
207 end;
208
209 --- Checks if this sound is dynamically positioned (attached to an object).
210 -- @return true if the sound is dynamic.
211 function subtitles.Sound:is_dynamic()
212 return self.params.object ~= nil;
213 end;