added secondary fire zoom on nex
[divverent/nexuiz.git] / qcsrc / gamec / cl_client.c
1 void info_player_start (void)
2 {
3         self.classname = "info_player_deathmatch";
4 }
5
6 void info_player_deathmatch (void)
7 {
8 }
9
10 /*
11 =============
12 SelectSpawnPoint
13
14 Finds a point to respawn
15 =============
16 */
17 entity SelectSpawnPoint (void)
18 {
19         local entity spot, thing;
20         local float pcount;
21
22         spot = find (world, classname, "testplayerstart");
23         if (spot)
24                 return spot;
25
26         spot = lastspawn;
27         while (1)
28         {
29                 spot = find(spot, classname, "info_player_deathmatch");
30                 if (spot != world)
31                 {
32                         if (spot == lastspawn)
33                                 return lastspawn;
34                         pcount = 0;
35                         thing = findradius(spot.origin, 70);
36                         while(thing)
37                         {
38                                 if (thing.classname == "player")
39                                         pcount = pcount + 1;
40                                 thing = thing.chain;
41                         }
42                         if (pcount == 0)
43                         {
44                                 lastspawn = spot;
45                                 return spot;
46                         }
47                 }
48         }
49
50         spot = find (world, classname, "info_player_start");
51         if (!spot)
52                 error ("PutClientInServer: no info_player_start on level");
53
54         return spot;
55 }
56
57 /*
58 =============
59 CheckPlayerModel
60
61 Checks if the argument string can be a valid playermodel.
62 Returns a valid one in doubt.
63 =============
64 */
65 string CheckPlayerModel(string playermodel) {
66         if( substring(playermodel,0,14) != "models/player/") playermodel = "models/player/marine.zym";
67
68         /* Possible Fixme: Check if server can open the model?
69            This would kill custom models, however. */
70
71         return playermodel;
72 }
73
74
75 /*
76 =============
77 PutClientInServer
78
79 Called when a client spawns in the server
80 =============
81 */
82 void PutClientInServer (void)
83 {
84         entity  spot;
85
86         spot = SelectSpawnPoint ();
87
88         self.classname = "player";
89         self.movetype = MOVETYPE_WALK;
90         self.solid = SOLID_SLIDEBOX;
91         self.flags = FL_CLIENT;
92         self.takedamage = DAMAGE_AIM;
93         self.effects = 0;
94         self.health = cvar("g_balance_health_start");
95         self.armorvalue = cvar("g_balance_armor_start");
96         self.damageforcescale = 2;
97         self.death_time = 0;
98         self.dead_time = 0;
99         self.dead_frame = 0;
100         self.die_frame = 0;
101         self.alpha = 0;
102         self.scale = 0;
103         self.fade_time = 0;
104         self.pain_frame = 0;
105         self.pain_finished = 0;
106         self.strength_finished = 0;
107         self.invincible_finished = 0;
108         //self.speed_finished = 0;
109         //self.slowmo_finished = 0;
110         // players have no think function
111         self.think = SUB_Null;
112         self.nextthink = 0;
113
114         self.deadflag = DEAD_NO;
115
116         self.angles = spot.angles;
117         self.fixangle = TRUE; // turn this way immediately
118         self.velocity = '0 0 0';
119         self.avelocity = '0 0 0';
120         self.punchangle = '0 0 0';
121         self.punchvector = '0 0 0';
122
123         self.viewzoom = 0.6;
124
125
126         self.playermodel = CheckPlayerModel(self.playermodel);
127
128         precache_model (self.playermodel);
129         setmodel (self, self.playermodel);
130         self.skin = stof(self.playerskin);
131         self.crouch = FALSE;
132         self.view_ofs = PL_VIEW_OFS;
133         setsize (self, PL_MIN, PL_MAX);
134         setorigin (self, spot.origin + '0 0 1' * (1 - self.mins_z - 24));
135         // don't reset back to last position, even if new position is stuck in solid
136         self.oldorigin = self.origin;
137
138 //      self.items = IT_LASER | IT_UZI| IT_SHOTGUN | IT_GRENADE_LAUNCHER | IT_ELECTRO | IT_CRYLINK | IT_NEX | IT_HAGAR | IT_ROCKET_LAUNCHER;
139 //      self.weapon = IT_UZI;
140
141         if (cvar("g_instagib") == 1)
142         {
143                 self.items = IT_NEX;
144                 self.switchweapon = WEP_NEX;
145                 self.ammo_shells = 0;
146                 self.ammo_nails = 0;
147                 self.ammo_rockets = 0;
148                 self.ammo_cells = 999;
149         }
150         else if (cvar("g_rocketarena") == 1)
151         {
152                 self.items = IT_ROCKET_LAUNCHER;
153                 self.switchweapon = WEP_ROCKET_LAUNCHER;
154                 self.ammo_shells = 0;
155                 self.ammo_nails = 0;
156                 self.ammo_rockets = 999;
157                 self.ammo_cells = 0;
158         }
159         else
160         {
161                 self.items = IT_LASER | IT_SHOTGUN;
162                 self.switchweapon = WEP_SHOTGUN;
163                 self.ammo_shells = 50;
164                 self.ammo_nails = 0;
165                 self.ammo_rockets = 0;
166                 self.ammo_cells = 0;
167         }
168
169         if (cvar("g_fullbrightplayers") == 1)
170                 self.effects = EF_FULLBRIGHT;
171
172         self.event_damage = PlayerDamage;
173
174         self.statdraintime = time + 5;
175         self.button0 = self.button1 = self.button2 = self.button3 = 0;
176
177         /*
178         W_UpdateWeapon();
179         W_UpdateAmmo();
180         */
181         CL_SpawnWeaponentity();
182
183         //stuffcmd(self, "chase_active 0");
184 }
185
186 /*
187 =============
188 SetNewParms
189 =============
190 */
191 void SetNewParms (void)
192 {
193
194 }
195
196 /*
197 =============
198 SetChangeParms
199 =============
200 */
201 void SetChangeParms (void)
202 {
203
204 }
205
206 /*
207 =============
208 ClientKill
209
210 Called when a client types 'kill' in the console
211 =============
212 */
213 void ClientKill (void)
214 {
215         Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
216 }
217
218 /*
219 =============
220 ClientConnect
221
222 Called when a client connects to the server
223 =============
224 */
225 void ClientConnect (void)
226 {
227         ClientInRankings();
228         bprint ("^4",self.netname);
229         bprint (" connected\n");
230         stuffcmd(self, strcat("exec maps/", mapname, ".cfg\n"));
231         // send prediction settings to the client
232         stuffcmd(self, strcat("cl_movement_maxspeed ", ftos(cvar("sv_maxspeed")), "\n"));
233         stuffcmd(self, strcat("cl_movement_maxairspeed ", ftos(cvar("sv_maxairspeed")), "\n"));
234         stuffcmd(self, strcat("cl_movement_accelerate ", ftos(cvar("sv_accelerate")), "\n"));
235         stuffcmd(self, strcat("cl_movement_friction ", ftos(cvar("sv_friction")), "\n"));
236         stuffcmd(self, strcat("cl_movement_stopspeed ", ftos(cvar("sv_stopspeed")), "\n"));
237         stuffcmd(self, strcat("cl_movement_jumpvelocity ", ftos(cvar("g_balance_jumpheight")), "\n"));
238         stuffcmd(self, strcat("cl_movement_stepheight ", ftos(cvar("sv_stepheight")), "\n"));
239         stuffcmd(self, strcat("cl_movement_edgefriction 0\n"));
240 }
241
242 /*
243 =============
244 ClientDisconnect
245
246 Called when a client disconnects from the server
247 =============
248 */
249 void ClientDisconnect (void)
250 {
251         ClientDisconnected();
252         bprint ("^4",self.netname);
253         bprint (" disconnected\n");
254 }
255
256 .entity chatbubbleentity;
257 .float buttonchat;
258 void() ChatBubbleThink =
259 {
260         self.nextthink = time;
261         if (!self.owner.modelindex || self.owner.chatbubbleentity != self)
262         {
263                 remove(self);
264                 return;
265         }
266         setorigin(self, self.owner.origin + '0 0 15' + self.owner.maxs_z * '0 0 1');
267         if (self.owner.buttonchat)
268                 self.effects = 0;
269         else
270                 self.effects = EF_NODRAW;
271 };
272
273 void() UpdateChatBubble =
274 {
275         if (!self.modelindex)
276                 return;
277         // spawn a chatbubble entity if needed
278         if (!self.chatbubbleentity)
279         {
280                 self.chatbubbleentity = spawn();
281                 self.chatbubbleentity.owner = self;
282                 self.chatbubbleentity.think = ChatBubbleThink;
283                 self.chatbubbleentity.nextthink = time;
284                 setmodel(self.chatbubbleentity, "models/misc/chatbubble.spr");
285                 setorigin(self.chatbubbleentity, self.origin + '0 0 15' + self.maxs_z * '0 0 1');
286                 self.chatbubbleentity.effects = EF_NODRAW;
287         }
288 }
289
290 // LordHavoc: this hack will be removed when proper _pants/_shirt layers are
291 // added to the model skins
292 void() UpdateColorModHack =
293 {
294         local float c;
295         c = self.clientcolors & 15;
296         // LordHavoc: only bothering to support white, green, red, yellow, blue
297              if (teamplay == 0) self.colormod = '0 0 0';
298         else if (c ==  0) self.colormod = '1.00 1.00 1.00';
299         else if (c ==  3) self.colormod = '0.10 1.73 0.10';
300         else if (c ==  4) self.colormod = '1.73 0.10 0.10';
301         else if (c == 12) self.colormod = '1.22 1.22 0.10';
302         else if (c == 13) self.colormod = '0.10 0.10 1.73';
303         else self.colormod = '1 1 1';
304 };
305
306 /*
307 =============
308 PlayerJump
309
310 When you press the jump key
311 =============
312 */
313 void PlayerJump (void)
314 {
315         if (self.waterlevel >= 2)
316         {
317                 if (self.watertype == CONTENT_WATER)
318                         self.velocity_z = 200;
319                 else if (self.watertype == CONTENT_SLIME)
320                         self.velocity_z = 80;
321                 else
322                         self.velocity_z = 50;
323
324                 return;
325         }
326
327
328         if (!(self.flags & FL_ONGROUND))
329                 return;
330
331         if (!(self.flags & FL_JUMPRELEASED))
332                 return;
333
334         self.velocity_z = self.velocity_z + cvar("g_balance_jumpheight");
335
336         self.flags = self.flags - FL_ONGROUND;
337         self.flags = self.flags - FL_JUMPRELEASED;
338 }
339
340 .float watersound_finished;
341 void() WaterMove =
342 {
343         if (self.movetype == MOVETYPE_NOCLIP)
344                 return;
345         if (self.health < 0)
346                 return;
347
348         if (self.waterlevel != 3)
349         {
350                 self.air_finished = time + 12;
351                 self.dmg = 2;
352         }
353         else if (self.air_finished < time)
354         {       // drown!
355                 if (self.pain_finished < time)
356                 {
357                         Damage (self, world, world, 5, DEATH_DROWN, self.origin, '0 0 0');
358                         self.pain_finished = time + 0.5;
359                 }
360         }
361
362         if (!self.waterlevel)
363         {
364                 if (self.flags & FL_INWATER)
365                 {
366                         // play leave water sound
367                         self.flags = self.flags - FL_INWATER;
368                 }
369                 return;
370         }
371
372         if (self.watersound_finished < time)
373         {
374                 self.watersound_finished = time + 0.5;
375                 if (self.watertype == CONTENT_LAVA)
376                         sound (self, CHAN_BODY, "player/lava.wav", 1, ATTN_NORM);
377                 if (self.watertype == CONTENT_SLIME)
378                         sound (self, CHAN_BODY, "player/slime.wav", 1, ATTN_NORM);
379                 //if (self.watertype == CONTENT_WATER)
380                 //      sound (self, CHAN_BODY, "player/water.wav", 1, ATTN_NORM);
381         }
382
383         if (self.watertype == CONTENT_LAVA)
384         {       // do damage
385                 if (self.dmgtime < time)
386                 {
387                         self.dmgtime = time + 0.1;
388                         Damage (self, world, world, 3 * self.waterlevel, DEATH_LAVA, self.origin, '0 0 0');
389                 }
390         }
391         else if (self.watertype == CONTENT_SLIME)
392         {       // do damage
393                 if (self.dmgtime < time)
394                 {
395                         self.dmgtime = time + 0.1;
396                         Damage (self, world, world, 1 * self.waterlevel, DEATH_SLIME, self.origin, '0 0 0');
397                 }
398         }
399
400         if ( !(self.flags & FL_INWATER) )
401         {
402
403                 //if (self.watertype == CONTENT_LAVA)
404                 //      sound (self, CHAN_BODY, "player/inlava.wav", 1, ATTN_NORM);
405                 //if (self.watertype == CONTENT_WATER)
406                 //      sound (self, CHAN_BODY, "player/inh2o.wav", 1, ATTN_NORM);
407                 //if (self.watertype == CONTENT_SLIME)
408                 //      sound (self, CHAN_BODY, "player/slimbrn2.wav", 1, ATTN_NORM);
409
410                 self.flags = self.flags + FL_INWATER;
411                 self.dmgtime = 0;
412         }
413 };
414
415 void() CheckWaterJump =
416 {
417         local vector start, end;
418
419 // check for a jump-out-of-water
420         makevectors (self.angles);
421         start = self.origin;
422         start_z = start_z + 8;
423         v_forward_z = 0;
424         normalize(v_forward);
425         end = start + v_forward*24;
426         traceline (start, end, TRUE, self);
427         if (trace_fraction < 1)
428         {       // solid at waist
429                 start_z = start_z + self.maxs_z - 8;
430                 end = start + v_forward*24;
431                 self.movedir = trace_plane_normal * -50;
432                 traceline (start, end, TRUE, self);
433                 if (trace_fraction == 1)
434                 {       // open at eye level
435                         self.flags = self.flags | FL_WATERJUMP;
436                         self.velocity_z = 225;
437                         self.flags = self.flags - (self.flags & FL_JUMPRELEASED);
438                         self.teleport_time = time + 2;  // safety net
439                         return;
440                 }
441         }
442 };
443
444
445 void respawn(void)
446 {
447         CopyBody(1);
448         PutClientInServer();
449 }
450
451 void player_powerups (void)
452 {
453         self.effects = self.effects - (self.effects & (EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT));
454         if (self.items & IT_STRENGTH)
455         {
456                 self.effects = self.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
457                 if (time > self.strength_finished)
458                 {
459                         self.items = self.items - (self.items & IT_STRENGTH);
460                         sprint(self, "^3Strength has worn off\n");
461                 }
462         }
463         else
464         {
465                 if (time < self.strength_finished)
466                 {
467                         self.items = self.items | IT_STRENGTH;
468                         sprint(self, "^3Strength infuses your weapons with devestating power\n");
469                 }
470         }
471         if (self.items & IT_INVINCIBLE)
472         {
473                 self.effects = self.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
474                 if (time > self.invincible_finished)
475                 {
476                         self.items = self.items - (self.items & IT_INVINCIBLE);
477                         sprint(self, "^3Shield has worn off\n");
478                 }
479         }
480         else
481         {
482                 if (time < self.invincible_finished)
483                 {
484                         self.items = self.items | IT_INVINCIBLE;
485                         sprint(self, "^3Shield surrounds you\n");
486                 }
487         }
488 }
489
490 void player_regen (void)
491 {
492         local float maxh;
493         local float maxa;
494         maxh = cvar("g_balance_health_stable");
495         maxa = cvar("g_balance_armor_stable");
496         if (self.health > maxh)
497                 self.health = bound(0, self.health + (maxh - self.health) * cvar("g_balance_health_rot") * frametime, 1000);
498         else if (time > self.pain_finished)
499                 self.health = bound(0, self.health + (maxh- self.health) * cvar("g_balance_health_regen") * frametime, 1000);
500         if (self.armorvalue > maxa)
501                 self.armorvalue = bound(0, self.armorvalue + (maxa - self.armorvalue) * cvar("g_balance_armor_rot") * frametime, 1000);
502         else if (time > self.pain_finished)
503                 self.armorvalue = bound(0, self.armorvalue + (maxa - self.armorvalue) * cvar("g_balance_armor_regen") * frametime, 1000);
504 }
505
506 /*
507 =============
508 PlayerPreThink
509
510 Called every frame for each client before the physics are run
511 =============
512 */
513 .float attack_finished;
514 void PlayerPreThink (void)
515 {
516         local vector m1, m2;
517
518         if (BotPreFrame())
519                 return;
520
521         CheckRules_Player();
522
523         if (intermission_running)
524         {
525                 IntermissionThink ();   // otherwise a button could be missed between
526                 return;                                 // the think tics
527         }
528
529         if (self.deadflag != DEAD_NO)
530         {
531                 player_anim();
532                 weapon_freeze();
533                 if (self.deadflag == DEAD_DYING)
534                 {
535                         if (time > self.dead_time)
536                                 self.deadflag = DEAD_DEAD;
537                 }
538                 else if (self.deadflag == DEAD_DEAD)
539                 {
540                         if (!self.button0 && !self.button2 && !self.button3)
541                                 self.deadflag = DEAD_RESPAWNABLE;
542                 }
543                 else if (self.deadflag == DEAD_RESPAWNABLE)
544                 {
545                         if (self.button0 || self.button2 || self.button3  || self.button4)
546                                 respawn();
547                 }
548                 return;
549         }
550
551         if (self.button5)
552         {
553                 if (!self.crouch)
554                 {
555                         self.crouch = TRUE;
556                         self.view_ofs = PL_CROUCH_VIEW_OFS;
557                         setsize (self, PL_CROUCH_MIN, PL_CROUCH_MAX);
558                 }
559         }
560         else
561         {
562                 if (self.crouch)
563                 {
564                         tracebox(self.origin, PL_MIN, PL_MAX, self.origin, FALSE, self);
565                         if (!trace_startsolid)
566                         {
567                                 self.crouch = FALSE;
568                                 self.view_ofs = PL_VIEW_OFS;
569                                 setsize (self, PL_MIN, PL_MAX);
570                         }
571                 }
572         }
573
574         if (self.playermodel != self.model)
575         {
576                 self.playermodel = CheckPlayerModel(self.playermodel);
577                 m1 = self.mins;
578                 m2 = self.maxs;
579                 precache_model (self.playermodel);
580                 setmodel (self, self.playermodel);
581                 setsize (self, m1, m2);
582         }
583
584         if (self.skin != stof(self.playerskin))
585                 self.skin = stof(self.playerskin);
586
587         W_WeaponFrame();
588
589         if (self.button4 || (self.weapon == WEP_NEX && self.button3))
590         {
591                 if (self.viewzoom > 0.4)
592                         self.viewzoom = max (0.4, self.viewzoom - frametime * 2);
593         }
594         else if (self.viewzoom < 1.0)
595                 self.viewzoom = min (1.0, self.viewzoom + frametime);
596
597
598         if (self.button2)
599                 PlayerJump ();
600         else
601                 self.flags = self.flags | FL_JUMPRELEASED;
602
603
604         player_powerups();
605         player_regen();
606         player_anim();
607
608         //self.angles_y=self.v_angle_y + 90;   // temp
609
610         WaterMove ();
611         if (self.waterlevel == 2)
612                 CheckWaterJump ();
613
614         //if (TetrisPreFrame()) return;
615 }
616
617 /*
618 =============
619 PlayerPostThink
620
621 Called every frame for each client after the physics are run
622 =============
623 */
624 void PlayerPostThink (void)
625 {
626         float soundrandom;
627         if (BotPostFrame())
628                 return;
629         CheckRules_Player();
630         UpdateChatBubble();
631         UpdateColorModHack();
632         if (self.deadflag == DEAD_NO)
633         if (self.impulse)
634                 ImpulseCommands ();
635         if (intermission_running)
636                 return;         // intermission or finale
637
638         // VorteX: landing on floor, landing damage etc.
639         // LordHavoc: removed 'big fall' death code that VorteX added
640         if (self.flags & FL_ONGROUND)
641         {
642                 if (self.jump_flag < -100 && !self.watertype == CONTENT_WATER) // HitGround
643                 {
644                         soundrandom = random() * 4;
645                         if (soundrandom < 1)
646                                 sound (self, CHAN_BODY, "misc/hitground1.wav", 1, ATTN_NORM);
647                         else if (soundrandom < 2)
648                                 sound (self, CHAN_BODY, "misc/hitground2.wav", 1, ATTN_NORM);
649                         else if (soundrandom < 3)
650                                 sound (self, CHAN_BODY, "misc/hitground3.wav", 1, ATTN_NORM);
651                         else if (soundrandom < 4)
652                                 sound (self, CHAN_BODY, "misc/hitground4.wav", 1, ATTN_NORM);
653                         if (self.jump_flag < -650) // landing damage
654                         {
655                                 local float dm;
656                                 dm = bound(0, 0.1*(fabs(self.jump_flag) - 600), 5);
657                                 Damage (self, world, world, dm, DEATH_FALL, self.origin, '0 0 0');
658                         }
659                         self.jump_flag = 0;
660                 }
661         }
662         else
663                 self.jump_flag = self.velocity_z;
664
665         //if (TetrisPostFrame()) return;
666 }