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