]> icculus.org git repositories - divverent/nexuiz.git/blob - qcsrc/gamec/cl_client.c
added bot_number cvar
[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.pushltime = 0;
172         //self.speed_finished = 0;
173         //self.slowmo_finished = 0;
174         // players have no think function
175         self.think = SUB_Null;
176         self.nextthink = 0;
177
178         self.deadflag = DEAD_NO;
179
180         self.angles = spot.angles;
181         self.angles_z = 0; // never spawn tilted even if the spot says to
182         self.fixangle = TRUE; // turn this way immediately
183         self.velocity = '0 0 0';
184         self.avelocity = '0 0 0';
185         self.punchangle = '0 0 0';
186         self.punchvector = '0 0 0';
187         self.oldvelocity = self.velocity;
188
189         self.viewzoom = 0.6;
190
191
192         self.playermodel = CheckPlayerModel(self.playermodel);
193
194         precache_model (self.playermodel);
195         setmodel (self, self.playermodel);
196         self.skin = stof(self.playerskin);
197         self.crouch = FALSE;
198         self.view_ofs = PL_VIEW_OFS;
199         setsize (self, PL_MIN, PL_MAX);
200         setorigin (self, spot.origin + '0 0 1' * (1 - self.mins_z - 24));
201         // don't reset back to last position, even if new position is stuck in solid
202         self.oldorigin = self.origin;
203
204 //      self.items = IT_LASER | IT_UZI| IT_SHOTGUN | IT_GRENADE_LAUNCHER | IT_ELECTRO | IT_CRYLINK | IT_NEX | IT_HAGAR | IT_ROCKET_LAUNCHER;
205 //      self.weapon = IT_UZI;
206
207         if (cvar("g_instagib") == 1)
208         {
209                 self.items = IT_NEX;
210                 self.switchweapon = WEP_NEX;
211                 self.ammo_shells = 0;
212                 self.ammo_nails = 0;
213                 self.ammo_rockets = 0;
214                 self.ammo_cells = 999;
215         }
216         else if (cvar("g_rocketarena") == 1)
217         {
218                 self.items = IT_ROCKET_LAUNCHER;
219                 self.switchweapon = WEP_ROCKET_LAUNCHER;
220                 self.ammo_shells = 0;
221                 self.ammo_nails = 0;
222                 self.ammo_rockets = 999;
223                 self.ammo_cells = 0;
224         }
225         else
226         {
227                 self.items = IT_LASER | IT_SHOTGUN;
228                 self.switchweapon = WEP_SHOTGUN;
229                 self.ammo_shells = 50;
230                 self.ammo_nails = 0;
231                 self.ammo_rockets = 0;
232                 self.ammo_cells = 0;
233         }
234
235         self.event_damage = PlayerDamage;
236
237         self.statdraintime = time + 5;
238         self.button0 = self.button1 = self.button2 = self.button3 = 0;
239
240         /*
241         W_UpdateWeapon();
242         W_UpdateAmmo();
243         */
244         CL_SpawnWeaponentity();
245
246         //stuffcmd(self, "chase_active 0");
247 }
248
249 /*
250 =============
251 SetNewParms
252 =============
253 */
254 void SetNewParms (void)
255 {
256
257 }
258
259 /*
260 =============
261 SetChangeParms
262 =============
263 */
264 void SetChangeParms (void)
265 {
266
267 }
268
269 /*
270 =============
271 ClientKill
272
273 Called when a client types 'kill' in the console
274 =============
275 */
276 void ClientKill (void)
277 {
278         Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
279 }
280
281 /*
282 =============
283 ClientConnect
284
285 Called when a client connects to the server
286 =============
287 */
288 void ClientConnect (void)
289 {
290         bprint ("^4",self.netname);
291         bprint (" connected\n");
292         stuffcmd(self, strcat("exec maps/", mapname, ".cfg\n"));
293         // send prediction settings to the client
294         stuffcmd(self, strcat("cl_movement_maxspeed ", ftos(cvar("sv_maxspeed")), "\n"));
295         stuffcmd(self, strcat("cl_movement_maxairspeed ", ftos(cvar("sv_maxairspeed")), "\n"));
296         stuffcmd(self, strcat("cl_movement_accelerate ", ftos(cvar("sv_accelerate")), "\n"));
297         stuffcmd(self, strcat("cl_movement_friction ", ftos(cvar("sv_friction")), "\n"));
298         stuffcmd(self, strcat("cl_movement_stopspeed ", ftos(cvar("sv_stopspeed")), "\n"));
299         stuffcmd(self, strcat("cl_movement_jumpvelocity ", ftos(cvar("g_balance_jumpheight")), "\n"));
300         stuffcmd(self, strcat("cl_movement_stepheight ", ftos(cvar("sv_stepheight")), "\n"));
301         stuffcmd(self, strcat("cl_movement_edgefriction 0\n"));
302 }
303
304 /*
305 =============
306 ClientDisconnect
307
308 Called when a client disconnects from the server
309 =============
310 */
311 .entity chatbubbleentity;
312 void ClientDisconnect (void)
313 {
314         bprint ("^4",self.netname);
315         bprint (" disconnected\n");
316
317         if (self.chatbubbleentity)
318         {
319                 remove (self.chatbubbleentity);
320                 self.chatbubbleentity = world;
321         }
322 }
323
324 .float buttonchat;
325 void() ChatBubbleThink =
326 {
327         self.nextthink = time;
328         if (!self.owner.modelindex || self.owner.chatbubbleentity != self)
329         {
330                 remove(self);
331                 return;
332         }
333         setorigin(self, self.owner.origin + '0 0 15' + self.owner.maxs_z * '0 0 1');
334         if (self.owner.buttonchat && !self.owner.deadflag)
335                 self.model = self.mdl;
336         else
337                 self.model = "";
338 };
339
340 void() UpdateChatBubble =
341 {
342         if (!self.modelindex)
343                 return;
344         // spawn a chatbubble entity if needed
345         if (!self.chatbubbleentity)
346         {
347                 self.chatbubbleentity = spawn();
348                 self.chatbubbleentity.owner = self;
349                 self.chatbubbleentity.exteriormodeltoclient = self;
350                 self.chatbubbleentity.think = ChatBubbleThink;
351                 self.chatbubbleentity.nextthink = time;
352                 setmodel(self.chatbubbleentity, "models/misc/chatbubble.spr");
353                 setorigin(self.chatbubbleentity, self.origin + '0 0 15' + self.maxs_z * '0 0 1');
354                 self.chatbubbleentity.mdl = self.chatbubbleentity.model;
355                 self.chatbubbleentity.model = "";
356         }
357 }
358
359 // LordHavoc: this hack will be removed when proper _pants/_shirt layers are
360 // added to the model skins
361 void() UpdateColorModHack =
362 {
363         local float c;
364         c = self.clientcolors & 15;
365         // LordHavoc: only bothering to support white, green, red, yellow, blue
366              if (teamplay == 0) self.colormod = '0 0 0';
367         else if (c ==  0) self.colormod = '1.00 1.00 1.00';
368         else if (c ==  3) self.colormod = '0.10 1.73 0.10';
369         else if (c ==  4) self.colormod = '1.73 0.10 0.10';
370         else if (c == 12) self.colormod = '1.22 1.22 0.10';
371         else if (c == 13) self.colormod = '0.10 0.10 1.73';
372         else self.colormod = '1 1 1';
373 };
374
375 /*
376 =============
377 PlayerJump
378
379 When you press the jump key
380 =============
381 */
382 void PlayerJump (void)
383 {
384         if (self.waterlevel >= 2)
385         {
386                 if (self.watertype == CONTENT_WATER)
387                         self.velocity_z = 200;
388                 else if (self.watertype == CONTENT_SLIME)
389                         self.velocity_z = 80;
390                 else
391                         self.velocity_z = 50;
392
393                 return;
394         }
395
396
397         if (!(self.flags & FL_ONGROUND))
398                 return;
399
400         if (!(self.flags & FL_JUMPRELEASED))
401                 return;
402
403         self.velocity_z = self.velocity_z + cvar("g_balance_jumpheight");
404         self.oldvelocity_z = self.velocity_z;
405
406         self.flags = self.flags - FL_ONGROUND;
407         self.flags = self.flags - FL_JUMPRELEASED;
408 }
409
410 void() CheckWaterJump =
411 {
412         local vector start, end;
413
414 // check for a jump-out-of-water
415         makevectors (self.angles);
416         start = self.origin;
417         start_z = start_z + 8;
418         v_forward_z = 0;
419         normalize(v_forward);
420         end = start + v_forward*24;
421         traceline (start, end, TRUE, self);
422         if (trace_fraction < 1)
423         {       // solid at waist
424                 start_z = start_z + self.maxs_z - 8;
425                 end = start + v_forward*24;
426                 self.movedir = trace_plane_normal * -50;
427                 traceline (start, end, TRUE, self);
428                 if (trace_fraction == 1)
429                 {       // open at eye level
430                         self.flags = self.flags | FL_WATERJUMP;
431                         self.velocity_z = 225;
432                         self.flags = self.flags - (self.flags & FL_JUMPRELEASED);
433                         self.teleport_time = time + 2;  // safety net
434                         return;
435                 }
436         }
437 };
438
439
440 void respawn(void)
441 {
442         CopyBody(1);
443         PutClientInServer();
444 }
445
446 void player_powerups (void)
447 {
448         self.effects = self.effects - (self.effects & (EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT));
449         if (self.items & IT_STRENGTH)
450         {
451                 self.effects = self.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT);
452                 if (time > self.strength_finished)
453                 {
454                         self.items = self.items - (self.items & IT_STRENGTH);
455                         sprint(self, "^3Strength has worn off\n");
456                 }
457         }
458         else
459         {
460                 if (time < self.strength_finished)
461                 {
462                         self.items = self.items | IT_STRENGTH;
463                         sprint(self, "^3Strength infuses your weapons with devestating power\n");
464                 }
465         }
466         if (self.items & IT_INVINCIBLE)
467         {
468                 self.effects = self.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT);
469                 if (time > self.invincible_finished)
470                 {
471                         self.items = self.items - (self.items & IT_INVINCIBLE);
472                         sprint(self, "^3Shield has worn off\n");
473                 }
474         }
475         else
476         {
477                 if (time < self.invincible_finished)
478                 {
479                         self.items = self.items | IT_INVINCIBLE;
480                         sprint(self, "^3Shield surrounds you\n");
481                 }
482         }
483         if (cvar("g_fullbrightplayers"))
484                 self.effects = self.effects | EF_FULLBRIGHT;
485 }
486
487 void player_regen (void)
488 {
489         local float maxh;
490         local float maxa;
491         maxh = cvar("g_balance_health_stable");
492         maxa = cvar("g_balance_armor_stable");
493         if (time > self.pauserothealth_finished)
494         if (self.health > maxh)
495                 self.health = bound(0, self.health + (maxh - self.health) * cvar("g_balance_health_rot") * frametime, 1000);
496         if (time > self.pauserotarmor_finished)
497         if (self.armorvalue > maxa)
498                 self.armorvalue = bound(0, self.armorvalue + (maxa - self.armorvalue) * cvar("g_balance_armor_rot") * frametime, 1000);
499         if (time > self.pauseregen_finished)
500         {
501                 if (self.health < maxh)
502                         self.health = bound(0, self.health + (maxh- self.health) * cvar("g_balance_health_regen") * frametime, 1000);
503                 if (self.armorvalue < maxa)
504                         self.armorvalue = bound(0, self.armorvalue + (maxa - self.armorvalue) * cvar("g_balance_armor_regen") * frametime, 1000);
505         }
506 }
507
508 /*
509 =============
510 PlayerPreThink
511
512 Called every frame for each client before the physics are run
513 =============
514 */
515 void PlayerPreThink (void)
516 {
517         local vector m1, m2;
518
519 //      MauveBot_AI();
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         // Savage: Check for nameless players
585         if (strlen(self.netname) < 1) {
586                 self.netname = "Player";
587                 stuffcmd(self, "name Player\n");
588         }
589
590         if (self.skin != stof(self.playerskin))
591                 self.skin = stof(self.playerskin);
592
593         W_WeaponFrame();
594
595         if (self.button4 || (self.weapon == WEP_NEX && self.button3))
596         {
597                 if (self.viewzoom > 0.4)
598                         self.viewzoom = max (0.4, self.viewzoom - frametime * 2);
599         }
600         else if (self.viewzoom < 1.0)
601                 self.viewzoom = min (1.0, self.viewzoom + frametime);
602
603
604         if (self.button2)
605                 PlayerJump ();
606         else
607                 self.flags = self.flags | FL_JUMPRELEASED;
608
609
610         player_powerups();
611         player_regen();
612         player_anim();
613
614         //self.angles_y=self.v_angle_y + 90;   // temp
615
616         if (self.waterlevel == 2)
617                 CheckWaterJump ();
618
619         //if (TetrisPreFrame()) return;
620 }
621
622 /*
623 =============
624 PlayerPostThink
625
626 Called every frame for each client after the physics are run
627 =============
628 */
629 void PlayerPostThink (void)
630 {
631         CheckRules_Player();
632         UpdateChatBubble();
633         UpdateColorModHack();
634         if (self.deadflag == DEAD_NO)
635         if (self.impulse)
636                 ImpulseCommands ();
637         if (intermission_running)
638                 return;         // intermission or finale
639
640         //if (TetrisPostFrame()) return;
641 }