]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/effects/qc/globalsound.qc
Merge branch 'master' into develop
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / effects / qc / globalsound.qc
1 #include "globalsound.qh"
2
3 #include <common/ent_cs.qh>
4
5         #include <common/animdecide.qh>
6
7         REGISTER_NET_TEMP(globalsound)
8         REGISTER_NET_TEMP(playersound)
9
10         #ifdef GAMEQC
11                 REPLICATE(cvar_cl_autotaunt, float, "cl_autotaunt");
12                 REPLICATE(cvar_cl_voice_directional, int, "cl_voice_directional");
13                 REPLICATE(cvar_cl_voice_directional_taunt_attenuation, float, "cl_voice_directional_taunt_attenuation");
14         #endif
15
16         #ifdef SVQC
17                 /**
18                  * @param from the source entity, its position is sent
19                  * @param gs the global sound def
20                  * @param r a random number in 0..1
21                  */
22                 void globalsound(int channel, entity from, entity gs, float r, int chan, float _vol, float _atten, float _pitch)
23                 {
24                         //assert(IS_PLAYER(from), eprint(from));
25                         if (channel == MSG_ONE && !IS_REAL_CLIENT(msg_entity)) return;
26                         if (!autocvar_g_debug_globalsounds) {
27                                 string sample = GlobalSound_sample(gs.m_globalsoundstr, r);
28                                 switch (channel) {
29                                         case MSG_ONE:
30                                                 soundto(channel, from, chan, sample, _vol, _atten, _pitch);
31                                                 break;
32                                         case MSG_ALL:
33                                                 if(sound_allowed(MSG_BROADCAST, from))
34                                                         sound7(from, chan, sample, _vol, _atten, _pitch, 0);
35                                                 break;
36                                 }
37                                 return;
38                         }
39                         // FIXME: pitch not implemented
40                         WriteHeader(channel, globalsound);
41                         WriteByte(channel, gs.m_id);
42                         WriteByte(channel, r * 255);
43                         WriteByte(channel, etof(from));
44                         WriteByte(channel, chan);
45                         WriteByte(channel, floor(_vol * 255));
46                         WriteByte(channel, floor(_atten * 64));
47                         entcs_force_origin(from);
48                         vector o = from.origin + 0.5 * (from.mins + from.maxs);
49                         WriteVector(channel, o);
50                 }
51
52                 /**
53                 * @param from the source entity, its position is sent
54                 * @param ps the player sound def
55                 * @param r a random number in 0..1
56                 */
57                 void playersound(int channel, entity from, entity ps, float r, int chan, float _vol, float _atten, float _pitch)
58                 {
59                         //assert(IS_PLAYER(from), eprint(from));
60                         if (channel == MSG_ONE && !IS_REAL_CLIENT(msg_entity)) return;
61                         if (!autocvar_g_debug_globalsounds) {
62                                 //UpdatePlayerSounds(from);
63                                 string s = from.(ps.m_playersoundfld);
64                                 string sample = GlobalSound_sample(s, r);
65                                 switch (channel) {
66                                         case MSG_ONE:
67                                                 soundto(channel, from, chan, sample, _vol, _atten, _pitch);
68                                                 break;
69                                         case MSG_ALL:
70                                                 if(sound_allowed(MSG_BROADCAST, from))
71                                                         sound7(from, chan, sample, _vol, _atten, _pitch, 0);
72                                                 break;
73                                 }
74                                 return;
75                         }
76                         // FIXME: pitch not implemented
77                         WriteHeader(channel, playersound);
78                         WriteByte(channel, ps.m_id);
79                         WriteByte(channel, r * 255);
80                         WriteByte(channel, etof(from));
81                         WriteByte(channel, chan);
82                         WriteByte(channel, floor(_vol * 255));
83                         WriteByte(channel, floor(_atten * 64));
84                         entcs_force_origin(from);
85                         vector o = from.origin + 0.5 * (from.mins + from.maxs);
86                         WriteVector(channel, o);
87                 }
88         #endif
89
90         #ifdef CSQC
91
92                 NET_HANDLE(globalsound, bool isnew)
93                 {
94                         entity gs = REGISTRY_GET(GlobalSounds, ReadByte());
95                         float r = ReadByte() / 255;
96                         string sample = GlobalSound_sample(gs.m_globalsoundstr, r);
97                         int who = ReadByte();
98                         entity e = entcs_receiver(who - 1);
99                         int chan = ReadSByte();
100                         float vol = ReadByte() / 255;
101                         float atten = ReadByte() / 64;
102                         vector o = ReadVector();
103                         // TODO: is this really what we want to be doing? Footsteps that follow the player at head height?
104                         if (who == player_currententnum) e = findfloat(NULL, entnum, who);  // play at camera position for full volume
105                         else if (e) e.origin = o;
106                         if (e)
107                         {
108                                 sound7(e, chan, sample, vol, atten, 0, 0);
109                         }
110                         else
111                         {
112                                 // Can this happen?
113                                 LOG_WARNF("Missing entcs data for player %d", who);
114                                 sound8(e, o, chan, sample, vol, atten, 0, 0);
115                         }
116                         return true;
117                 }
118
119                 NET_HANDLE(playersound, bool isnew)
120                 {
121                         entity ps = REGISTRY_GET(PlayerSounds, ReadByte());
122                         float r = ReadByte() / 255;
123                         int who = ReadByte();
124                         entity e = entcs_receiver(who - 1);
125                         UpdatePlayerSounds(e);
126                         string s = e.(ps.m_playersoundfld);
127                         string sample = GlobalSound_sample(s, r);
128                         int chan = ReadSByte();
129                         float vol = ReadByte() / 255;
130                         float atten = ReadByte() / 64;
131                         vector o = ReadVector();
132                         if (who == player_currententnum) e = findfloat(NULL, entnum, who);  // play at camera position for full volume
133                         else if (e) e.origin = o;
134                         if (e)
135                         {
136                                 // TODO: for non-visible players, origin should probably continue to be updated as long as the sound is playing
137                                 sound7(e, chan, sample, vol, atten, 0, 0);
138                         }
139                         else
140                         {
141                                 // Can this happen?
142                                 LOG_WARNF("Missing entcs data for player %d", who);
143                                 sound8(e, o, chan, sample, vol, atten, 0, 0);
144                         }
145                         return true;
146                 }
147
148         #endif
149
150         string GlobalSound_sample(string pair, float r)
151         {
152                 int n;
153                 {
154                         string s = cdr(pair);
155                         if (s) n = stof(s);
156                         else n = 0;
157                 }
158                 string sample = car(pair);
159                 if (n > 0) sample = sprintf("%s%d.wav", sample, floor(r * n + 1));  // randomization
160                 else sample = sprintf("%s.wav", sample);
161                 return sample;
162         }
163
164         float GlobalSound_pitch(float _pitch)
165         {
166                 // customizable gradient function that crosses (0,a), (c,1) and asymptotically approaches b
167                 float a = 1.5; // max pitch
168                 float b = 0.75; // min pitch
169                 float c = 100; // standard pitch (scale * 100)
170                 float d = _pitch;
171                 float pitch_shift = (b*d*(a-1) + a*c*(1-b)) / (d*(a-1) + c*(1-b));
172
173                 return pitch_shift * 100;
174         }
175
176         void PrecacheGlobalSound(string sample)
177         {
178                 int n;
179                 {
180                         string s = cdr(sample);
181                         if (s) n = stof(s);
182                         else n = 0;
183                 }
184                 sample = car(sample);
185                 if (n > 0)
186                 {
187                         for (int i = 1; i <= n; ++i)
188                                 precache_sound(sprintf("%s%d.wav", sample, i));
189                 }
190                 else
191                 {
192                         precache_sound(sprintf("%s.wav", sample));
193                 }
194         }
195
196         entity GetVoiceMessage(string type)
197         {
198                 FOREACH(PlayerSounds, it.m_playersoundstr == type && it.instanceOfVoiceMessage == true, return it);
199                 return NULL;
200         }
201
202         entity GetPlayerSound(string type)
203         {
204                 FOREACH(PlayerSounds, it.m_playersoundstr == type && it.instanceOfVoiceMessage == false, return it);
205                 return NULL;
206         }
207
208         .string _GetPlayerSoundSampleField(string type, bool voice)
209         {
210                 GetPlayerSoundSampleField_notFound = false;
211                 entity e = voice ? GetVoiceMessage(type) : GetPlayerSound(type);
212                 if (e) return e.m_playersoundfld;
213                 GetPlayerSoundSampleField_notFound = true;
214                 return playersound_taunt.m_playersoundfld;
215         }
216
217         .string GetVoiceMessageSampleField(string type)
218         {
219                 return _GetPlayerSoundSampleField(type, true);
220         }
221
222         void PrecachePlayerSounds(string f)
223         {
224                 int fh = fopen(f, FILE_READ);
225                 if (fh < 0)
226                 {
227                         LOG_WARNF("Player sound file not found: %s", f);
228                         return;
229                 }
230                 for (string s; (s = fgets(fh)); )
231                 {
232                         int n = tokenize_console(s);
233                         if (n != 3)
234                         {
235                                 if (n != 0) LOG_WARNF("Invalid sound info line: %s", s);
236                                 continue;
237                         }
238                         string file = argv(1);
239                         string variants = argv(2);
240                         PrecacheGlobalSound(strcat(file, " ", variants));
241                 }
242                 fclose(fh);
243         }
244
245         //#ifdef CSQC
246
247                 .string GetPlayerSoundSampleField(string type)
248                 {
249                         return _GetPlayerSoundSampleField(type, false);
250                 }
251
252                 void ClearPlayerSounds(entity this)
253                 {
254                         FOREACH(PlayerSounds, true, {
255                                 .string fld = it.m_playersoundfld;
256                                 if (this.(fld))
257                                 {
258                                         strfree(this.(fld));
259                                 }
260                         });
261                 }
262
263                 bool LoadPlayerSounds(entity this, string f, bool strict)
264                 {
265                         int fh = fopen(f, FILE_READ);
266                         if (fh < 0)
267                         {
268                                 if (strict) LOG_WARNF("Player sound file not found: %s", f);
269                                 return false;
270                         }
271                         for (string s; (s = fgets(fh)); )
272                         {
273                                 int n = tokenize_console(s);
274                                 if (n != 3)
275                                 {
276                                         if (n != 0) LOG_WARNF("Invalid sound info line: %s", s);
277                                         continue;
278                                 }
279                                 string key = argv(0);
280                                 var.string field = GetPlayerSoundSampleField(key);
281                                 if (GetPlayerSoundSampleField_notFound) field = GetVoiceMessageSampleField(key);
282                                 if (GetPlayerSoundSampleField_notFound)
283                                 {
284                                         LOG_TRACEF("Invalid sound info field in player sound file '%s': %s", f, key);
285                                         continue;
286                                 }
287                                 string file = argv(1);
288                                 string variants = argv(2);
289                                 strcpy(this.(field), strcat(file, " ", variants));
290                         }
291                         fclose(fh);
292                         return true;
293                 }
294
295                 .string model_for_playersound;
296                 .int skin_for_playersound;
297
298                 bool autocvar_g_debug_defaultsounds;
299
300                 void UpdatePlayerSounds(entity this)
301                 {
302                         if (this.model == this.model_for_playersound && this.skin == this.skin_for_playersound) return;
303                         strcpy(this.model_for_playersound, this.model);
304                         this.skin_for_playersound = this.skin;
305                         ClearPlayerSounds(this);
306                         LoadPlayerSounds(this, "sound/player/default.sounds", true);
307                         if (this.model == "null"
308                         #ifdef SVQC
309                                 && autocvar_g_debug_globalsounds
310                         #endif
311                         ) return;
312                         if (autocvar_g_debug_defaultsounds) return;
313                         if (LoadPlayerSounds(this, get_model_datafilename(this.model, this.skin, "sounds"), false)) return;
314                         LoadPlayerSounds(this, get_model_datafilename(this.model, 0, "sounds"), true);
315                 }
316
317         //#endif
318
319         #ifdef SVQC
320
321                 void _GlobalSound(entity this, entity gs, entity ps, string sample, int chan, float vol, int voicetype, bool fake)
322                 {
323                         if (gs == NULL && ps == NULL && sample == "") return;
324                         if(this.classname == "body") return;
325                         float r = random();
326                         float myscale = ((this.scale) ? this.scale : 1); // safety net
327                         float thepitch = ((myscale == 1) ? 0 : GlobalSound_pitch(myscale * 100));
328                         if (sample != "") sample = GlobalSound_sample(sample, r);
329                         switch (voicetype)
330                         {
331                                 case VOICETYPE_LASTATTACKER_ONLY:
332                                 case VOICETYPE_LASTATTACKER:
333                                 {
334                                         if (!fake)
335                                         {
336                                                 if (!this.pusher) break;
337                                                 msg_entity = this.pusher;
338                                                 if (IS_REAL_CLIENT(msg_entity))
339                                                 {
340                                                         float atten = (CS_CVAR(msg_entity).cvar_cl_voice_directional == 1) ? ATTEN_MIN : ATTEN_NONE;
341                                                         if (gs) globalsound(MSG_ONE, this, gs, r, chan, vol, atten, thepitch);
342                                                         else if (ps) playersound(MSG_ONE, this, ps, r, chan, vol, atten, thepitch);
343                                                         else soundto(MSG_ONE, this, chan, sample, vol, atten, thepitch);
344                                                 }
345                                         }
346                                         if (voicetype == VOICETYPE_LASTATTACKER_ONLY) break;
347                                         msg_entity = this;
348                                         if (IS_REAL_CLIENT(msg_entity))
349                                         {
350                                                 if (gs) globalsound(MSG_ONE, this, gs, r, chan, VOL_BASE, ATTEN_NONE, thepitch);
351                                                 else if (ps) playersound(MSG_ONE, this, ps, r, chan, VOL_BASE, ATTEN_NONE, thepitch);
352                                                 else soundto(MSG_ONE, this, chan, sample, VOL_BASE, ATTEN_NONE, thepitch);
353                                         }
354                                         break;
355                                 }
356                                 case VOICETYPE_TEAMRADIO:
357                                 {
358                                         #define X() \
359                                                 MACRO_BEGIN \
360                                                         float atten = (CS_CVAR(msg_entity).cvar_cl_voice_directional == 1) ? ATTEN_MIN : ATTEN_NONE; \
361                                                         if (gs) globalsound(MSG_ONE, this, gs, r, chan, vol, atten, thepitch); \
362                                                         else if (ps) playersound(MSG_ONE, this, ps, r, chan, vol, atten, thepitch); \
363                                                         else soundto(MSG_ONE, this, chan, sample, vol, atten, thepitch); \
364                                                 MACRO_END
365
366                                         if (fake) { msg_entity = this; X(); }
367                                         else
368                                         {
369                                                 FOREACH_CLIENT(IS_REAL_CLIENT(it) && SAME_TEAM(it, this), {
370                                                         msg_entity = it;
371                                                         X();
372                                                 });
373                                         }
374                                         #undef X
375                                         break;
376                                 }
377                                 case VOICETYPE_AUTOTAUNT:
378                                 case VOICETYPE_TAUNT:
379                                 {
380                                         if (voicetype == VOICETYPE_AUTOTAUNT)
381                                         {
382                                                 if (!autocvar_sv_autotaunt) break;
383                                         }
384                                         else if (IS_PLAYER(this) && !IS_DEAD(this))
385                                                 animdecide_setaction(this, ANIMACTION_TAUNT, true);
386
387                                         if (!autocvar_sv_taunt) break;
388                                         if (autocvar_sv_gentle) break;
389                                         float tauntrand = 0;
390                                         if (voicetype == VOICETYPE_AUTOTAUNT) tauntrand = random();
391
392                                         #define X() \
393                                                 MACRO_BEGIN \
394                                                         if (voicetype != VOICETYPE_AUTOTAUNT || tauntrand < CS_CVAR(msg_entity).cvar_cl_autotaunt) \
395                                                         { \
396                                                                 float atten = (CS_CVAR(msg_entity).cvar_cl_voice_directional >= 1) \
397                                                                         ? bound(ATTEN_MIN, CS_CVAR(msg_entity).cvar_cl_voice_directional_taunt_attenuation, \
398                                                                         ATTEN_MAX) \
399                                                                         : ATTEN_NONE; \
400                                                                 if (gs) globalsound(MSG_ONE, this, gs, r, chan, vol, atten, thepitch); \
401                                                                 else if (ps) playersound(MSG_ONE, this, ps, r, chan, vol, atten, thepitch); \
402                                                                 else soundto(MSG_ONE, this, chan, sample, vol, atten, thepitch); \
403                                                         } \
404                                                 MACRO_END
405                                         if (fake)
406                                         {
407                                                 msg_entity = this;
408                                                 X();
409                                         }
410                                         else
411                                         {
412                                                 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
413                                                         msg_entity = it;
414                                                         X();
415                                                 });
416                                         }
417                                         #undef X
418                                         break;
419                                 }
420                                 case VOICETYPE_PLAYERSOUND:
421                                 {
422                                         msg_entity = this;
423                                         if (fake)
424                                         {
425                                                 if (gs) globalsound(MSG_ONE, this, gs, r, chan, vol, ATTEN_NORM, thepitch);
426                                                 else if (ps) playersound(MSG_ONE, this, ps, r, chan, vol, ATTEN_NORM, thepitch);
427                                                 else soundto(MSG_ONE, this, chan, sample, vol, ATTEN_NORM, thepitch);
428                                         }
429                                         else
430                                         {
431                                                 if (gs) globalsound(MSG_ALL, this, gs, r, chan, vol, ATTEN_NORM, thepitch);
432                                                 else if (ps) playersound(MSG_ALL, this, ps, r, chan, vol, ATTEN_NORM, thepitch);
433                                                 else if (sound_allowed(MSG_BROADCAST, this)) sound7(this, chan, sample, vol, ATTEN_NORM, thepitch, 0);
434                                         }
435                                         break;
436                                 }
437                                 default:
438                                 {
439                                         backtrace("Invalid voice type!");
440                                         break;
441                                 }
442                         }
443                 }
444
445         #endif