]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/cl_player.qc
separate refire timers for each weapon; fixes "faster Nex refire by switching Laser...
[divverent/nexuiz.git] / data / qcsrc / server / cl_player.qc
1 $frame die1 die2 draw duck duckwalk duckjump duckidle idle
2 $frame jump pain1 pain2 shoot taunt run runbackwards
3 $frame strafeleft straferight dead1 dead2 forwardright
4 $frame forwardleft backright backleft
5
6 // changes by LordHavoc on 03/29/04 and 03/30/04 at Vermeulen's request
7 // merged player_run and player_stand to player_anim
8 // added death animations to player_anim
9 // can now spawn thrown weapons from anywhere, not just from players
10 // thrown weapons now fade out after 20 seconds
11 // created PlayerGib function
12 // PlayerDie no longer uses hitloc or damage
13 // PlayerDie now supports dying animations as well as gibbing
14 // cleaned up PlayerDie a lot
15 // added CopyBody
16
17 .entity pusher;
18 .float pushltime;
19
20 void CopyBody(float keepvelocity)
21 {
22         local entity oldself;
23         if (self.effects & EF_NODRAW)
24                 return;
25         oldself = self;
26         self = spawn();
27         self.colormap = oldself.colormap;
28         self.iscreature = oldself.iscreature;
29         self.angles = oldself.angles;
30         self.avelocity = oldself.avelocity;
31         self.classname = "body";
32         self.damageforcescale = oldself.damageforcescale;
33         self.effects = oldself.effects;
34         self.event_damage = oldself.event_damage;
35         self.frame = oldself.frame;
36         self.health = oldself.health;
37         self.armorvalue = oldself.armorvalue;
38         self.armortype = oldself.armortype;
39         self.model = oldself.model;
40         self.modelindex = oldself.modelindex;
41         self.movetype = oldself.movetype;
42         self.nextthink = oldself.nextthink;
43         self.skin = oldself.skin;
44         self.solid = oldself.solid;
45         self.takedamage = oldself.takedamage;
46         self.think = oldself.think;
47         self.gibrandom = oldself.gibrandom;
48         self.customizeentityforclient = oldself.customizeentityforclient;
49         if (keepvelocity == 1)
50                 self.velocity = oldself.velocity;
51         self.oldvelocity = self.velocity;
52         self.fade_time = oldself.fade_time;
53         self.fade_rate = oldself.fade_rate;
54         //self.weapon = oldself.weapon;
55         setorigin(self, oldself.origin);
56         setsize(self, oldself.mins, oldself.maxs);
57         self.oldorigin = oldself.origin;
58         self = oldself;
59 }
60
61 void player_anim (void)
62 {
63         if (self.deadflag != DEAD_NO)
64         {
65                 if (time > self.dead_time)
66                 {
67                         if (self.maxs_z > 5)
68                         {
69                                 self.maxs_z = 5;
70                                 setsize(self, self.mins, self.maxs);
71                         }
72                         self.frame = self.dead_frame;
73                 }
74                 else
75                         self.frame = self.die_frame;
76                 return;
77         }
78
79
80         if (self.crouch)
81         {
82                 if (self.movement_x * self.movement_x + self.movement_y * self.movement_y > 20)
83                         self.frame = $duckwalk;
84                 else
85                         self.frame = $duckidle;
86         }
87         else if ((self.movement_x * self.movement_x + self.movement_y * self.movement_y) > 20)
88         {
89                 if (self.movement_x > 0 && self.movement_y == 0)
90                         self.frame = $run;
91                 else if (self.movement_x < 0 && self.movement_y == 0)
92                         self.frame = $runbackwards;
93                 else if (self.movement_x == 0 && self.movement_y > 0)
94                         self.frame = $straferight;
95                 else if (self.movement_x == 0 && self.movement_y < 0)
96                         self.frame = $strafeleft;
97                 else if (self.movement_x > 0 && self.movement_y > 0)
98                         self.frame = $forwardright;
99                 else if (self.movement_x > 0 && self.movement_y < 0)
100                         self.frame = $forwardleft;
101                 else if (self.movement_x < 0 && self.movement_y > 0)
102                         self.frame = $backright;
103                 else if (self.movement_x < 0 && self.movement_y < 0)
104                         self.frame = $backleft;
105                 else
106                         self.frame = $run;
107         }
108         else if (self.pain_finished > time)
109                 self.frame = self.pain_frame;
110         else if (ATTACK_FINISHED(self) > time)
111                 self.frame = $shoot;
112         else
113                 self.frame = $idle;
114
115         if (!(self.flags & FL_ONGROUND))
116         {
117                 if (self.crouch)
118                         self.frame = $duckidle;         // if player is crouching while in air, show crouch frame
119                 else
120                         self.frame = $jump;
121         }
122 }
123 //End change by Supajoe on 11:44 PM EST 11/16/03 (Subject: Player animations)
124
125 void SpawnThrownWeapon (vector org, float w)
126 {
127         if (!cvar("g_pickup_items"))
128         if (!cvar("g_minstagib"))
129                 return;
130         if (!w)
131                 return;
132         if (w == IT_LASER)
133                 return;
134
135         W_ThrowWeapon(randomvec() * 100 + '0 0 200', org - self.origin, FALSE);
136 }
137
138 void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
139 {
140         local float take, save;
141         te_blood (hitloc, force, damage);
142         // damage resistance (ignore most of the damage from a bullet or similar)
143         damage = max(damage - 5, 1);
144
145         save = bound(0, damage * cvar("g_balance_armor_blockpercent"), self.armorvalue);
146         take = bound(0, damage - save, damage);
147
148         if (save > 10)
149                 sound (self, CHAN_IMPACT, "misc/armorimpact.wav", 1, ATTN_NORM);
150         else if (take > 30)
151                 sound (self, CHAN_IMPACT, "misc/bodyimpact2.wav", 1, ATTN_NORM);
152         else if (take > 10)
153                 sound (self, CHAN_IMPACT, "misc/bodyimpact1.wav", 1, ATTN_NORM);
154
155         if (take > 50)
156                 TossGib (world, "models/gibs/chunk.mdl", hitloc, force * -0.1,1);
157         if (take > 100)
158                 TossGib (world, "models/gibs/chunk.mdl", hitloc, force * -0.2,1);
159
160         if (!(self.flags & FL_GODMODE))
161         {
162                 self.armorvalue = self.armorvalue - save;
163                 self.health = self.health - take;
164                 // pause regeneration for 5 seconds
165                 self.pauseregen_finished = max(self.pauseregen_finished, time + cvar("g_balance_pause_health_regen"));
166         }
167         self.dmg_save = self.dmg_save + save;//max(save - 10, 0);
168         self.dmg_take = self.dmg_take + take;//max(take - 10, 0);
169         self.dmg_inflictor = inflictor;
170
171         if (self.health <= -50)
172         {
173                 // don't use any animations as a gib
174                 self.frame = 0;
175                 self.dead_frame = 0;
176                 self.die_frame = 0;
177                 // view just above the floor
178                 self.view_ofs = '0 0 4';
179
180                 // make a juicy mess
181                 local float multiplier;
182                 multiplier = 1;
183                 if (cvar("ekg"))
184                         multiplier = 5;
185                 te_bloodshower (self.origin + self.mins, self.origin + self.maxs, 1200 * multiplier, 1000);
186
187                 // make a meaty mess
188                 TossGib (self, "models/gibs/eye.md3", self.origin, self.velocity,0);
189                 TossGib (world, "models/gibs/bloodyskull.md3", self.origin, '0 0 600',0);
190
191                 local float c;
192                 c = 0;
193                 while (c < multiplier)
194                 {
195                         c = c + 1;
196                         TossGib (world, "models/gibs/gib1.md3", self.origin, self.velocity,0);
197                         //TossGib (world, "models/gibs/gib2.md3", self.origin, self.velocity,0);
198                         TossGib (world, "models/gibs/gib1.mdl", self.origin, self.velocity,0);
199                         //TossGib (world, "models/gibs/gib3.md3", self.origin, self.velocity,0);
200                         TossGib (world, "models/gibs/gib2.mdl", self.origin, self.velocity,0);
201                         //TossGib (world, "models/gibs/gib4.md3", self.origin, self.velocity,0);
202                         TossGib (world, "models/gibs/gib3.mdl", self.origin, self.velocity,0);
203
204                         // these destory on impact
205                         TossGib (world, "models/gibs/gib5.md3", self.origin, '-500 0 450',1);
206                         //TossGib (world, "models/gibs/gib6.md3", self.origin, '0 500 450',1);
207                         TossGib (world, "models/gibs/chunk.mdl", self.origin, '0 -500 450',1);
208                         TossGib (world, "models/gibs/chunk.mdl", self.origin, '500 0 450',1);
209                         TossGib (world, "models/gibs/chunk.mdl", self.origin, self.velocity,1);
210                         TossGib (world, "models/gibs/chunk.mdl", self.origin, '0 0 450',1);
211                 }
212
213                 sound (self, CHAN_VOICE, "misc/gib.wav", 1, ATTN_NORM);
214         }
215 }
216
217 void PlayerDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
218 {
219         local float take, save, waves, sdelay;
220
221         damage = damage * bound(1.0, self.cvar_cl_handicap, 100.0);
222
223         te_blood (hitloc, force, damage);
224         if (self.pain_finished < time)          //Don't switch pain sequences like crazy
225         {
226                 if (random() > 0.5)
227                         self.pain_frame = $pain1;
228                 else
229                         self.pain_frame = $pain2;
230                 self.pain_finished = time + 0.5;        //Supajoe
231
232                 // throw off bot aim temporarily
233                 local float shake;
234                 shake = damage * 5 / (bound(0,skill,100) + 1);
235                 self.v_angle_x = self.v_angle_x + (random() * 2 - 1) * shake;
236                 self.v_angle_y = self.v_angle_y + (random() * 2 - 1) * shake;
237         }
238
239         if(cvar("g_arena"))
240         if(numspawned < 2)
241                 return;
242
243         if (!cvar("g_minstagib"))
244         {
245                 save = bound(0, damage * cvar("g_balance_armor_blockpercent"), self.armorvalue);
246                 take = bound(0, damage - save, damage);
247         }
248         else
249         {
250                 save = 0;
251                 take = damage;
252         }
253
254         if (save > 10)
255                 sound (self, CHAN_IMPACT, "misc/armorimpact.wav", 1, ATTN_NORM);
256         else if (take > 30)
257                 sound (self, CHAN_IMPACT, "misc/bodyimpact2.wav", 1, ATTN_NORM);
258         else if (take > 10)
259                 sound (self, CHAN_IMPACT, "misc/bodyimpact1.wav", 1, ATTN_NORM);
260
261         if (take > 50)
262                 TossGib (world, "models/gibs/chunk.mdl", hitloc, force * -0.1,1);
263         if (take > 100)
264                 TossGib (world, "models/gibs/chunk.mdl", hitloc, force * -0.2,1);
265
266         if (time > self.spawnshieldtime)
267         {
268                 if (!(self.flags & FL_GODMODE))
269                 {
270                         self.armorvalue = self.armorvalue - save;
271                         self.health = self.health - take;
272                         // pause regeneration for 5 seconds
273                         self.pauseregen_finished = max(self.pauseregen_finished, time + cvar("g_balance_pause_health_regen"));
274                 }
275                 else
276                         self.max_armorvalue += (save + take);
277         }
278         self.dmg_save = self.dmg_save + save;//max(save - 10, 0);
279         self.dmg_take = self.dmg_take + take;//max(take - 10, 0);
280         self.dmg_inflictor = inflictor;
281
282         if(attacker == self)
283         {
284                 // don't reset pushltime for self damage as it may be an attempt to
285                 // escape a lava pit or similar
286                 //self.pushltime = 0;
287         }
288         else if(attacker.classname == "player" || attacker.classname == "gib")
289         {
290                 self.pusher = attacker;
291                 self.pushltime = time + cvar("g_maxpushtime");
292         }
293         else if(time < self.pushltime)
294         {
295                 attacker = self.pusher;
296                 self.pushltime = max(self.pushltime, time + 0.6);
297         }
298         else
299                 self.pushltime = 0;
300
301         if (self.health < 1)
302         {
303                 self.deaths += 1;
304
305                 // become fully visible
306                 self.alpha = 1;
307                 // clear selected player display
308                 ClearSelectedPlayer();
309                 // throw a weapon
310                 SpawnThrownWeapon (self.origin + (self.mins + self.maxs) * 0.5, self.switchweapon);
311                 // print an obituary message
312                 Obituary (attacker, self, deathtype);
313                 DropAllRunes(self);
314                 if(self == attacker)
315                         kh_Key_DropAll(self, TRUE);
316                 else if(attacker.classname == "player" || attacker.classname == "gib")
317                         kh_Key_DropAll(self, FALSE);
318                 else
319                         kh_Key_DropAll(self, TRUE);
320                 if(self.flagcarried)
321                         DropFlag(self.flagcarried);
322                 // clear waypoints
323                 WaypointSprite_PlayerDead();
324                 // make the corpse upright (not tilted)
325                 self.angles_x = 0;
326                 self.angles_z = 0;
327                 // don't spin
328                 self.avelocity = '0 0 0';
329                 // view from the floor
330                 self.view_ofs = '0 0 -8';
331                 // toss the corpse
332                 self.movetype = MOVETYPE_TOSS;
333                 // shootable corpse
334                 self.solid = SOLID_CORPSE;
335                 // don't stick to the floor
336                 self.flags = self.flags - (self.flags & FL_ONGROUND);
337                 // dying animation
338                 self.deadflag = DEAD_DYING;
339                 // when to allow respawn
340                 sdelay = 0;
341                 waves = 0;
342                 if(cvar("g_respawn_mapsettings"))
343                 {
344                         sdelay = cvar("g_respawn_mapsettings_delay");
345                         waves = cvar("g_respawn_mapsettings_waves");
346                 }
347                 if(!sdelay)
348                         sdelay = cvar(strcat("g_", GetGametype(), "_respawn_delay"));
349                 if(!sdelay)
350                         sdelay = cvar("g_respawn_delay");
351                 if(!waves)
352                         waves = cvar(strcat("g_", GetGametype(), "_respawn_waves"));
353                 if(!waves)
354                         waves = cvar("g_respawn_waves");
355                 if(waves)
356                         self.death_time = ceil((time + sdelay) / waves) * waves;
357                 else
358                         self.death_time = time + sdelay;
359                 if((sdelay + waves >= 5.0) && (self.death_time - time > 1.75))
360                         self.respawn_countdown = 10; // first number to count down from is 10
361                 else
362                         self.respawn_countdown = -1; // do not count down
363                 // when to switch to the dead_frame
364                 self.dead_time = time + 2;
365                 if (random() < 0.5)
366                 {
367                         self.die_frame = $die1;
368                         self.dead_frame = $dead1;
369                 }
370                 else
371                 {
372                         self.die_frame = $die2;
373                         self.dead_frame = $dead2;
374                 }
375                 // start the animation
376                 player_anim();
377                 // set damage function to corpse damage
378                 self.event_damage = PlayerCorpseDamage;
379                 // call the corpse damage function just in case it wants to gib
380                 self.event_damage(inflictor, attacker, 0, deathtype, hitloc, force);
381                 // set up to fade out later
382                 SUB_SetFade (self, time + 12 + random () * 4, 1);
383
384                 // remove laserdot
385                 if(self.weaponentity)
386                         if(self.weaponentity.lasertarget)
387                                 remove(self.weaponentity.lasertarget);
388
389                 if(clienttype(self) == CLIENTTYPE_REAL)
390                 {
391                         self.fixangle = TRUE;
392                         //msg_entity = self;
393                         //WriteByte (MSG_ONE, SVC_SETANGLE);
394                         //WriteAngle (MSG_ONE, self.v_angle_x);
395                         //WriteAngle (MSG_ONE, self.v_angle_y);
396                         //WriteAngle (MSG_ONE, 80);
397                 }
398
399                 if(cvar("g_arena"))
400                         Spawnqueue_Unmark(self);
401         }
402 }
403
404 float UpdateSelectedPlayer_countvalue(float v)
405 {
406         return max(0, (v - 1.0) / 0.5);
407 }
408
409 // returns: -2 if no hit, otherwise cos of the angle
410 // uses the global v_angle
411 float UpdateSelectedPlayer_canSee(entity p, float mincosangle, float maxdist)
412 {
413         vector so, d;
414         float c;
415
416         if(p == self)
417                 return -2;
418
419         if(p.deadflag)
420                 return -2;
421
422         so = self.origin + self.view_ofs;
423         d = p.origin - so;
424
425         // misaimed?
426         if(dist_point_line(d, '0 0 0', v_forward) > maxdist)
427                 return -2;
428
429         // now find the cos of the angle...
430         c = normalize(d) * v_forward;
431
432         if(c <= mincosangle)
433                 return -2;
434
435         traceline(so, p.origin, MOVE_NOMONSTERS, self);
436         if(trace_fraction < 1)
437                 return -2;
438
439         return c;
440 }
441
442 void ClearSelectedPlayer()
443 {
444         if(self.selected_player)
445         {
446                 centerprint_expire(self, CENTERPRIO_POINT);
447                 self.selected_player = world;
448                 self.selected_player_display_needs_update = FALSE;
449         }
450 }
451
452 void UpdateSelectedPlayer()
453 {
454         entity selected;
455         float selected_score;
456         selected = world;
457         selected_score = 0.95; // 18 degrees
458
459         if(!cvar("sv_allow_shownames"))
460                 return;
461
462         if(clienttype(self) != CLIENTTYPE_REAL)
463                 return;
464
465         if(self.cvar_cl_shownames == 0)
466                 return;
467
468         if(self.cvar_cl_shownames == 1 && !teams_matter)
469                 return;
470
471         makevectors(self.v_angle); // sets v_forward
472
473         // 1. cursor trace is always right
474         if(self.cursor_trace_ent && self.cursor_trace_ent.classname == "player" && !self.cursor_trace_ent.deadflag)
475         {
476                 selected = self.cursor_trace_ent;
477         }
478         else
479         {
480                 // 2. if we don't have a cursor trace, find the player which is least
481                 //    mis-aimed at
482                 entity p;
483                 FOR_EACH_PLAYER(p)
484                 {
485                         float c;
486                         c = UpdateSelectedPlayer_canSee(p, selected_score, 100); // 100 = 2.5 meters
487                         if(c >= -1)
488                         {
489                                 selected = p;
490                                 selected_score = c;
491                         }
492                 }
493         }
494
495         if(selected)
496         {
497                 self.selected_player_display_timeout = time + self.cvar_scr_centertime;
498         }
499         else
500         {
501                 if(time < self.selected_player_display_timeout)
502                         if(UpdateSelectedPlayer_canSee(self.selected_player, 0.7, 200) >= -1) // 5 meters, 45 degrees
503                                 selected = self.selected_player;
504         }
505
506         if(selected)
507         {
508                 if(selected == self.selected_player)
509                 {
510                         float save;
511                         save = UpdateSelectedPlayer_countvalue(self.selected_player_count);
512                         self.selected_player_count = self.selected_player_count + frametime;
513                         if(save != UpdateSelectedPlayer_countvalue(self.selected_player_count))
514                         {
515                                 string namestr, healthstr;
516                                 namestr = playername(selected);
517                                 if(teams_matter)
518                                 {
519                                         healthstr = ftos(floor(selected.health));
520                                         if(self.team == selected.team)
521                                         {
522                                                 namestr = strcat(namestr, " (", healthstr, "%)");
523                                                 self.selected_player_display_needs_update = TRUE;
524                                         }
525                                 }
526                                 centerprint_atprio(self, CENTERPRIO_POINT, namestr);
527                         }
528                 }
529                 else
530                 {
531                         ClearSelectedPlayer();
532                         self.selected_player = selected;
533                         self.selected_player_time = time;
534                         self.selected_player_count = 0;
535                         self.selected_player_display_needs_update = FALSE;
536                 }
537         }
538         else
539         {
540                 ClearSelectedPlayer();
541         }
542
543         if(self.selected_player)
544                 self.last_selected_player = self.selected_player;
545 }