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