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