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