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