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