]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/cl_player.qc
Viewmodels: CSQC rendering
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / cl_player.qc
1 #include "cl_player.qh"
2
3 #include "bot/bot.qh"
4 #include "cheats.qh"
5 #include "g_damage.qh"
6 #include "g_subs.qh"
7 #include "g_violence.qh"
8 #include "miscfunctions.qh"
9 #include "portals.qh"
10 #include "teamplay.qh"
11 #include "weapons/throwing.qh"
12 #include "command/common.qh"
13 #include "../common/animdecide.qh"
14 #include "../common/csqcmodel_settings.qh"
15 #include "../common/deathtypes/all.qh"
16 #include "../common/triggers/subs.qh"
17 #include "../common/playerstats.qh"
18 #include "../lib/csqcmodel/sv_model.qh"
19
20 #include "../common/minigames/sv_minigames.qh"
21
22 #include "../common/mutators/mutator/waypoints/waypointsprites.qh"
23 #include "../common/triggers/include.qh"
24
25 #include "weapons/weaponstats.qh"
26
27 #include "../common/animdecide.qh"
28
29 void Drop_Special_Items(entity player)
30 {
31         // called when the player has become stuck or frozen
32         // so objective items aren't stuck with the player
33
34         MUTATOR_CALLHOOK(DropSpecialItems, player);
35 }
36
37 void CopyBody_Think(void)
38 {SELFPARAM();
39         if(self.CopyBody_nextthink && time > self.CopyBody_nextthink)
40         {
41                 self.CopyBody_think();
42                 if(wasfreed(self))
43                         return;
44                 self.CopyBody_nextthink = self.nextthink;
45                 self.CopyBody_think = self.think;
46                 self.think = CopyBody_Think;
47         }
48         CSQCMODEL_AUTOUPDATE(self);
49         self.nextthink = time;
50 }
51 void CopyBody(float keepvelocity)
52 {SELFPARAM();
53         if (self.effects & EF_NODRAW)
54                 return;
55         setself(spawn());
56         self.enemy = this;
57         self.lip = this.lip;
58         self.colormap = this.colormap;
59         self.iscreature = this.iscreature;
60         self.teleportable = this.teleportable;
61         self.damagedbycontents = this.damagedbycontents;
62         self.angles = this.angles;
63         self.v_angle = this.v_angle;
64         self.avelocity = this.avelocity;
65         self.classname = "body";
66         self.damageforcescale = this.damageforcescale;
67         self.effects = this.effects;
68         self.glowmod = this.glowmod;
69         self.event_damage = this.event_damage;
70         self.anim_state = this.anim_state;
71         self.anim_time = this.anim_time;
72         self.anim_lower_action = this.anim_lower_action;
73         self.anim_lower_time = this.anim_lower_time;
74         self.anim_upper_action = this.anim_upper_action;
75         self.anim_upper_time = this.anim_upper_time;
76         self.anim_implicit_state = this.anim_implicit_state;
77         self.anim_implicit_time = this.anim_implicit_time;
78         self.anim_lower_implicit_action = this.anim_lower_implicit_action;
79         self.anim_lower_implicit_time = this.anim_lower_implicit_time;
80         self.anim_upper_implicit_action = this.anim_upper_implicit_action;
81         self.anim_upper_implicit_time = this.anim_upper_implicit_time;
82         self.dphitcontentsmask = this.dphitcontentsmask;
83         self.death_time = this.death_time;
84         self.pain_finished = this.pain_finished;
85         self.health = this.health;
86         self.armorvalue = this.armorvalue;
87         self.armortype = this.armortype;
88         self.model = this.model;
89         self.modelindex = this.modelindex;
90         self.skin = this.skin;
91         self.species = this.species;
92         self.movetype = this.movetype;
93         self.solid = this.solid;
94         self.ballistics_density = this.ballistics_density;
95         self.takedamage = this.takedamage;
96         self.customizeentityforclient = this.customizeentityforclient;
97         self.uncustomizeentityforclient = this.uncustomizeentityforclient;
98         self.uncustomizeentityforclient_set = this.uncustomizeentityforclient_set;
99         if (keepvelocity == 1)
100                 self.velocity = this.velocity;
101         self.oldvelocity = self.velocity;
102         self.alpha = this.alpha;
103         self.fade_time = this.fade_time;
104         self.fade_rate = this.fade_rate;
105         //self.weapon = this.weapon;
106         setorigin(self, this.origin);
107         setsize(self, this.mins, this.maxs);
108         self.prevorigin = this.origin;
109         self.reset = SUB_Remove;
110
111         Drag_MoveDrag(this, self);
112
113         if(self.colormap <= maxclients && self.colormap > 0)
114                 self.colormap = 1024 + this.clientcolors;
115
116         CSQCMODEL_AUTOINIT(self);
117         self.CopyBody_nextthink = this.nextthink;
118         self.CopyBody_think = this.think;
119         self.nextthink = time;
120         self.think = CopyBody_Think;
121         // "bake" the current animation frame for clones (they don't get clientside animation)
122         animdecide_load_if_needed(self);
123         animdecide_setframes(self, false, frame, frame1time, frame2, frame2time);
124
125         setself(this);
126 }
127
128 float player_getspecies()
129 {SELFPARAM();
130         float s;
131         get_model_parameters(self.model, self.skin);
132         s = get_model_parameters_species;
133         get_model_parameters(string_null, 0);
134         if(s < 0)
135                 return SPECIES_HUMAN;
136         return s;
137 }
138
139 void player_setupanimsformodel()
140 {SELFPARAM();
141         // load animation info
142         animdecide_load_if_needed(self);
143         animdecide_setstate(self, 0, false);
144 }
145
146 void player_anim (void)
147 {SELFPARAM();
148         int deadbits = (self.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2));
149         if(self.deadflag) {
150                 if (!deadbits) {
151                         // Decide on which death animation to use.
152                         if(random() < 0.5)
153                                 deadbits = ANIMSTATE_DEAD1;
154                         else
155                                 deadbits = ANIMSTATE_DEAD2;
156                 }
157         } else {
158                 // Clear a previous death animation.
159                 deadbits = 0;
160         }
161         int animbits = deadbits;
162         if(self.frozen)
163                 animbits |= ANIMSTATE_FROZEN;
164         if(self.movetype == MOVETYPE_FOLLOW)
165                 animbits |= ANIMSTATE_FOLLOW;
166         if(self.crouch)
167                 animbits |= ANIMSTATE_DUCK;
168         animdecide_setstate(self, animbits, false);
169         animdecide_setimplicitstate(self, (self.flags & FL_ONGROUND));
170 }
171
172 void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
173 {SELFPARAM();
174         float take, save;
175         vector v;
176         Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
177
178         // damage resistance (ignore most of the damage from a bullet or similar)
179         damage = max(damage - 5, 1);
180
181         v = healtharmor_applydamage(self.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
182         take = v.x;
183         save = v.y;
184
185         if(sound_allowed(MSG_BROADCAST, attacker))
186         {
187                 if (save > 10)
188                         sound (self, CH_SHOTS, SND_ARMORIMPACT, VOL_BASE, ATTEN_NORM);
189                 else if (take > 30)
190                         sound (self, CH_SHOTS, SND_BODYIMPACT2, VOL_BASE, ATTEN_NORM);
191                 else if (take > 10)
192                         sound (self, CH_SHOTS, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM);
193         }
194
195         if (take > 50)
196                 Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
197         if (take > 100)
198                 Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
199
200         self.armorvalue = self.armorvalue - save;
201         self.health = self.health - take;
202         // pause regeneration for 5 seconds
203         self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
204
205         self.dmg_save = self.dmg_save + save;//max(save - 10, 0);
206         self.dmg_take = self.dmg_take + take;//max(take - 10, 0);
207         self.dmg_inflictor = inflictor;
208
209         if (self.health <= -autocvar_sv_gibhealth && self.alpha >= 0)
210         {
211                 // don't use any animations as a gib
212                 self.frame = 0;
213                 // view just above the floor
214                 self.view_ofs = '0 0 4';
215
216                 Violence_GibSplash(self, 1, 1, attacker);
217                 self.alpha = -1;
218                 self.solid = SOLID_NOT; // restore later
219                 self.takedamage = DAMAGE_NO; // restore later
220                 self.damagedbycontents = false;
221         }
222 }
223
224 void calculate_player_respawn_time()
225 {SELFPARAM();
226         if(g_ca)
227                 return;
228
229         float gametype_setting_tmp;
230         float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
231         float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
232         float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
233         float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
234         float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
235         float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
236
237         float pcount = 1;  // Include myself whether or not team is already set right and I'm a "player".
238         entity pl;
239         if (teamplay)
240         {
241                 FOR_EACH_PLAYER(pl)
242                         if (pl != self)
243                                 if (pl.team == self.team)
244                                         ++pcount;
245                 if (sdelay_small_count == 0)
246                         sdelay_small_count = 1;
247                 if (sdelay_large_count == 0)
248                         sdelay_large_count = 1;
249         }
250         else
251         {
252                 FOR_EACH_PLAYER(pl)
253                         if (pl != self)
254                                 ++pcount;
255                 if (sdelay_small_count == 0)
256                 {
257                         if (g_cts)
258                         {
259                                 // Players play independently. No point in requiring enemies.
260                                 sdelay_small_count = 1;
261                         }
262                         else
263                         {
264                                 // Players play AGAINST each other. Enemies required.
265                                 sdelay_small_count = 2;
266                         }
267                 }
268                 if (sdelay_large_count == 0)
269                 {
270                         if (g_cts)
271                         {
272                                 // Players play independently. No point in requiring enemies.
273                                 sdelay_large_count = 1;
274                         }
275                         else
276                         {
277                                 // Players play AGAINST each other. Enemies required.
278                                 sdelay_large_count = 2;
279                         }
280                 }
281         }
282
283         float sdelay;
284
285         if (pcount <= sdelay_small_count)
286                 sdelay = sdelay_small;
287         else if (pcount >= sdelay_large_count)
288                 sdelay = sdelay_large;
289         else  // NOTE: this case implies sdelay_large_count > sdelay_small_count.
290                 sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
291
292         if(waves)
293                 self.respawn_time = ceil((time + sdelay) / waves) * waves;
294         else
295                 self.respawn_time = time + sdelay;
296
297         if(sdelay < sdelay_max)
298                 self.respawn_time_max = time + sdelay_max;
299         else
300                 self.respawn_time_max = self.respawn_time;
301
302         if((sdelay + waves >= 5.0) && (self.respawn_time - time > 1.75))
303                 self.respawn_countdown = 10; // first number to count down from is 10
304         else
305                 self.respawn_countdown = -1; // do not count down
306
307         if(autocvar_g_forced_respawn)
308                 self.respawn_flags = self.respawn_flags | RESPAWN_FORCE;
309 }
310
311 void PlayerDamage (entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
312 {SELFPARAM();
313         float take, save, dh, da;
314         int j;
315         vector v;
316         float valid_damage_for_weaponstats;
317         float excess;
318
319         dh = max(self.health, 0);
320         da = max(self.armorvalue, 0);
321
322         if(!DEATH_ISSPECIAL(deathtype))
323         {
324                 damage *= sqrt(bound(1.0, self.cvar_cl_handicap, 100.0));
325                 if(self != attacker)
326                         damage /= sqrt(bound(1.0, attacker.cvar_cl_handicap, 100.0));
327         }
328
329         if(DEATH_ISWEAPON(deathtype, WEP_TUBA))
330         {
331                 // tuba causes blood to come out of the ears
332                 vector ear1, ear2;
333                 vector d;
334                 float f;
335                 ear1 = self.origin;
336                 ear1_z += 0.125 * self.view_ofs.z + 0.875 * self.maxs.z; // 7/8
337                 ear2 = ear1;
338                 makevectors(self.angles);
339                 ear1 += v_right * -10;
340                 ear2 += v_right * +10;
341                 d = inflictor.origin - self.origin;
342                 if (d)
343                         f = (d * v_right) / vlen(d); // this is cos of angle of d and v_right!
344                 else
345                         f = 0;  // Assum ecenter.
346                 force = v_right * vlen(force);
347                 Violence_GibSplash_At(ear1, force * -1, 2, bound(0, damage, 25) / 2 * (0.5 - 0.5 * f), self, attacker);
348                 Violence_GibSplash_At(ear2, force,      2, bound(0, damage, 25) / 2 * (0.5 + 0.5 * f), self, attacker);
349                 if(f > 0)
350                 {
351                         hitloc = ear1;
352                         force = force * -1;
353                 }
354                 else
355                 {
356                         hitloc = ear2;
357                         // force is already good
358                 }
359         }
360         else
361                 Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
362
363
364         v = healtharmor_applydamage(self.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
365         take = v.x;
366         save = v.y;
367
368         if(attacker == self)
369         {
370                 // don't reset pushltime for self damage as it may be an attempt to
371                 // escape a lava pit or similar
372                 //self.pushltime = 0;
373                 self.istypefrag = 0;
374         }
375         else if(IS_PLAYER(attacker))
376         {
377                 self.pusher = attacker;
378                 self.pushltime = time + autocvar_g_maxpushtime;
379                 self.istypefrag = self.BUTTON_CHAT;
380         }
381         else if(time < self.pushltime)
382         {
383                 attacker = self.pusher;
384                 self.pushltime = max(self.pushltime, time + 0.6);
385         }
386         else
387         {
388                 self.pushltime = 0;
389                 self.istypefrag = 0;
390         }
391
392         frag_damage = damage;
393         MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor, inflictor, attacker, self, force, take, save);
394         take = bound(0, damage_take, self.health);
395         save = bound(0, damage_save, self.armorvalue);
396         excess = max(0, damage - take - save);
397
398         if(sound_allowed(MSG_BROADCAST, attacker))
399         {
400                 if (save > 10)
401                         sound (self, CH_SHOTS, SND_ARMORIMPACT, VOL_BASE, ATTEN_NORM);
402                 else if (take > 30)
403                         sound (self, CH_SHOTS, SND_BODYIMPACT2, VOL_BASE, ATTEN_NORM);
404                 else if (take > 10)
405                         sound (self, CH_SHOTS, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM); // FIXME possibly remove them?
406         }
407
408         if (take > 50)
409                 Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
410         if (take > 100)
411                 Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
412
413         if (time >= self.spawnshieldtime)
414         {
415                 if (!(self.flags & FL_GODMODE))
416                 {
417                         self.armorvalue = self.armorvalue - save;
418                         self.health = self.health - take;
419                         // pause regeneration for 5 seconds
420                         if(take)
421                                 self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
422
423                         if (time > self.pain_finished)          //Don't switch pain sequences like crazy
424                         {
425                                 self.pain_finished = time + 0.5;        //Supajoe
426
427                                 if(autocvar_sv_gentle < 1) {
428                                         if(self.classname != "body") // pain anim is BORKED on our ZYMs, FIXME remove this once we have good models
429                                         {
430                                                 if (!self.animstate_override)
431                                                 {
432                                                         if (random() > 0.5)
433                                                                 animdecide_setaction(self, ANIMACTION_PAIN1, true);
434                                                         else
435                                                                 animdecide_setaction(self, ANIMACTION_PAIN2, true);
436                                                 }
437                                         }
438
439                                         if(sound_allowed(MSG_BROADCAST, attacker))
440                                         if((self.health < 2 * WEP_CVAR_PRI(blaster, damage) * autocvar_g_balance_selfdamagepercent + 1) || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || attacker != self) // WEAPONTODO: create separate limit for pain notification with laser
441                                         if(self.health > 1)
442                                         // exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two
443                                         {
444                                                 if(deathtype == DEATH_FALL.m_id)
445                                                         PlayerSound(playersound_fall, CH_PAIN, VOICETYPE_PLAYERSOUND);
446                                                 else if(self.health > 75) // TODO make a "gentle" version?
447                                                         PlayerSound(playersound_pain100, CH_PAIN, VOICETYPE_PLAYERSOUND);
448                                                 else if(self.health > 50)
449                                                         PlayerSound(playersound_pain75, CH_PAIN, VOICETYPE_PLAYERSOUND);
450                                                 else if(self.health > 25)
451                                                         PlayerSound(playersound_pain50, CH_PAIN, VOICETYPE_PLAYERSOUND);
452                                                 else
453                                                         PlayerSound(playersound_pain25, CH_PAIN, VOICETYPE_PLAYERSOUND);
454                                         }
455                                 }
456                         }
457
458                         // throw off bot aim temporarily
459                         float shake;
460                         if(IS_BOT_CLIENT(self) && self.health >= 1)
461                         {
462                                 shake = damage * 5 / (bound(0,skill,100) + 1);
463                                 self.v_angle_x = self.v_angle.x + (random() * 2 - 1) * shake;
464                                 self.v_angle_y = self.v_angle.y + (random() * 2 - 1) * shake;
465                                 self.v_angle_x = bound(-90, self.v_angle.x, 90);
466                         }
467                 }
468                 else
469                         self.max_armorvalue += (save + take);
470         }
471         self.dmg_save = self.dmg_save + save;//max(save - 10, 0);
472         self.dmg_take = self.dmg_take + take;//max(take - 10, 0);
473         self.dmg_inflictor = inflictor;
474
475         if (self != attacker) {
476                 float realdmg = damage - excess;
477                 if (IS_PLAYER(attacker)) {
478                         PlayerScore_Add(attacker, SP_DMG, realdmg);
479                 }
480                 if (IS_PLAYER(self)) {
481                         PlayerScore_Add(self, SP_DMGTAKEN, realdmg);
482                 }
483         }
484         
485         bool abot = (IS_BOT_CLIENT(attacker));
486         bool vbot = (IS_BOT_CLIENT(self));
487
488         valid_damage_for_weaponstats = 0;
489         Weapon awep = WEP_Null;
490
491         if(vbot || IS_REAL_CLIENT(self))
492         if(abot || IS_REAL_CLIENT(attacker))
493         if(attacker && self != attacker)
494         if(DIFF_TEAM(self, attacker))
495         {
496                 if(DEATH_ISSPECIAL(deathtype))
497                         awep = get_weaponinfo(attacker.weapon);
498                 else
499                         awep = DEATH_WEAPONOF(deathtype);
500                 valid_damage_for_weaponstats = 1;
501         }
502
503         if(valid_damage_for_weaponstats)
504         {
505                 dh = dh - max(self.health, 0);
506                 da = da - max(self.armorvalue, 0);
507                 WeaponStats_LogDamage(awep.m_id, abot, self.weapon, vbot, dh + da);
508                 MUTATOR_CALLHOOK(PlayerDamaged, attacker, self, dh, da, hitloc, deathtype);
509         }
510
511         if (self.health < 1)
512         {
513                 float defer_ClientKill_Now_TeamChange;
514                 defer_ClientKill_Now_TeamChange = false;
515
516                 if(self.alivetime)
517                 {
518                         PS_GR_P_ADDVAL(self, PLAYERSTATS_ALIVETIME, time - self.alivetime);
519                         self.alivetime = 0;
520                 }
521
522                 if(valid_damage_for_weaponstats)
523                         WeaponStats_LogKill(awep.m_id, abot, self.weapon, vbot);
524
525                 if(autocvar_sv_gentle < 1) // TODO make a "gentle" version?
526                 if(sound_allowed(MSG_BROADCAST, attacker))
527                 {
528                         if(deathtype == DEATH_DROWN.m_id)
529                                 PlayerSound(playersound_drown, CH_PAIN, VOICETYPE_PLAYERSOUND);
530                         else
531                                 PlayerSound(playersound_death, CH_PAIN, VOICETYPE_PLAYERSOUND);
532                 }
533
534                 // get rid of kill indicator
535                 if(self.killindicator)
536                 {
537                         remove(self.killindicator);
538                         self.killindicator = world;
539                         if(self.killindicator_teamchange)
540                                 defer_ClientKill_Now_TeamChange = true;
541
542                         if(self.classname == "body")
543                         if(deathtype == DEATH_KILL.m_id)
544                         {
545                                 // for the lemmings fans, a small harmless explosion
546                                 Send_Effect(EFFECT_ROCKET_EXPLODE, self.origin, '0 0 0', 1);
547                         }
548                 }
549
550                 // print an obituary message
551                 if(self.classname != "body")
552                         Obituary (attacker, inflictor, self, deathtype);
553
554         // increment frag counter for used weapon type
555         Weapon w = DEATH_WEAPONOF(deathtype);
556         if(w != WEP_Null)
557         if(accuracy_isgooddamage(attacker, self))
558         attacker.accuracy.(accuracy_frags[w.m_id-1]) += 1;
559
560                 MUTATOR_CALLHOOK(PlayerDies, inflictor, attacker, self, deathtype);
561                 excess = frag_damage;
562
563                 Weapon wep = get_weaponinfo(self.weapon);
564                 wep.wr_playerdeath(wep);
565
566                 RemoveGrapplingHook(self);
567
568                 Portal_ClearAllLater(self);
569
570                 self.fixangle = true;
571
572                 if(defer_ClientKill_Now_TeamChange)
573                         ClientKill_Now_TeamChange(); // can turn player into spectator
574
575                 // player could have been miraculously resuscitated ;)
576                 // e.g. players in freezetag get frozen, they don't really die
577                 if(self.health >= 1 || !(IS_PLAYER(self) || self.classname == "body"))
578                         return;
579
580                 // when we get here, player actually dies
581
582                 Unfreeze(self); // remove any icy remains
583                 self.health = 0; // Unfreeze resets health, so we need to set it back
584
585                 // clear waypoints
586                 WaypointSprite_PlayerDead();
587                 // throw a weapon
588                 SpawnThrownWeapon (self.origin + (self.mins + self.maxs) * 0.5, self.switchweapon);
589
590                 // become fully visible
591                 self.alpha = default_player_alpha;
592                 // make the corpse upright (not tilted)
593                 self.angles_x = 0;
594                 self.angles_z = 0;
595                 // don't spin
596                 self.avelocity = '0 0 0';
597                 // view from the floor
598                 self.view_ofs = '0 0 -8';
599                 // toss the corpse
600                 self.movetype = MOVETYPE_TOSS;
601                 // shootable corpse
602                 self.solid = SOLID_CORPSE;
603                 self.ballistics_density = autocvar_g_ballistics_density_corpse;
604                 // don't stick to the floor
605                 self.flags &= ~FL_ONGROUND;
606                 // dying animation
607                 self.deadflag = DEAD_DYING;
608
609                 // when to allow respawn
610                 calculate_player_respawn_time();
611
612                 self.death_time = time;
613                 if (random() < 0.5)
614                         animdecide_setstate(self, self.anim_state | ANIMSTATE_DEAD1, true);
615                 else
616                         animdecide_setstate(self, self.anim_state | ANIMSTATE_DEAD2, true);
617                 if (self.maxs.z > 5)
618                 {
619                         self.maxs_z = 5;
620                         setsize(self, self.mins, self.maxs);
621                 }
622                 // set damage function to corpse damage
623                 self.event_damage = PlayerCorpseDamage;
624                 // call the corpse damage function just in case it wants to gib
625                 self.event_damage(inflictor, attacker, excess, deathtype, hitloc, force);
626
627                 // set up to fade out later
628                 SUB_SetFade (self, time + 6 + random (), 1);
629                 // reset body think wrapper broken by SUB_SetFade
630                 if(self.classname == "body" && self.think != CopyBody_Think) {
631                         self.CopyBody_think = self.think;
632                         self.CopyBody_nextthink = self.nextthink;
633                         self.think = CopyBody_Think;
634                         self.nextthink = time;
635                 }
636
637                 if(autocvar_sv_gentle > 0 || autocvar_ekg || self.classname == "body") {
638                         // remove corpse
639                         // clones don't run any animation code any more, so we must gib them when they die :(
640                         PlayerCorpseDamage (inflictor, attacker, autocvar_sv_gibhealth+1.0, deathtype, hitloc, force);
641                 }
642
643                 // reset fields the weapons may use just in case
644                 for (j = WEP_FIRST; j <= WEP_LAST; ++j)
645                 {
646                         Weapon w = get_weaponinfo(j);
647                         w.wr_resetplayer(w);
648                         for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
649                         {
650                                 ATTACK_FINISHED_FOR(self, j, slot) = 0;
651                         }
652                 }
653         }
654 }
655
656 float Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol)
657 // message "": do not say, just test flood control
658 // return value:
659 //   1 = accept
660 //   0 = reject
661 //  -1 = fake accept
662 {
663         string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr, colorprefix;
664         float flood;
665         var .float flood_field;
666         entity head;
667         float ret;
668         string privatemsgprefix = string_null; float privatemsgprefixlen = 0;
669
670         if(!teamsay && !privatesay)
671                 if(substring(msgin, 0, 1) == " ")
672                         msgin = substring(msgin, 1, strlen(msgin) - 1); // work around DP say bug (say_team does not have this!)
673
674         msgin = formatmessage(msgin);
675
676         if (!IS_PLAYER(source))
677                 colorstr = "^0"; // black for spectators
678         else if(teamplay)
679                 colorstr = Team_ColorCode(source.team);
680         else
681         {
682                 colorstr = "";
683                 teamsay = false;
684         }
685
686         if(intermission_running)
687                 teamsay = false;
688
689         if(msgin != "")
690                 msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
691
692         /*
693          * using bprint solves this... me stupid
694         // how can we prevent the message from appearing in a listen server?
695         // for now, just give "say" back and only handle say_team
696         if(!teamsay)
697         {
698                 clientcommand(self, strcat("say ", msgin));
699                 return;
700         }
701         */
702
703         if(autocvar_g_chat_teamcolors)
704                 namestr = playername(source);
705         else
706                 namestr = source.netname;
707
708         if(strdecolorize(namestr) == namestr)
709                 colorprefix = "^3";
710         else
711                 colorprefix = "^7";
712
713         if(msgin != "")
714         {
715                 if(privatesay)
716                 {
717                         msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
718                         privatemsgprefixlen = strlen(msgstr);
719                         msgstr = strcat(msgstr, msgin);
720                         cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
721                         if(autocvar_g_chat_teamcolors)
722                                 privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay), ": ^7");
723                         else
724                                 privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", privatesay.netname, ": ^7");
725                 }
726                 else if(teamsay)
727                 {
728                         if(strstrofs(msgin, "/me", 0) >= 0)
729                         {
730                                 //msgin = strreplace("/me", "", msgin);
731                                 //msgin = substring(msgin, 3, strlen(msgin));
732                                 msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
733                                 msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
734                         }
735                         else
736                                 msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
737                         cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
738                 }
739                 else
740                 {
741                         if(strstrofs(msgin, "/me", 0) >= 0)
742                         {
743                                 //msgin = strreplace("/me", "", msgin);
744                                 //msgin = substring(msgin, 3, strlen(msgin));
745                                 msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
746                                 msgstr = strcat("\{1}^4* ", "^7", msgin);
747                         }
748                         else
749                                 msgstr = strcat("\{1}", colorprefix, namestr, "^7: ", msgin);
750                         cmsgstr = "";
751                 }
752                 msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
753         }
754         else
755         {
756                 msgstr = cmsgstr = "";
757         }
758
759         fullmsgstr = msgstr;
760         fullcmsgstr = cmsgstr;
761
762         // FLOOD CONTROL
763         flood = 0;
764         flood_field = floodcontrol_chat;
765         if(floodcontrol)
766         {
767                 float flood_spl;
768                 float flood_burst;
769                 float flood_lmax;
770                 float lines;
771                 if(privatesay)
772                 {
773                         flood_spl = autocvar_g_chat_flood_spl_tell;
774                         flood_burst = autocvar_g_chat_flood_burst_tell;
775                         flood_lmax = autocvar_g_chat_flood_lmax_tell;
776                         flood_field = floodcontrol_chattell;
777                 }
778                 else if(teamsay)
779                 {
780                         flood_spl = autocvar_g_chat_flood_spl_team;
781                         flood_burst = autocvar_g_chat_flood_burst_team;
782                         flood_lmax = autocvar_g_chat_flood_lmax_team;
783                         flood_field = floodcontrol_chatteam;
784                 }
785                 else
786                 {
787                         flood_spl = autocvar_g_chat_flood_spl;
788                         flood_burst = autocvar_g_chat_flood_burst;
789                         flood_lmax = autocvar_g_chat_flood_lmax;
790                         flood_field = floodcontrol_chat;
791                 }
792                 flood_burst = max(0, flood_burst - 1);
793                 // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
794
795                 // do flood control for the default line size
796                 if(msgstr != "")
797                 {
798                         getWrappedLine_remaining = msgstr;
799                         msgstr = "";
800                         lines = 0;
801                         while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
802                         {
803                                 msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
804                                 ++lines;
805                         }
806                         msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
807
808                         if(getWrappedLine_remaining != "")
809                         {
810                                 msgstr = strcat(msgstr, "\n");
811                                 flood = 2;
812                         }
813
814                         if (time >= source.(flood_field))
815                         {
816                                 source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl;
817                         }
818                         else
819                         {
820                                 flood = 1;
821                                 msgstr = fullmsgstr;
822                         }
823                 }
824                 else
825                 {
826                         if (time >= source.(flood_field))
827                                 source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl;
828                         else
829                                 flood = 1;
830                 }
831
832                 if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection
833                         source.(flood_field) = flood = 0;
834         }
835
836         if(flood == 2) // cannot happen for empty msgstr
837         {
838                 if(autocvar_g_chat_flood_notify_flooder)
839                 {
840                         sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
841                         sourcecmsgstr = "";
842                 }
843                 else
844                 {
845                         sourcemsgstr = fullmsgstr;
846                         sourcecmsgstr = fullcmsgstr;
847                 }
848                 cmsgstr = "";
849         }
850         else
851         {
852                 sourcemsgstr = msgstr;
853                 sourcecmsgstr = cmsgstr;
854         }
855
856         if(!privatesay)
857         if (!IS_PLAYER(source))
858         {
859                 if (!intermission_running)
860                         if(teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !(warmup_stage || gameover)))
861                                 teamsay = -1; // spectators
862         }
863
864         if(flood)
865                 LOG_INFO("NOTE: ", playername(source), "^7 is flooding.\n");
866
867         // build sourcemsgstr by cutting off a prefix and replacing it by the other one
868         if(privatesay)
869                 sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
870
871         if(source.muted)
872         {
873                 // always fake the message
874                 ret = -1;
875         }
876         else if(flood == 1)
877         {
878                 if (autocvar_g_chat_flood_notify_flooder)
879                 {
880                         sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
881                         ret = 0;
882                 }
883                 else
884                         ret = -1;
885         }
886         else
887         {
888                 ret = 1;
889         }
890
891         if(sourcemsgstr != "" && ret != 0)
892         {
893                 if(ret < 0) // faked message, because the player is muted
894                 {
895                         sprint(source, sourcemsgstr);
896                         if(sourcecmsgstr != "" && !privatesay)
897                                 centerprint(source, sourcecmsgstr);
898                 }
899                 else if(privatesay) // private message, between 2 people only
900                 {
901                         sprint(source, sourcemsgstr);
902                         sprint(privatesay, msgstr);
903                         if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
904                         if(cmsgstr != "")
905                                 centerprint(privatesay, cmsgstr);
906                 }
907                 else if ( teamsay && source.active_minigame )
908                 {
909                         sprint(source, sourcemsgstr);
910                         dedicated_print(msgstr); // send to server console too
911                         FOR_EACH_REALCLIENT(head)
912                                 if(head != source)
913                                 if(head.active_minigame == source.active_minigame)
914                                         sprint(head, msgstr);
915                 }
916                 else if(teamsay > 0) // team message, only sent to team mates
917                 {
918                         sprint(source, sourcemsgstr);
919                         dedicated_print(msgstr); // send to server console too
920                         if(sourcecmsgstr != "")
921                                 centerprint(source, sourcecmsgstr);
922                         FOR_EACH_REALPLAYER(head) if(head.team == source.team)
923                                 if(head != source)
924                                 {
925                                         sprint(head, msgstr);
926                                         if(cmsgstr != "")
927                                                 centerprint(head, cmsgstr);
928                                 }
929                 }
930                 else if(teamsay < 0) // spectator message, only sent to spectators
931                 {
932                         sprint(source, sourcemsgstr);
933                         dedicated_print(msgstr); // send to server console too
934                         FOR_EACH_REALCLIENT(head) if (!IS_PLAYER(head))
935                                 if(head != source)
936                                         sprint(head, msgstr);
937                 }
938                 else if(sourcemsgstr != msgstr) // trimmed/server fixed message, sent to all players
939                 {
940                         sprint(source, sourcemsgstr);
941                         dedicated_print(msgstr); // send to server console too
942                         FOR_EACH_REALCLIENT(head)
943                                 if(head != source)
944                                         sprint(head, msgstr);
945                 }
946                 else
947                         bprint(msgstr); // entirely normal message, sent to all players -- bprint sends to server console too.
948         }
949
950         return ret;
951 }
952
953 float GetVoiceMessageVoiceType(string type)
954 {
955         if(type == "taunt")
956                 return VOICETYPE_TAUNT;
957         if(type == "teamshoot")
958                 return VOICETYPE_LASTATTACKER;
959         return VOICETYPE_TEAMRADIO;
960 }
961
962 .string GetVoiceMessageSampleField(string type)
963 {
964         GetPlayerSoundSampleField_notFound = 0;
965         switch(type)
966         {
967 #define _VOICEMSG(m) case #m: return playersound_##m;
968                 ALLVOICEMSGS
969 #undef _VOICEMSG
970         }
971         GetPlayerSoundSampleField_notFound = 1;
972         return playersound_taunt;
973 }
974
975 .string GetPlayerSoundSampleField(string type)
976 {
977         GetPlayerSoundSampleField_notFound = 0;
978         switch(type)
979         {
980 #define _VOICEMSG(m) case #m: return playersound_##m;
981                 ALLPLAYERSOUNDS
982 #undef _VOICEMSG
983         }
984         GetPlayerSoundSampleField_notFound = 1;
985         return playersound_taunt;
986 }
987
988 void PrecacheGlobalSound(string samplestring)
989 {
990         float n, i;
991         tokenize_console(samplestring);
992         n = stof(argv(1));
993         if(n > 0)
994         {
995                 for(i = 1; i <= n; ++i)
996                         precache_sound(strcat(argv(0), ftos(i), ".wav"));
997         }
998         else
999         {
1000                 precache_sound(strcat(argv(0), ".wav"));
1001         }
1002 }
1003
1004 void PrecachePlayerSounds(string f)
1005 {
1006         int fh = fopen(f, FILE_READ);
1007         if (fh < 0)
1008                 return;
1009         for (string s; (s = fgets(fh)); )
1010         {
1011                 int n = tokenize_console(s);
1012                 if (n != 3)
1013                 {
1014                         if (n != 0) LOG_TRACEF("Invalid sound info line: %s\n", s);
1015                         continue;
1016                 }
1017                 PrecacheGlobalSound(strcat(argv(1), " ", argv(2)));
1018         }
1019         fclose(fh);
1020
1021         if (!allvoicesamples)
1022         {
1023 #define _VOICEMSG(m) allvoicesamples = strcat(allvoicesamples, " ", #m);
1024                 ALLVOICEMSGS
1025 #undef _VOICEMSG
1026                 allvoicesamples = strzone(substring(allvoicesamples, 1, strlen(allvoicesamples) - 1));
1027         }
1028 }
1029
1030 void ClearPlayerSounds()
1031 {SELFPARAM();
1032 #define _VOICEMSG(m) if(self.playersound_##m) { strunzone(self.playersound_##m); self.playersound_##m = string_null; }
1033         ALLPLAYERSOUNDS
1034         ALLVOICEMSGS
1035 #undef _VOICEMSG
1036 }
1037
1038 float LoadPlayerSounds(string f, float first)
1039 {SELFPARAM();
1040         float fh;
1041         string s;
1042         var .string field;
1043         fh = fopen(f, FILE_READ);
1044         if(fh < 0)
1045         {
1046                 LOG_TRACE("Player sound file not found: ", f, "\n");
1047                 return 0;
1048         }
1049         while((s = fgets(fh)))
1050         {
1051                 if(tokenize_console(s) != 3)
1052                         continue;
1053                 field = GetPlayerSoundSampleField(argv(0));
1054                 if(GetPlayerSoundSampleField_notFound)
1055                         field = GetVoiceMessageSampleField(argv(0));
1056                 if(GetPlayerSoundSampleField_notFound)
1057                         continue;
1058                 if (self.(field))
1059                         strunzone(self.(field));
1060                 self.(field) = strzone(strcat(argv(1), " ", argv(2)));
1061         }
1062         fclose(fh);
1063         return 1;
1064 }
1065
1066 void UpdatePlayerSounds()
1067 {SELFPARAM();
1068         if(self.modelindex == self.modelindex_for_playersound)
1069         if(self.skin == self.skin_for_playersound)
1070                 return;
1071         self.modelindex_for_playersound = self.modelindex;
1072         self.skin_for_playersound = self.skin;
1073         ClearPlayerSounds();
1074         LoadPlayerSounds("sound/player/default.sounds", 1);
1075         if(!autocvar_g_debug_defaultsounds)
1076                 if(!LoadPlayerSounds(get_model_datafilename(self.model, self.skin, "sounds"), 0))
1077                         LoadPlayerSounds(get_model_datafilename(self.model, 0, "sounds"), 0);
1078 }
1079
1080 void FakeGlobalSound(string sample, float chan, float voicetype)
1081 {SELFPARAM();
1082         float n;
1083         float tauntrand;
1084
1085         if(sample == "")
1086                 return;
1087
1088         tokenize_console(sample);
1089         n = stof(argv(1));
1090         if(n > 0)
1091                 sample = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
1092         else
1093                 sample = strcat(argv(0), ".wav"); // randomization
1094
1095         switch(voicetype)
1096         {
1097                 case VOICETYPE_LASTATTACKER_ONLY:
1098                         break;
1099                 case VOICETYPE_LASTATTACKER:
1100                         if(self.pusher)
1101                         {
1102                                 msg_entity = self;
1103                                 if(IS_REAL_CLIENT(msg_entity))
1104                                         soundto(MSG_ONE, self, chan, sample, VOL_BASE, ATTEN_NONE);
1105                         }
1106                         break;
1107                 case VOICETYPE_TEAMRADIO:
1108                         msg_entity = self;
1109                         if(msg_entity.cvar_cl_voice_directional == 1)
1110                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_MIN);
1111                         else
1112                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_NONE);
1113                         break;
1114                 case VOICETYPE_AUTOTAUNT:
1115                         if(!sv_autotaunt)
1116                                 break;
1117                         if(!sv_taunt)
1118                                 break;
1119                         if(autocvar_sv_gentle)
1120                                 break;
1121                         tauntrand = random();
1122                         msg_entity = self;
1123                         if (tauntrand < msg_entity.cvar_cl_autotaunt)
1124                         {
1125                                 if (msg_entity.cvar_cl_voice_directional >= 1)
1126                                         soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, bound(ATTEN_MIN, msg_entity.cvar_cl_voice_directional_taunt_attenuation, ATTEN_MAX));
1127                                 else
1128                                         soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_NONE);
1129                         }
1130                         break;
1131                 case VOICETYPE_TAUNT:
1132                         if(IS_PLAYER(self))
1133                                 if(self.deadflag == DEAD_NO)
1134                                         animdecide_setaction(self, ANIMACTION_TAUNT, true);
1135                         if(!sv_taunt)
1136                                 break;
1137                         if(autocvar_sv_gentle)
1138                                 break;
1139                         msg_entity = self;
1140                         if (msg_entity.cvar_cl_voice_directional >= 1)
1141                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, bound(ATTEN_MIN, msg_entity.cvar_cl_voice_directional_taunt_attenuation, ATTEN_MAX));
1142                         else
1143                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_NONE);
1144                         break;
1145                 case VOICETYPE_PLAYERSOUND:
1146                         msg_entity = self;
1147                         soundto(MSG_ONE, self, chan, sample, VOL_BASE, ATTEN_NORM);
1148                         break;
1149                 default:
1150                         backtrace("Invalid voice type!");
1151                         break;
1152         }
1153 }
1154
1155 void GlobalSound(string sample, float chan, float voicetype)
1156 {SELFPARAM();
1157         float n;
1158         float tauntrand;
1159
1160         if(sample == "")
1161                 return;
1162
1163         tokenize_console(sample);
1164         n = stof(argv(1));
1165         if(n > 0)
1166                 sample = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
1167         else
1168                 sample = strcat(argv(0), ".wav"); // randomization
1169
1170         switch(voicetype)
1171         {
1172                 case VOICETYPE_LASTATTACKER_ONLY:
1173                         if(self.pusher)
1174                         {
1175                                 msg_entity = self.pusher;
1176                                 if(IS_REAL_CLIENT(msg_entity))
1177                                 {
1178                                         if(msg_entity.cvar_cl_voice_directional == 1)
1179                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_MIN);
1180                                         else
1181                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_NONE);
1182                                 }
1183                         }
1184                         break;
1185                 case VOICETYPE_LASTATTACKER:
1186                         if(self.pusher)
1187                         {
1188                                 msg_entity = self.pusher;
1189                                 if(IS_REAL_CLIENT(msg_entity))
1190                                 {
1191                                         if(msg_entity.cvar_cl_voice_directional == 1)
1192                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_MIN);
1193                                         else
1194                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_NONE);
1195                                 }
1196                                 msg_entity = self;
1197                                 if(IS_REAL_CLIENT(msg_entity))
1198                                         soundto(MSG_ONE, self, chan, sample, VOL_BASE, ATTEN_NONE);
1199                         }
1200                         break;
1201                 case VOICETYPE_TEAMRADIO:
1202                         FOR_EACH_REALCLIENT(msg_entity)
1203                                 if(!teamplay || msg_entity.team == self.team)
1204                                 {
1205                                         if(msg_entity.cvar_cl_voice_directional == 1)
1206                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_MIN);
1207                                         else
1208                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_NONE);
1209                                 }
1210                         break;
1211                 case VOICETYPE_AUTOTAUNT:
1212                         if(!sv_autotaunt)
1213                                 break;
1214                         if(!sv_taunt)
1215                                 break;
1216                         if(autocvar_sv_gentle)
1217                                 break;
1218                         tauntrand = random();
1219                         FOR_EACH_REALCLIENT(msg_entity)
1220                                 if (tauntrand < msg_entity.cvar_cl_autotaunt)
1221                                 {
1222                                         if (msg_entity.cvar_cl_voice_directional >= 1)
1223                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, bound(ATTEN_MIN, msg_entity.cvar_cl_voice_directional_taunt_attenuation, ATTEN_MAX));
1224                                         else
1225                                                 soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_NONE);
1226                                 }
1227                         break;
1228                 case VOICETYPE_TAUNT:
1229                         if(IS_PLAYER(self))
1230                                 if(self.deadflag == DEAD_NO)
1231                                         animdecide_setaction(self, ANIMACTION_TAUNT, true);
1232                         if(!sv_taunt)
1233                                 break;
1234                         if(autocvar_sv_gentle)
1235                                 break;
1236                         FOR_EACH_REALCLIENT(msg_entity)
1237                         {
1238                                 if (msg_entity.cvar_cl_voice_directional >= 1)
1239                                         soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, bound(ATTEN_MIN, msg_entity.cvar_cl_voice_directional_taunt_attenuation, ATTEN_MAX));
1240                                 else
1241                                         soundto(MSG_ONE, self, chan, sample, VOL_BASEVOICE, ATTEN_NONE);
1242                         }
1243                         break;
1244                 case VOICETYPE_PLAYERSOUND:
1245                         _sound(self, chan, sample, VOL_BASE, ATTEN_NORM);
1246                         break;
1247                 default:
1248                         backtrace("Invalid voice type!");
1249                         break;
1250         }
1251 }
1252
1253 void PlayerSound(.string samplefield, float chan, float voicetype)
1254 {SELFPARAM();
1255         GlobalSound(self.(samplefield), chan, voicetype);
1256 }
1257
1258 void VoiceMessage(string type, string msg)
1259 {SELFPARAM();
1260         float voicetype, ownteam;
1261         float flood;
1262         var .string sample = GetVoiceMessageSampleField(type);
1263
1264         if(GetPlayerSoundSampleField_notFound)
1265         {
1266                 sprint(self, strcat("Invalid voice. Use one of: ", allvoicesamples, "\n"));
1267                 return;
1268         }
1269
1270         voicetype = GetVoiceMessageVoiceType(type);
1271         ownteam = (voicetype == VOICETYPE_TEAMRADIO);
1272
1273         flood = Say(self, ownteam, world, msg, 1);
1274
1275         if (IS_SPEC(self) || IS_OBSERVER(self) || flood < 0)
1276                 FakeGlobalSound(self.(sample), CH_VOICE, voicetype);
1277         else if (flood > 0)
1278                 GlobalSound(self.(sample), CH_VOICE, voicetype);
1279 }
1280
1281 void MoveToTeam(entity client, float team_colour, float type)
1282 {
1283         float lockteams_backup;
1284
1285         lockteams_backup = lockteams;  // backup any team lock
1286
1287         lockteams = 0;  // disable locked teams
1288
1289         TeamchangeFrags(client);  // move the players frags
1290         SetPlayerColors(client, team_colour - 1);  // set the players colour
1291         Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, client.origin, '0 0 0');  // kill the player
1292
1293         lockteams = lockteams_backup;  // restore the team lock
1294
1295         LogTeamchange(client.playerid, client.team, type);
1296 }