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