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