]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/cl_player.qc
Merge remote-tracking branch 'origin/nifrek/hagar_own_traileffect_fix'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / cl_player.qc
1 .entity accuracy;
2 .float accuracy_frags[WEP_MAXCOUNT];
3
4 float weaponstats_buffer;
5
6 void WeaponStats_Init()
7 {
8         if(autocvar_sv_weaponstats_file != "")
9                 weaponstats_buffer = buf_create();
10         else
11                 weaponstats_buffer = -1;
12 }
13
14 #define WEAPONSTATS_GETINDEX(awep,abot,vwep,vbot) (((vwep) + (awep) * (WEP_LAST - WEP_FIRST + 1) - (WEP_FIRST + WEP_FIRST * (WEP_LAST - WEP_FIRST + 1))) * 4 + (abot) * 2 + (vbot))
15
16 void WeaponStats_ready(entity fh, entity pass, float status)
17 {
18         float i, j, n, ibot, jbot, idx;
19         vector v;
20         string prefix, s;
21         switch(status)
22         {
23                 case URL_READY_CANWRITE:
24                         // we can write
25                         prefix = strcat(autocvar_hostname, "\t", GetGametype(), "_", GetMapname(), "\t");
26                         url_fputs(fh, "#begin statsfile\n");
27                         url_fputs(fh, strcat("#date ", strftime(TRUE, "%a %b %e %H:%M:%S %Z %Y"), "\n"));
28 #ifdef WATERMARK
29                         url_fputs(fh, strcat("#version ", WATERMARK(), "\n"));
30 #endif
31                         url_fputs(fh, strcat("#config ", ftos(crc16(FALSE, cvar_purechanges)), "\n"));
32                         url_fputs(fh, strcat("#cvar_purechanges ", ftos(cvar_purechanges_count), "\n"));
33                         n = tokenizebyseparator(cvar_purechanges, "\n");
34                         for(i = 0; i < n; ++i)
35                                 url_fputs(fh, strcat("#cvar_purechange ", argv(i), "\n"));
36                         for(i = WEP_FIRST; i <= WEP_LAST; ++i) for(ibot = 0; ibot <= 1; ++ibot)
37                                 for(j = WEP_FIRST; j <= WEP_LAST; ++j) for(jbot = 0; jbot <= 1; ++jbot)
38                                 {
39                                         idx = WEAPONSTATS_GETINDEX(i, ibot, j, jbot);
40                                         v = stov(bufstr_get(weaponstats_buffer, idx));
41                                         if(v != '0 0 0')
42                                         {
43                                                 //vector is: kills hits damage
44                                                 url_fputs(fh, sprintf("%s%d %d\t%d %d\t", prefix, i, ibot, j, jbot));
45                                                 url_fputs(fh, sprintf("%d %d %g\n", v_x, v_y, v_z));
46                                         }
47                                 }
48                         url_fputs(fh, "#end\n\n");
49                         url_fclose(fh);
50                         break;
51                 case URL_READY_CANREAD:
52                         // url_fclose is processing, we got a response for writing the data
53                         // this must come from HTTP
54                         print("Got response from weapon stats server:\n");
55                         while((s = url_fgets(fh)))
56                                 print("  ", s, "\n");
57                         print("End of response.\n");
58                         url_fclose(fh);
59                         break;
60                 case URL_READY_CLOSED:
61                         // url_fclose has finished
62                         print("Weapon stats written\n");
63                         buf_del(weaponstats_buffer);
64                         weaponstats_buffer = -1;
65                         break;
66                 case URL_READY_ERROR:
67                 default:
68                         print("Weapon stats writing failed: ", ftos(status), "\n");
69                         buf_del(weaponstats_buffer);
70                         weaponstats_buffer = -1;
71                         break;
72         }
73 }
74
75 void WeaponStats_Shutdown()
76 {
77         if(weaponstats_buffer < 0)
78                 return;
79         if(autocvar_sv_weaponstats_file != "")
80         {
81                 url_multi_fopen(autocvar_sv_weaponstats_file, FILE_APPEND, WeaponStats_ready, world);
82         }
83         else
84         {
85                 buf_del(weaponstats_buffer);
86                 weaponstats_buffer = -1;
87         }
88 }
89
90 void WeaponStats_LogItem(float awep, float abot, float vwep, float vbot, vector item)
91 {
92         float idx;
93         if(weaponstats_buffer < 0)
94                 return;
95         if(awep < WEP_FIRST || vwep < WEP_FIRST)
96                 return;
97         if(awep > WEP_LAST || vwep > WEP_LAST)
98                 return;
99         idx = WEAPONSTATS_GETINDEX(awep,abot,vwep,vbot);
100         bufstr_set(weaponstats_buffer, idx, vtos(stov(bufstr_get(weaponstats_buffer, idx)) + item));
101 }
102 void WeaponStats_LogDamage(float awep, float abot, float vwep, float vbot, float damage)
103 {
104         if(damage < 0)
105                 error("negative damage?");
106         WeaponStats_LogItem(awep, abot, vwep, vbot, '0 0 1' * damage + '0 1 0');
107 }
108 void WeaponStats_LogKill(float awep, float abot, float vwep, float vbot)
109 {
110         WeaponStats_LogItem(awep, abot, vwep, vbot, '1 0 0');
111 }
112
113 // changes by LordHavoc on 03/29/04 and 03/30/04 at Vermeulen's request
114 // merged player_run and player_stand to player_anim
115 // added death animations to player_anim
116 // can now spawn thrown weapons from anywhere, not just from players
117 // thrown weapons now fade out after 20 seconds
118 // created PlayerGib function
119 // PlayerDie no longer uses hitloc or damage
120 // PlayerDie now supports dying animations as well as gibbing
121 // cleaned up PlayerDie a lot
122 // added CopyBody
123
124 .entity pusher;
125 .float pushltime;
126
127 void CopyBody(float keepvelocity)
128 {
129         entity oldself;
130         if (self.effects & EF_NODRAW)
131                 return;
132         oldself = self;
133         self = spawn();
134         self.enemy = oldself;
135         self.lip = oldself.lip;
136         self.colormap = oldself.colormap;
137         self.glowmod = oldself.glowmod;
138         self.iscreature = oldself.iscreature;
139         self.damagedbycontents = oldself.damagedbycontents;
140         self.angles = oldself.angles;
141         self.avelocity = oldself.avelocity;
142         self.classname = "body";
143         self.damageforcescale = oldself.damageforcescale;
144         self.effects = oldself.effects;
145         self.event_damage = oldself.event_damage;
146         self.animstate_startframe = oldself.animstate_startframe;
147         self.animstate_numframes = oldself.animstate_numframes;
148         self.animstate_framerate = oldself.animstate_framerate;
149         self.animstate_starttime = oldself.animstate_starttime;
150         self.animstate_endtime = oldself.animstate_endtime;
151         self.animstate_override = oldself.animstate_override;
152         self.animstate_looping = oldself.animstate_looping;
153         self.frame = oldself.frame;
154         self.dead_frame = oldself.dead_frame;
155         self.pain_finished = oldself.pain_finished;
156         self.health = oldself.health;
157         self.armorvalue = oldself.armorvalue;
158         self.armortype = oldself.armortype;
159         self.model = oldself.model;
160         self.modelindex = oldself.modelindex;
161         self.modelindex_lod0 = oldself.modelindex_lod0;
162         self.modelindex_lod0_from_xonotic = oldself.modelindex_lod0_from_xonotic;
163         self.modelindex_lod1 = oldself.modelindex_lod1;
164         self.modelindex_lod2 = oldself.modelindex_lod2;
165         self.skinindex = oldself.skinindex;
166         self.species = oldself.species;
167         self.movetype = oldself.movetype;
168         self.nextthink = oldself.nextthink;
169         self.solid = oldself.solid;
170         self.ballistics_density = oldself.ballistics_density;
171         self.takedamage = oldself.takedamage;
172         self.think = oldself.think;
173         self.customizeentityforclient = oldself.customizeentityforclient;
174         self.uncustomizeentityforclient = oldself.uncustomizeentityforclient;
175         self.uncustomizeentityforclient_set = oldself.uncustomizeentityforclient_set;
176         if (keepvelocity == 1)
177                 self.velocity = oldself.velocity;
178         self.oldvelocity = self.velocity;
179         self.fade_time = oldself.fade_time;
180         self.fade_rate = oldself.fade_rate;
181         //self.weapon = oldself.weapon;
182         setorigin(self, oldself.origin);
183         setsize(self, oldself.mins, oldself.maxs);
184         self.prevorigin = oldself.origin;
185         self.reset = SUB_Remove;
186
187         Drag_MoveDrag(oldself, self);
188
189         if(self.colormap <= maxclients && self.colormap > 0)
190                 self.colormap = 1024 + self.clientcolors;
191
192         self = oldself;
193 }
194
195 float player_getspecies()
196 {
197         float s;
198         get_model_parameters(self.model, self.skinindex);
199         s = get_model_parameters_species;
200         get_model_parameters(string_null, 0);
201         if(s < 0)
202                 return SPECIES_HUMAN;
203         return s;
204 }
205
206 void player_setupanimsformodel()
207 {
208         // defaults for legacy .zym models without animinfo files
209         self.anim_die1 = animfixfps(self, '0 1 0.5'); // 2 seconds
210         self.anim_die2 = animfixfps(self, '1 1 0.5'); // 2 seconds
211         self.anim_draw = animfixfps(self, '2 1 3');
212         // self.anim_duck = '3 1 100'; // This anim is broken, use slot 3 as a new free slot in the future ;)
213         self.anim_duckwalk = animfixfps(self, '4 1 1');
214         self.anim_duckjump = '5 1 100'; // NOTE: zym anims keep playing until changed, so this only has to start the anim, landing will end it
215         self.anim_duckidle = animfixfps(self, '6 1 1');
216         self.anim_idle = animfixfps(self, '7 1 1');
217         self.anim_jump = '8 1 100'; // NOTE: zym anims keep playing until changed, so this only has to start the anim, landing will end it
218         self.anim_pain1 = animfixfps(self, '9 1 2'); // 0.5 seconds
219         self.anim_pain2 = animfixfps(self, '10 1 2'); // 0.5 seconds
220         self.anim_shoot = animfixfps(self, '11 1 5'); // analyze models and set framerate
221         self.anim_taunt = animfixfps(self, '12 1 0.33');
222         self.anim_run = animfixfps(self, '13 1 1');
223         self.anim_runbackwards = animfixfps(self, '14 1 1');
224         self.anim_strafeleft = animfixfps(self, '15 1 1');
225         self.anim_straferight = animfixfps(self, '16 1 1');
226         self.anim_dead1 = animfixfps(self, '17 1 1');
227         self.anim_dead2 = animfixfps(self, '18 1 1');
228         self.anim_forwardright = animfixfps(self, '19 1 1');
229         self.anim_forwardleft = animfixfps(self, '20 1 1');
230         self.anim_backright = animfixfps(self, '21 1 1');
231         self.anim_backleft  = animfixfps(self, '22 1 1');
232         self.anim_melee = animfixfps2(self, '23 1 1', '11 1 5');
233         self.anim_duckwalkbackwards = animfixfps2(self, '24 1 1', '4 1 1');
234         self.anim_duckwalkstrafeleft = animfixfps2(self, '25 1 1', '4 1 1');
235         self.anim_duckwalkstraferight = animfixfps2(self, '26 1 1', '4 1 1');
236         self.anim_duckwalkforwardright = animfixfps2(self, '27 1 1', '4 1 1');
237         self.anim_duckwalkforwardleft = animfixfps2(self, '28 1 1', '4 1 1');
238         self.anim_duckwalkbackright = animfixfps2(self, '29 1 1', '4 1 1');
239         self.anim_duckwalkbackleft  = animfixfps2(self, '30 1 1', '4 1 1');
240         // TODO introspect models for finding right "fps" value (1/duration)
241         // reset animstate now
242         setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
243 }
244
245 void player_anim (void)
246 {
247         updateanim(self);
248         if (self.weaponentity)
249                 updateanim(self.weaponentity);
250
251         if (self.deadflag != DEAD_NO)
252         {
253                 if (time > self.animstate_endtime)
254                 {
255                         if (self.maxs_z > 5)
256                         {
257                                 self.maxs_z = 5;
258                                 setsize(self, self.mins, self.maxs);
259                         }
260                         self.frame = self.dead_frame;
261                 }
262                 return;
263         }
264
265         if (!self.animstate_override)
266         {
267                 if (!(self.flags & FL_ONGROUND) || self.BUTTON_JUMP)
268                 {
269                         if (self.crouch)
270                         {
271                                 if (self.animstate_startframe != self.anim_duckjump_x) // don't perform another trace if already playing the crouch jump anim
272                                 {
273                                         traceline(self.origin + '0 0 1' * PL_CROUCH_MIN_z, self.origin + '0 0 1' * (PL_CROUCH_MIN_z - autocvar_sv_player_jumpanim_minfall), TRUE, self);
274                                         if(!trace_startsolid && trace_fraction == 1 || !(self.animstate_startframe == self.anim_duckwalk_x || self.animstate_startframe == self.anim_duckidle_x)) // don't get stuck on non-crouch anims
275                                         {
276                                                 setanim(self, self.anim_duckjump, FALSE, TRUE, self.restart_jump);
277                                                 self.restart_jump = FALSE;
278                                         }
279                                 }
280                         }
281                         else
282                         {
283                 if (self.animstate_startframe != self.anim_jump_x) // don't perform another trace if already playing the jump anim
284                 {
285                     traceline(self.origin + '0 0 1' * PL_MIN_z, self.origin + '0 0 1' * (PL_MIN_z - autocvar_sv_player_jumpanim_minfall), TRUE, self);
286                     if(!trace_startsolid && trace_fraction == 1 || self.animstate_startframe == self.anim_idle_x || (self.animstate_startframe == self.anim_melee_x && time - self.animstate_starttime >= 21/20)) // don't get stuck on idle animation in midair, nor melee after it finished
287                     {
288                         setanim(self, self.anim_jump, FALSE, TRUE, self.restart_jump);
289                         self.restart_jump = FALSE;
290                     }
291                 }
292                         }
293                 }
294                 else if (self.crouch)
295                 {
296                         if (self.movement_x > 0 && self.movement_y == 0)
297                                 setanim(self, self.anim_duckwalk, TRUE, FALSE, FALSE);
298                         else if (self.movement_x < 0 && self.movement_y == 0)
299                                 setanim(self, self.anim_duckwalkbackwards, TRUE, FALSE, FALSE);
300                         else if (self.movement_x == 0 && self.movement_y > 0)
301                                 setanim(self, self.anim_duckwalkstraferight, TRUE, FALSE, FALSE);
302                         else if (self.movement_x == 0 && self.movement_y < 0)
303                                 setanim(self, self.anim_duckwalkstrafeleft, TRUE, FALSE, FALSE);
304                         else if (self.movement_x > 0 && self.movement_y > 0)
305                                 setanim(self, self.anim_duckwalkforwardright, TRUE, FALSE, FALSE);
306                         else if (self.movement_x > 0 && self.movement_y < 0)
307                                 setanim(self, self.anim_duckwalkforwardleft, TRUE, FALSE, FALSE);
308                         else if (self.movement_x < 0 && self.movement_y > 0)
309                                 setanim(self, self.anim_duckwalkbackright, TRUE, FALSE, FALSE);
310                         else if (self.movement_x < 0 && self.movement_y < 0)
311                                 setanim(self, self.anim_duckwalkbackleft, TRUE, FALSE, FALSE);
312                         else
313                                 setanim(self, self.anim_duckidle, TRUE, FALSE, FALSE);
314                 }
315                 else if ((self.movement_x * self.movement_x + self.movement_y * self.movement_y) > 20)
316                 {
317                         if (self.movement_x > 0 && self.movement_y == 0)
318                                 setanim(self, self.anim_run, TRUE, FALSE, FALSE);
319                         else if (self.movement_x < 0 && self.movement_y == 0)
320                                 setanim(self, self.anim_runbackwards, TRUE, FALSE, FALSE);
321                         else if (self.movement_x == 0 && self.movement_y > 0)
322                                 setanim(self, self.anim_straferight, TRUE, FALSE, FALSE);
323                         else if (self.movement_x == 0 && self.movement_y < 0)
324                                 setanim(self, self.anim_strafeleft, TRUE, FALSE, FALSE);
325                         else if (self.movement_x > 0 && self.movement_y > 0)
326                                 setanim(self, self.anim_forwardright, TRUE, FALSE, FALSE);
327                         else if (self.movement_x > 0 && self.movement_y < 0)
328                                 setanim(self, self.anim_forwardleft, TRUE, FALSE, FALSE);
329                         else if (self.movement_x < 0 && self.movement_y > 0)
330                                 setanim(self, self.anim_backright, TRUE, FALSE, FALSE);
331                         else if (self.movement_x < 0 && self.movement_y < 0)
332                                 setanim(self, self.anim_backleft, TRUE, FALSE, FALSE);
333                         else
334                                 setanim(self, self.anim_run, TRUE, FALSE, FALSE);
335                 }
336                 else
337                         setanim(self, self.anim_idle, TRUE, FALSE, FALSE);
338         }
339
340         if (self.weaponentity)
341         if (!self.weaponentity.animstate_override)
342                 setanim(self.weaponentity, self.weaponentity.anim_idle, TRUE, FALSE, FALSE);
343 }
344
345 void SpawnThrownWeapon (vector org, float w)
346 {
347         if(g_minstagib)
348         if(self.ammo_cells <= 0)
349                 return;
350
351         if(g_pinata)
352         {
353                 float j;
354                 for(j = WEP_FIRST; j <= WEP_LAST; ++j)
355                 {
356                         if(self.weapons & W_WeaponBit(j))
357                                 if(W_IsWeaponThrowable(j))
358                                         W_ThrowNewWeapon(self, j, FALSE, org, randomvec() * 175 + '0 0 325');
359                 }
360         }
361         else
362         {
363                 if(W_IsWeaponThrowable(self.weapon))
364                         W_ThrowNewWeapon(self, self.weapon, FALSE, org, randomvec() * 125 + '0 0 200');
365         }
366 }
367
368 void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
369 {
370         float take, save;
371         vector v;
372         Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
373
374         // damage resistance (ignore most of the damage from a bullet or similar)
375         damage = max(damage - 5, 1);
376
377         v = healtharmor_applydamage(self.armorvalue, autocvar_g_balance_armor_blockpercent, damage);
378         take = v_x;
379         save = v_y;
380
381         if(sound_allowed(MSG_BROADCAST, attacker))
382         {
383                 if (save > 10)
384                         sound (self, CH_SHOTS, "misc/armorimpact.wav", VOL_BASE, ATTN_NORM);
385                 else if (take > 30)
386                         sound (self, CH_SHOTS, "misc/bodyimpact2.wav", VOL_BASE, ATTN_NORM);
387                 else if (take > 10)
388                         sound (self, CH_SHOTS, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM);
389         }
390
391         if (take > 50)
392                 Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
393         if (take > 100)
394                 Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
395
396         if (!(self.flags & FL_GODMODE))
397         {
398                 self.armorvalue = self.armorvalue - save;
399                 self.health = self.health - take;
400                 // pause regeneration for 5 seconds
401                 self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
402         }
403         self.dmg_save = self.dmg_save + save;//max(save - 10, 0);
404         self.dmg_take = self.dmg_take + take;//max(take - 10, 0);
405         self.dmg_inflictor = inflictor;
406
407         if (self.health <= -autocvar_sv_gibhealth && self.modelindex != 0)
408         {
409                 // don't use any animations as a gib
410                 self.frame = 0;
411                 self.dead_frame = 0;
412                 // view just above the floor
413                 self.view_ofs = '0 0 4';
414
415                 Violence_GibSplash(self, 1, 1, attacker);
416                 self.modelindex = 0; // restore later
417                 self.solid = SOLID_NOT; // restore later
418         }
419 }
420
421 void ClientKill_Now_TeamChange();
422 void freezetag_CheckWinner();
423
424 void PlayerDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
425 {
426         float take, save, waves, sdelay, dh, da, j;
427         vector v;
428         float valid_damage_for_weaponstats;
429         float excess;
430
431         if((g_arena && numspawned < 2) || (g_ca && !ca_teams_ok) && !inWarmupStage)
432                 return;
433
434         dh = max(self.health, 0);
435         da = max(self.armorvalue, 0);
436
437         if(!DEATH_ISSPECIAL(deathtype))
438         {
439                 damage *= sqrt(bound(1.0, self.cvar_cl_handicap, 100.0));
440                 if(self != attacker)
441                         damage /= sqrt(bound(1.0, attacker.cvar_cl_handicap, 100.0));
442         }
443
444         if(DEATH_ISWEAPON(deathtype, WEP_TUBA))
445         {
446                 // tuba causes blood to come out of the ears
447                 vector ear1, ear2;
448                 vector d;
449                 float f;
450                 ear1 = self.origin;
451                 ear1_z += 0.125 * self.view_ofs_z + 0.875 * self.maxs_z; // 7/8
452                 ear2 = ear1;
453                 makevectors(self.angles);
454                 ear1 += v_right * -10;
455                 ear2 += v_right * +10;
456                 d = inflictor.origin - self.origin;
457                 f = (d * v_right) / vlen(d); // this is cos of angle of d and v_right!
458                 force = v_right * vlen(force);
459                 Violence_GibSplash_At(ear1, force * -1, 2, bound(0, damage, 25) / 2 * (0.5 - 0.5 * f), self, attacker);
460                 Violence_GibSplash_At(ear2, force,      2, bound(0, damage, 25) / 2 * (0.5 + 0.5 * f), self, attacker);
461                 if(f > 0)
462                 {
463                         hitloc = ear1;
464                         force = force * -1;
465                 }
466                 else
467                 {
468                         hitloc = ear2;
469                         // force is already good
470                 }
471         }
472         else
473                 Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
474
475         if (!g_minstagib)
476         {
477                 v = healtharmor_applydamage(self.armorvalue, autocvar_g_balance_armor_blockpercent, damage);
478                 take = v_x;
479                 save = v_y;
480         }
481         else
482         {
483                 save = 0;
484                 take = damage;
485         }
486
487         frag_inflictor = inflictor;
488         frag_attacker = attacker;
489         frag_target = self;
490         damage_take = take;
491         damage_save = save;
492         damage_force = force;
493         MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor);
494         take = bound(0, damage_take, self.health);
495         save = bound(0, damage_save, self.armorvalue);
496         excess = max(0, damage - take - save);
497
498         if(sound_allowed(MSG_BROADCAST, attacker))
499         {
500                 if (save > 10)
501                         sound (self, CH_SHOTS, "misc/armorimpact.wav", VOL_BASE, ATTN_NORM);
502                 else if (take > 30)
503                         sound (self, CH_SHOTS, "misc/bodyimpact2.wav", VOL_BASE, ATTN_NORM);
504                 else if (take > 10)
505                         sound (self, CH_SHOTS, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM); // FIXME possibly remove them?
506         }
507
508         if (take > 50)
509                 Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
510         if (take > 100)
511                 Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
512
513         if (time >= self.spawnshieldtime)
514         {
515                 if (!(self.flags & FL_GODMODE))
516                 {
517                         self.armorvalue = self.armorvalue - save;
518                         self.health = self.health - take;
519                         // pause regeneration for 5 seconds
520                         if(take)
521                 self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
522
523                         if (time > self.pain_finished)          //Don't switch pain sequences like crazy
524                         {
525                                 self.pain_finished = time + 0.5;        //Supajoe
526
527                                 if(sv_gentle < 1) {
528                                         if(self.classname != "body") // pain anim is BORKED on our ZYMs, FIXME remove this once we have good models
529                                         {
530                                                 if (!self.animstate_override)
531                                                 {
532                                                         if (random() > 0.5)
533                                                                 setanim(self, self.anim_pain1, FALSE, TRUE, TRUE);
534                                                         else
535                                                                 setanim(self, self.anim_pain2, FALSE, TRUE, TRUE);
536                                                 }
537                                         }
538
539                                         if(sound_allowed(MSG_BROADCAST, attacker))
540                                         if(!DEATH_ISWEAPON(deathtype, WEP_LASER) || attacker != self || self.health < 2 * autocvar_g_balance_laser_primary_damage * autocvar_g_balance_selfdamagepercent + 1)
541                                         if(self.health > 1)
542                                         // exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two
543                                         {
544                                                 if(deathtype == DEATH_FALL)
545                                                         PlayerSound(playersound_fall, CH_PAIN, VOICETYPE_PLAYERSOUND);
546                                                 else if(self.health > 75) // TODO make a "gentle" version?
547                                                         PlayerSound(playersound_pain100, CH_PAIN, VOICETYPE_PLAYERSOUND);
548                                                 else if(self.health > 50)
549                                                         PlayerSound(playersound_pain75, CH_PAIN, VOICETYPE_PLAYERSOUND);
550                                                 else if(self.health > 25)
551                                                         PlayerSound(playersound_pain50, CH_PAIN, VOICETYPE_PLAYERSOUND);
552                                                 else
553                                                         PlayerSound(playersound_pain25, CH_PAIN, VOICETYPE_PLAYERSOUND);
554                                         }
555                                 }
556
557                                 // throw off bot aim temporarily
558                                 float shake;
559                                 shake = damage * 5 / (bound(0,skill,100) + 1);
560                                 self.v_angle_x = self.v_angle_x + (random() * 2 - 1) * shake;
561                                 self.v_angle_y = self.v_angle_y + (random() * 2 - 1) * shake;
562                         }
563                 }
564                 else
565                         self.max_armorvalue += (save + take);
566         }
567         self.dmg_save = self.dmg_save + save;//max(save - 10, 0);
568         self.dmg_take = self.dmg_take + take;//max(take - 10, 0);
569         self.dmg_inflictor = inflictor;
570
571         if(attacker == self)
572         {
573                 // don't reset pushltime for self damage as it may be an attempt to
574                 // escape a lava pit or similar
575                 //self.pushltime = 0;
576         }
577         else if(attacker.classname == "player")
578         {
579                 self.pusher = attacker;
580                 self.pushltime = time + autocvar_g_maxpushtime;
581         }
582         else if(time < self.pushltime)
583         {
584                 attacker = self.pusher;
585                 self.pushltime = max(self.pushltime, time + 0.6);
586         }
587         else
588                 self.pushltime = 0;
589
590         float abot, vbot, awep;
591         abot = (clienttype(attacker) == CLIENTTYPE_BOT);
592         vbot = (clienttype(self) == CLIENTTYPE_BOT);
593
594         valid_damage_for_weaponstats = 0;
595         if(vbot || clienttype(self) == CLIENTTYPE_REAL)
596         if(abot || clienttype(attacker) == CLIENTTYPE_REAL)
597         if(attacker && self != attacker)
598         if(IsDifferentTeam(self, attacker))
599         {
600                 if(DEATH_ISSPECIAL(deathtype))
601                         awep = attacker.weapon;
602                 else
603                         awep = DEATH_WEAPONOF(deathtype);
604                 valid_damage_for_weaponstats = 1;
605         }
606
607         if(valid_damage_for_weaponstats)
608         {
609                 dh = dh - max(self.health, 0);
610                 da = da - max(self.armorvalue, 0);
611                 WeaponStats_LogDamage(awep, abot, self.weapon, vbot, dh + da);
612         }
613
614         if (self.health < 1)
615         {
616                 float defer_ClientKill_Now_TeamChange;
617                 defer_ClientKill_Now_TeamChange = FALSE;
618
619                 if(self.alivetime)
620                 {
621                         PlayerStats_Event(self, PLAYERSTATS_ALIVETIME, time - self.alivetime);
622                         self.alivetime = 0;
623                 }
624
625                 if(valid_damage_for_weaponstats)
626                         WeaponStats_LogKill(awep, abot, self.weapon, vbot);
627
628                 if(sv_gentle < 1) // TODO make a "gentle" version?
629                 if(sound_allowed(MSG_BROADCAST, attacker))
630                 {
631                         if(deathtype == DEATH_DROWN)
632                                 PlayerSound(playersound_drown, CH_PAIN, VOICETYPE_PLAYERSOUND);
633                         else
634                                 PlayerSound(playersound_death, CH_PAIN, VOICETYPE_PLAYERSOUND);
635                 }
636
637                 // get rid of kill indicator
638                 if(self.killindicator)
639                 {
640                         remove(self.killindicator);
641                         self.killindicator = world;
642                         if(self.killindicator_teamchange)
643                                 defer_ClientKill_Now_TeamChange = TRUE;
644
645                         if(self.classname == "body")
646                         if(deathtype == DEATH_KILL)
647                         {
648                                 // for the lemmings fans, a small harmless explosion
649                                 pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1);
650                         }
651                 }
652
653                 if(!g_freezetag)
654                 {
655                         // become fully visible
656                         self.alpha = 1;
657                         // throw a weapon
658                         SpawnThrownWeapon (self.origin + (self.mins + self.maxs) * 0.5, self.switchweapon);
659                 }
660
661                 // print an obituary message
662                 Obituary (attacker, inflictor, self, deathtype);
663                 race_PreDie();
664                 DropAllRunes(self);
665
666         // increment frag counter for used weapon type
667         float w;
668         w = DEATH_WEAPONOF(deathtype);
669         if(WEP_VALID(w))
670         if(self.classname == "player")
671         if(self != attacker)
672         attacker.accuracy.(accuracy_frags[w-1]) += 1;
673
674                 if(deathtype == DEATH_HURTTRIGGER && g_freezetag)
675                 {
676                         PutClientInServer();
677                         count_alive_players(); // re-count players
678                         freezetag_CheckWinner();
679                         return;
680                 }
681
682                 frag_attacker = attacker;
683                 frag_inflictor = inflictor;
684                 frag_target = self;
685                 MUTATOR_CALLHOOK(PlayerDies);
686                 weapon_action(self.weapon, WR_PLAYERDEATH);
687
688                 RemoveGrapplingHook(self);
689
690                 if(self.flagcarried)
691                 {
692                         if(attacker.classname != "player")
693                                 DropFlag(self.flagcarried, self, attacker); // penalty for flag loss by suicide
694                         else if(attacker.team == self.team)
695                                 DropFlag(self.flagcarried, attacker, attacker); // penalty for flag loss by suicide/teamkill
696                         else
697                                 DropFlag(self.flagcarried, world, attacker);
698                 }
699                 if(self.ballcarried && g_nexball)
700                         DropBall(self.ballcarried, self.origin, self.velocity);
701                 Portal_ClearAllLater(self);
702
703                 if(clienttype(self) == CLIENTTYPE_REAL)
704                 {
705                         stuffcmd(self, "-zoom\n");
706                         self.fixangle = TRUE;
707                         //msg_entity = self;
708                         //WriteByte (MSG_ONE, SVC_SETANGLE);
709                         //WriteAngle (MSG_ONE, self.v_angle_x);
710                         //WriteAngle (MSG_ONE, self.v_angle_y);
711                         //WriteAngle (MSG_ONE, 80);
712                 }
713
714                 if(defer_ClientKill_Now_TeamChange) // TODO does this work with FreezeTag?
715                         ClientKill_Now_TeamChange();
716
717                 if(g_arena)
718                         Spawnqueue_Unmark(self);
719
720                 if(g_freezetag)
721                         return;
722
723                 // when we get here, player actually dies
724                 // clear waypoints (do this AFTER FreezeTag)
725                 WaypointSprite_PlayerDead();
726
727                 // make the corpse upright (not tilted)
728                 self.angles_x = 0;
729                 self.angles_z = 0;
730                 // don't spin
731                 self.avelocity = '0 0 0';
732                 // view from the floor
733                 self.view_ofs = '0 0 -8';
734                 // toss the corpse
735                 self.movetype = MOVETYPE_TOSS;
736                 // shootable corpse
737                 self.solid = SOLID_CORPSE;
738                 self.ballistics_density = autocvar_g_ballistics_density_corpse;
739                 // don't stick to the floor
740                 self.flags &~= FL_ONGROUND;
741                 // dying animation
742                 self.deadflag = DEAD_DYING;
743                 // when to allow respawn
744                 sdelay = 0;
745                 waves = 0;
746                 sdelay = cvar(strcat("g_", GetGametype(), "_respawn_delay"));
747                 if(!sdelay)
748                 {
749                         if(g_cts)
750                                 sdelay = 0; // no respawn delay in CTS
751                         else
752                                 sdelay = autocvar_g_respawn_delay;
753                 }
754                 waves = cvar(strcat("g_", GetGametype(), "_respawn_waves"));
755                 if(!waves)
756                         waves = autocvar_g_respawn_waves;
757                 if(waves)
758                         self.death_time = ceil((time + sdelay) / waves) * waves;
759                 else
760                         self.death_time = time + sdelay;
761                 if((sdelay + waves >= 5.0) && (self.death_time - time > 1.75))
762                         self.respawn_countdown = 10; // first number to count down from is 10
763                 else
764                         self.respawn_countdown = -1; // do not count down
765                 if (random() < 0.5)
766                 {
767                         setanim(self, self.anim_die1, FALSE, TRUE, TRUE);
768                         self.dead_frame = self.anim_dead1_x;
769                 }
770                 else
771                 {
772                         setanim(self, self.anim_die2, FALSE, TRUE, TRUE);
773                         self.dead_frame = self.anim_dead2_x;
774                 }
775                 // set damage function to corpse damage
776                 self.event_damage = PlayerCorpseDamage;
777                 // call the corpse damage function just in case it wants to gib
778                 self.event_damage(inflictor, attacker, excess, deathtype, hitloc, force);
779                 // set up to fade out later
780                 SUB_SetFade (self, time + 6 + random (), 1);
781
782                 if(sv_gentle > 0 || autocvar_ekg) {
783                         // remove corpse
784                         PlayerCorpseDamage (inflictor, attacker, autocvar_sv_gibhealth+1.0, deathtype, hitloc, force);
785                 }
786
787                 // reset fields the weapons may use just in case
788                 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
789                 {
790                         weapon_action(j, WR_RESETPLAYER);
791                         ATTACK_FINISHED_FOR(self, j) = 0;
792                 }
793         }
794 }
795
796 .float muted; // to be used by prvm_edictset server playernumber muted 1
797 float Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol)
798 // message "": do not say, just test flood control
799 // return value:
800 //   1 = accept
801 //   0 = reject
802 //  -1 = fake accept
803 {
804         string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr, privatemsgprefix;
805         float flood, privatemsgprefixlen;
806         var .float flood_field;
807         entity head;
808         float ret;
809
810         if(Ban_MaybeEnforceBan(source))
811                 return 0;
812
813         if(!teamsay && !privatesay)
814                 if(substring(msgin, 0, 1) == " ")
815                         msgin = substring(msgin, 1, strlen(msgin) - 1); // work around DP say bug (say_team does not have this!)
816
817         msgin = formatmessage(msgin);
818
819         if(source.classname != "player")
820                 colorstr = "^0"; // black for spectators
821         else if(teamplay)
822                 colorstr = Team_ColorCode(source.team);
823         else
824                 teamsay = FALSE;
825
826         if(intermission_running)
827                 teamsay = FALSE;
828
829         if(msgin != "")
830                 msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
831
832         /*
833          * using bprint solves this... me stupid
834         // how can we prevent the message from appearing in a listen server?
835         // for now, just give "say" back and only handle say_team
836         if(!teamsay)
837         {
838                 clientcommand(self, strcat("say ", msgin));
839                 return;
840         }
841         */
842
843         if(autocvar_g_chat_teamcolors)
844                 namestr = playername(source);
845         else
846                 namestr = source.netname;
847
848         if(msgin != "")
849         {
850                 if(privatesay)
851                 {
852                         msgstr = strcat("\{1}\{13}* ^3", namestr, "^3 tells you: ^7");
853                         privatemsgprefixlen = strlen(msgstr);
854                         msgstr = strcat(msgstr, msgin);
855                         cmsgstr = strcat(colorstr, "^3", namestr, "^3 tells you:\n^7", msgin);
856                         if(autocvar_g_chat_teamcolors)
857                                 privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay), ": ^7");
858                         else
859                                 privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", privatesay.netname, ": ^7");
860                 }
861                 else if(teamsay)
862                 {
863                         msgstr = strcat("\{1}\{13}", colorstr, "(^3", namestr, colorstr, ") ^7", msgin);
864                         cmsgstr = strcat(colorstr, "(^3", namestr, colorstr, ")\n^7", msgin);
865                 }
866                 else
867                 {
868                         msgstr = strcat("\{1}", namestr, "^7: ", msgin);
869                         cmsgstr = "";
870                 }
871                 msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
872         }
873         else
874         {
875                 msgstr = cmsgstr = "";
876         }
877
878         fullmsgstr = msgstr;
879         fullcmsgstr = cmsgstr;
880
881         // FLOOD CONTROL
882         flood = 0;
883         if(floodcontrol)
884         {
885                 float flood_spl;
886                 float flood_burst;
887                 float flood_lmax;
888                 float lines;
889                 if(privatesay)
890                 {
891                         flood_spl = autocvar_g_chat_flood_spl_tell;
892                         flood_burst = autocvar_g_chat_flood_burst_tell;
893                         flood_lmax = autocvar_g_chat_flood_lmax_tell;
894                         flood_field = floodcontrol_chattell;
895                 }
896                 else if(teamsay)
897                 {
898                         flood_spl = autocvar_g_chat_flood_spl_team;
899                         flood_burst = autocvar_g_chat_flood_burst_team;
900                         flood_lmax = autocvar_g_chat_flood_lmax_team;
901                         flood_field = floodcontrol_chatteam;
902                 }
903                 else
904                 {
905                         flood_spl = autocvar_g_chat_flood_spl;
906                         flood_burst = autocvar_g_chat_flood_burst;
907                         flood_lmax = autocvar_g_chat_flood_lmax;
908                         flood_field = floodcontrol_chat;
909                 }
910                 flood_burst = max(0, flood_burst - 1);
911                 // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
912
913                 // do flood control for the default line size
914                 if(msgstr != "")
915                 {
916                         getWrappedLine_remaining = msgstr;
917                         msgstr = "";
918                         lines = 0;
919                         while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
920                         {
921                                 msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
922                                 ++lines;
923                         }
924                         msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
925
926                         if(getWrappedLine_remaining != "")
927                         {
928                                 msgstr = strcat(msgstr, "\n");
929                                 flood = 2;
930                         }
931
932                         if(time >= source.flood_field)
933                         {
934                                 source.flood_field = max(time - flood_burst * flood_spl, source.flood_field) + lines * flood_spl;
935                         }
936                         else
937                         {
938                                 flood = 1;
939                                 msgstr = fullmsgstr;
940                         }
941                 }
942                 else
943                 {
944                         if(time >= source.flood_field)
945                                 source.flood_field = max(time - flood_burst * flood_spl, source.flood_field) + flood_spl;
946                         else
947                                 flood = 1;
948                 }
949
950                 if (timeoutStatus == 2) //when game is paused, no flood protection
951                         source.flood_field = flood = 0;
952         }
953
954         if(flood == 2) // cannot happen for empty msgstr
955         {
956                 if(autocvar_g_chat_flood_notify_flooder)
957                 {
958                         sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
959                         sourcecmsgstr = "";
960                 }
961                 else
962                 {
963                         sourcemsgstr = fullmsgstr;
964                         sourcecmsgstr = fullcmsgstr;
965                 }
966                 cmsgstr = "";
967         }
968         else
969         {
970                 sourcemsgstr = msgstr;
971                 sourcecmsgstr = cmsgstr;
972         }
973
974         if(!privatesay)
975         if(source.classname != "player")
976         {
977                 if(teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !inWarmupStage))
978                         teamsay = -1; // spectators
979         }
980
981         if(flood)
982                 print("NOTE: ", playername(source), "^7 is flooding.\n");
983
984         // build sourcemsgstr by cutting off a prefix and replacing it by the other one
985         if(privatesay)
986                 sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
987
988         if(source.muted)
989         {
990                 // always fake the message
991                 ret = -1;
992         }
993         else if(flood == 1)
994         {
995                 if(autocvar_g_chat_flood_notify_flooder)
996                 {
997                         sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.flood_field - time), "^3 seconds\n"));
998                         ret = 0;
999                 }
1000                 else
1001                         ret = -1;
1002         }
1003         else
1004         {
1005                 ret = 1;
1006         }
1007
1008         if(sourcemsgstr != "" && ret != 0)
1009         {
1010                 if(ret < 0) // fake
1011                 {
1012                         sprint(source, sourcemsgstr);
1013                         if(sourcecmsgstr != "" && !privatesay)
1014                                 centerprint(source, sourcecmsgstr);
1015                 }
1016                 else if(privatesay)
1017                 {
1018                         sprint(source, sourcemsgstr);
1019                         sprint(privatesay, msgstr);
1020                         if(cmsgstr != "")
1021                                 centerprint(privatesay, cmsgstr);
1022                 }
1023                 else if(teamsay > 0)
1024                 {
1025                         sprint(source, sourcemsgstr);
1026                         if(sourcecmsgstr != "")
1027                                 centerprint(source, sourcecmsgstr);
1028                         FOR_EACH_REALPLAYER(head) if(head.team == source.team)
1029                                 if(head != source)
1030                                 {
1031                                         sprint(head, msgstr);
1032                                         if(cmsgstr != "")
1033                                                 centerprint(head, cmsgstr);
1034                                 }
1035                 }
1036                 else if(teamsay < 0)
1037                 {
1038                         sprint(source, sourcemsgstr);
1039                         FOR_EACH_REALCLIENT(head) if(head.classname != "player")
1040                                 if(head != source)
1041                                         sprint(head, msgstr);
1042                 }
1043                 else if(sourcemsgstr != msgstr)
1044                 {
1045                         sprint(source, sourcemsgstr);
1046                         FOR_EACH_REALCLIENT(head)
1047                                 if(head != source)
1048                                         sprint(head, msgstr);
1049                 }
1050                 else
1051                         bprint(msgstr);
1052         }
1053
1054         return ret;
1055 }
1056
1057 float GetVoiceMessageVoiceType(string type)
1058 {
1059         if(type == "taunt")
1060                 return VOICETYPE_TAUNT;
1061         if(type == "teamshoot")
1062                 return VOICETYPE_LASTATTACKER;
1063         return VOICETYPE_TEAMRADIO;
1064 }
1065
1066 string allvoicesamples;
1067 .string GetVoiceMessageSampleField(string type)
1068 {
1069         GetPlayerSoundSampleField_notFound = 0;
1070         switch(type)
1071         {
1072 #define _VOICEMSG(m) case #m: return playersound_##m;
1073                 ALLVOICEMSGS
1074 #undef _VOICEMSG
1075         }
1076         GetPlayerSoundSampleField_notFound = 1;
1077         return playersound_taunt;
1078 }
1079
1080 .string GetPlayerSoundSampleField(string type)
1081 {
1082         GetPlayerSoundSampleField_notFound = 0;
1083         switch(type)
1084         {
1085 #define _VOICEMSG(m) case #m: return playersound_##m;
1086                 ALLPLAYERSOUNDS
1087 #undef _VOICEMSG
1088         }
1089         GetPlayerSoundSampleField_notFound = 1;
1090         return playersound_taunt;
1091 }
1092
1093 void PrecacheGlobalSound(string samplestring)
1094 {
1095         float n, i;
1096         tokenize_console(samplestring);
1097         n = stof(argv(1));
1098         if(n > 0)
1099         {
1100                 for(i = 1; i <= n; ++i)
1101                         precache_sound(strcat(argv(0), ftos(i), ".wav"));
1102         }
1103         else
1104         {
1105                 precache_sound(strcat(argv(0), ".wav"));
1106         }
1107 }
1108
1109 void PrecachePlayerSounds(string f)
1110 {
1111         float fh;
1112         string s;
1113         fh = fopen(f, FILE_READ);
1114         if(fh < 0)
1115                 return;
1116         while((s = fgets(fh)))
1117         {
1118                 if(tokenize_console(s) != 3)
1119                 {
1120                         dprint("Invalid sound info line: ", s, "\n");
1121                         continue;
1122                 }
1123                 PrecacheGlobalSound(strcat(argv(1), " ", argv(2)));
1124         }
1125         fclose(fh);
1126
1127         if not(allvoicesamples)
1128         {
1129 #define _VOICEMSG(m) allvoicesamples = strcat(allvoicesamples, " ", #m);
1130                 ALLVOICEMSGS
1131 #undef _VOICEMSG
1132                 allvoicesamples = strzone(substring(allvoicesamples, 1, strlen(allvoicesamples) - 1));
1133         }
1134 }
1135
1136 void ClearPlayerSounds()
1137 {
1138 #define _VOICEMSG(m) if(self.playersound_##m) { strunzone(self.playersound_##m); self.playersound_##m = string_null; }
1139         ALLPLAYERSOUNDS
1140         ALLVOICEMSGS
1141 #undef _VOICEMSG
1142 }
1143
1144 float LoadPlayerSounds(string f, float first)
1145 {
1146         float fh;
1147         string s;
1148         var .string field;
1149         fh = fopen(f, FILE_READ);
1150         if(fh < 0)
1151         {
1152                 dprint("Player sound file not found: ", f, "\n");
1153                 return 0;
1154         }
1155         while((s = fgets(fh)))
1156         {
1157                 if(tokenize_console(s) != 3)
1158                         continue;
1159                 field = GetPlayerSoundSampleField(argv(0));
1160                 if(GetPlayerSoundSampleField_notFound)
1161                         field = GetVoiceMessageSampleField(argv(0));
1162                 if(GetPlayerSoundSampleField_notFound)
1163                         continue;
1164                 if(self.field)
1165                         strunzone(self.field);
1166                 self.field = strzone(strcat(argv(1), " ", argv(2)));
1167         }
1168         fclose(fh);
1169         return 1;
1170 }
1171
1172 .float modelindex_for_playersound;
1173 .float skinindex_for_playersound;
1174 void UpdatePlayerSounds()
1175 {
1176         if(self.modelindex == self.modelindex_for_playersound)
1177         if(self.skinindex == self.skinindex_for_playersound)
1178                 return;
1179         self.modelindex_for_playersound = self.modelindex;
1180         self.skinindex_for_playersound = self.skinindex;
1181         ClearPlayerSounds();
1182         LoadPlayerSounds("sound/player/default.sounds", 1);
1183         if(!autocvar_g_debug_defaultsounds)
1184                 if(!LoadPlayerSounds(get_model_datafilename(self.model, self.skinindex, "sounds"), 0))
1185                         LoadPlayerSounds(get_model_datafilename(self.model, 0, "sounds"), 0);
1186 }
1187
1188 void FakeGlobalSound(string sample, float chan, float voicetype)
1189 {
1190         float n;
1191         float tauntrand;
1192
1193         if(sample == "")
1194                 return;
1195
1196         tokenize_console(sample);
1197         n = stof(argv(1));
1198         if(n > 0)
1199                 sample = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
1200         else
1201                 sample = strcat(argv(0), ".wav"); // randomization
1202
1203         switch(voicetype)
1204         {
1205                 case VOICETYPE_LASTATTACKER_ONLY:
1206                         break;
1207                 case VOICETYPE_LASTATTACKER:
1208                         if(self.pusher)
1209                         {
1210                                 msg_entity = self;
1211                                 if(clienttype(msg_entity) == CLIENTTYPE_REAL)
1212                                         soundto(MSG_ONE, self, chan, sample, VOL_BASE, ATTN_NONE);
1213                         }
1214                         break;
1215                 case VOICETYPE_TEAMRADIO:
1216                         msg_entity = self;
1217                         if(msg_entity.cvar_cl_voice_directional == 1)
1218                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_MIN);
1219                         else
1220                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_NONE);
1221                         break;
1222                 case VOICETYPE_AUTOTAUNT:
1223                         if(!sv_autotaunt)
1224                                 break;
1225                         if(!sv_taunt)
1226                                 break;
1227                         if(sv_gentle)
1228                                 break;
1229                         tauntrand = random();
1230                         msg_entity = self;
1231                         if (tauntrand < msg_entity.cvar_cl_autotaunt)
1232                         {
1233                                 if (msg_entity.cvar_cl_voice_directional >= 1)
1234                                         soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, bound(ATTN_MIN, msg_entity.cvar_cl_voice_directional_taunt_attenuation, ATTN_MAX));
1235                                 else
1236                                         soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_NONE);
1237                         }
1238                         break;
1239                 case VOICETYPE_TAUNT:
1240                         if(self.classname == "player")
1241                                 if(self.deadflag == DEAD_NO)
1242                                         setanim(self, self.anim_taunt, FALSE, TRUE, TRUE);
1243                         if(!sv_taunt)
1244                                 break;
1245                         if(sv_gentle)
1246                                 break;
1247                         msg_entity = self;
1248                         if (msg_entity.cvar_cl_voice_directional >= 1)
1249                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, bound(ATTN_MIN, msg_entity.cvar_cl_voice_directional_taunt_attenuation, ATTN_MAX));
1250                         else
1251                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_NONE);
1252                         break;
1253                 case VOICETYPE_PLAYERSOUND:
1254                         msg_entity = self;
1255                         soundto(MSG_ONE, self, chan, sample, VOL_BASE, ATTN_NORM);
1256                         break;
1257                 default:
1258                         backtrace("Invalid voice type!");
1259                         break;
1260         }
1261 }
1262
1263 void GlobalSound(string sample, float chan, float voicetype)
1264 {
1265         float n;
1266         float tauntrand;
1267
1268         if(sample == "")
1269                 return;
1270
1271         tokenize_console(sample);
1272         n = stof(argv(1));
1273         if(n > 0)
1274                 sample = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
1275         else
1276                 sample = strcat(argv(0), ".wav"); // randomization
1277
1278         switch(voicetype)
1279         {
1280                 case VOICETYPE_LASTATTACKER_ONLY:
1281                         if(self.pusher)
1282                         {
1283                                 msg_entity = self.pusher;
1284                                 if(clienttype(msg_entity) == CLIENTTYPE_REAL)
1285                                 {
1286                                         if(msg_entity.cvar_cl_voice_directional == 1)
1287                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_MIN);
1288                                         else
1289                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_NONE);
1290                                 }
1291                         }
1292                         break;
1293                 case VOICETYPE_LASTATTACKER:
1294                         if(self.pusher)
1295                         {
1296                                 msg_entity = self.pusher;
1297                                 if(clienttype(msg_entity) == CLIENTTYPE_REAL)
1298                                 {
1299                                         if(msg_entity.cvar_cl_voice_directional == 1)
1300                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_MIN);
1301                                         else
1302                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_NONE);
1303                                 }
1304                                 msg_entity = self;
1305                                 if(clienttype(msg_entity) == CLIENTTYPE_REAL)
1306                                         soundto(MSG_ONE, self, chan, sample, VOL_BASE, ATTN_NONE);
1307                         }
1308                         break;
1309                 case VOICETYPE_TEAMRADIO:
1310                         FOR_EACH_REALCLIENT(msg_entity)
1311                                 if(!teamplay || msg_entity.team == self.team)
1312                                 {
1313                                         if(msg_entity.cvar_cl_voice_directional == 1)
1314                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_MIN);
1315                                         else
1316                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_NONE);
1317                                 }
1318                         break;
1319                 case VOICETYPE_AUTOTAUNT:
1320                         if(!sv_autotaunt)
1321                                 break;
1322                         if(!sv_taunt)
1323                                 break;
1324                         if(sv_gentle)
1325                                 break;
1326                         tauntrand = random();
1327                         FOR_EACH_REALCLIENT(msg_entity)
1328                                 if (tauntrand < msg_entity.cvar_cl_autotaunt)
1329                                 {
1330                                         if (msg_entity.cvar_cl_voice_directional >= 1)
1331                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, bound(ATTN_MIN, msg_entity.cvar_cl_voice_directional_taunt_attenuation, ATTN_MAX));
1332                                         else
1333                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_NONE);
1334                                 }
1335                         break;
1336                 case VOICETYPE_TAUNT:
1337                         if(self.classname == "player")
1338                                 if(self.deadflag == DEAD_NO)
1339                                         setanim(self, self.anim_taunt, FALSE, TRUE, TRUE);
1340                         if(!sv_taunt)
1341                                 break;
1342                         if(sv_gentle)
1343                                 break;
1344                         FOR_EACH_REALCLIENT(msg_entity)
1345                         {
1346                                 if (msg_entity.cvar_cl_voice_directional >= 1)
1347                                         soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, bound(ATTN_MIN, msg_entity.cvar_cl_voice_directional_taunt_attenuation, ATTN_MAX));
1348                                 else
1349                                         soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTN_NONE);
1350                         }
1351                         break;
1352                 case VOICETYPE_PLAYERSOUND:
1353                         sound(self, chan, sample, VOL_BASE, ATTN_NORM);
1354                         break;
1355                 default:
1356                         backtrace("Invalid voice type!");
1357                         break;
1358         }
1359 }
1360
1361 void PlayerSound(.string samplefield, float chan, float voicetype)
1362 {
1363         GlobalSound(self.samplefield, chan, voicetype);
1364 }
1365
1366 void VoiceMessage(string type, string msg)
1367 {
1368         var .string sample;
1369         float voicetype, ownteam;
1370         float flood;
1371         sample = GetVoiceMessageSampleField(type);
1372
1373         if(GetPlayerSoundSampleField_notFound)
1374         {
1375                 sprint(self, strcat("Invalid voice. Use one of: ", allvoicesamples, "\n"));
1376                 return;
1377         }
1378
1379         voicetype = GetVoiceMessageVoiceType(type);
1380         ownteam = (voicetype == VOICETYPE_TEAMRADIO);
1381
1382         flood = Say(self, ownteam, world, msg, 1);
1383
1384         if (flood > 0)
1385                 GlobalSound(self.sample, CH_VOICE, voicetype);
1386         else if (flood < 0)
1387                 FakeGlobalSound(self.sample, CH_VOICE, voicetype);
1388 }
1389
1390 void MoveToTeam(entity client, float team_colour, float type, float show_message)
1391 {
1392 //      show_message
1393 //      0 (00) automove centerprint, admin message
1394 //      1 (01) automove centerprint, no admin message
1395 //      2 (10) no centerprint, admin message
1396 //      3 (11) no centerprint, no admin message
1397
1398         float lockteams_backup;
1399
1400         lockteams_backup = lockteams;  // backup any team lock
1401
1402         lockteams = 0;  // disable locked teams
1403
1404         TeamchangeFrags(client);  // move the players frags
1405         SetPlayerColors(client, team_colour - 1);  // set the players colour
1406         Damage(client, client, client, 100000, ((show_message & 2) ? DEATH_QUIET : DEATH_AUTOTEAMCHANGE), client.origin, '0 0 0');  // kill the player
1407
1408         lockteams = lockteams_backup;  // restore the team lock
1409
1410         LogTeamchange(client.playerid, client.team, type);
1411
1412         if not(show_message & 1) // admin message
1413                 sprint(client, strcat("\{1}\{13}^3", admin_name(), "^7: You have been moved to the ", Team_ColorNameLowerCase(team_colour), " team\n"));  // send a chat message
1414
1415         bprint(strcat(client.netname, " joined the ", ColoredTeamName(client.team), "\n"));
1416 }