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