1 /*
2 * Copyright (C) 1997-2001 Id Software, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License as published by the Free
6 * Software Foundation; either version 2 of the License, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.
12 *
13 * See the GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59
17 * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 *
19 */
20 #include "g_local.h"
21 #include "m_player.h"
22
23 void ClientUserinfoChanged(edict_t * ent, char *userinfo);
24
25 void SP_misc_teleporter_dest(edict_t * ent);
26
27 //
28 /* Gross, ugly, disgustuing hack section */
29 //
30
31 /* this function is an ugly as hell hack to fix some map flaws */
32 //
33 /* the coop spawn spots on some maps are SNAFU. There are coop spots */
34 /* with the wrong targetname as well as spots with no name at all */
35 //
36
37 /*
38 * we use carnal knowledge of the maps to fix the coop spot targetnames to
39 * match
40 */
41 /* that of the nearest named single player spot */
42
43 static void
SP_FixCoopSpots(edict_t * self)44 SP_FixCoopSpots(edict_t * self)
45 {
46 edict_t *spot;
47 vec3_t d;
48
49 spot = NULL;
50
51 while (1) {
52 spot = G_Find(spot, FOFS(classname), "info_player_start");
53 if (!spot)
54 return;
55 if (!spot->targetname)
56 continue;
57 VectorSubtract(self->s.origin, spot->s.origin, d);
58 if (VectorLength(d) < 384) {
59 if ((!self->targetname) || Q_stricmp(self->targetname, spot->targetname) != 0) {
60
61 /*
62 * gi.dprintf("FixCoopSpots changed %s at %s
63 * targetname from %s to %s\n",
64 * self->classname, vtos(self->s.origin),
65 * self->targetname, spot->targetname);
66 */
67 self->targetname = spot->targetname;
68 }
69 return;
70 }
71 }
72 }
73
74 /* now if that one wasn't ugly enough for you then try this one on for size */
75 /* some maps don't have any coop spots at all, so we need to create them */
76 /* where they should have been */
77
78 static void
SP_CreateCoopSpots(edict_t * self)79 SP_CreateCoopSpots(edict_t * self)
80 {
81 edict_t *spot;
82
83 if (Q_stricmp(level.mapname, "security") == 0) {
84 spot = G_Spawn();
85 spot->classname = "info_player_coop";
86 spot->s.origin[0] = 188 - 64;
87 spot->s.origin[1] = -164;
88 spot->s.origin[2] = 80;
89 spot->targetname = "jail3";
90 spot->s.angles[1] = 90;
91
92 spot = G_Spawn();
93 spot->classname = "info_player_coop";
94 spot->s.origin[0] = 188 + 64;
95 spot->s.origin[1] = -164;
96 spot->s.origin[2] = 80;
97 spot->targetname = "jail3";
98 spot->s.angles[1] = 90;
99
100 spot = G_Spawn();
101 spot->classname = "info_player_coop";
102 spot->s.origin[0] = 188 + 128;
103 spot->s.origin[1] = -164;
104 spot->s.origin[2] = 80;
105 spot->targetname = "jail3";
106 spot->s.angles[1] = 90;
107
108 return;
109 }
110 }
111
112
113 /*
114 * QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) The normal
115 * starting point for a level.
116 */
117 void
SP_info_player_start(edict_t * self)118 SP_info_player_start(edict_t * self)
119 {
120 if (!coop->value)
121 return;
122 if (Q_stricmp(level.mapname, "security") == 0) {
123 /* invoke one of our gross, ugly, disgusting hacks */
124 self->think = SP_CreateCoopSpots;
125 self->nextthink = level.time + FRAMETIME;
126 }
127 }
128
129 /*
130 * QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) potential
131 * spawning position for deathmatch games
132 */
133 void
SP_info_player_deathmatch(edict_t * self)134 SP_info_player_deathmatch(edict_t * self)
135 {
136 if (!deathmatch->value) {
137 G_FreeEdict(self);
138 return;
139 }
140 SP_misc_teleporter_dest(self);
141 }
142
143 /*
144 * QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) potential
145 * spawning position for coop games
146 */
147
148 void
SP_info_player_coop(edict_t * self)149 SP_info_player_coop(edict_t * self)
150 {
151 if (!coop->value) {
152 G_FreeEdict(self);
153 return;
154 }
155 if ((Q_stricmp(level.mapname, "jail2") == 0) ||
156 (Q_stricmp(level.mapname, "jail4") == 0) ||
157 (Q_stricmp(level.mapname, "mine1") == 0) ||
158 (Q_stricmp(level.mapname, "mine2") == 0) ||
159 (Q_stricmp(level.mapname, "mine3") == 0) ||
160 (Q_stricmp(level.mapname, "mine4") == 0) ||
161 (Q_stricmp(level.mapname, "lab") == 0) ||
162 (Q_stricmp(level.mapname, "boss1") == 0) ||
163 (Q_stricmp(level.mapname, "fact3") == 0) ||
164 (Q_stricmp(level.mapname, "biggun") == 0) ||
165 (Q_stricmp(level.mapname, "space") == 0) ||
166 (Q_stricmp(level.mapname, "command") == 0) ||
167 (Q_stricmp(level.mapname, "power2") == 0) ||
168 (Q_stricmp(level.mapname, "strike") == 0)) {
169 /* invoke one of our gross, ugly, disgusting hacks */
170 self->think = SP_FixCoopSpots;
171 self->nextthink = level.time + FRAMETIME;
172 }
173 }
174
175
176 /*
177 * QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) The
178 * deathmatch intermission point will be at one of these Use 'angles' instead
179 * of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll'
180 */
181 void
SP_info_player_intermission(void)182 SP_info_player_intermission(void)
183 {
184 }
185
186
187 /* ======================================================================= */
188
189
190 void
player_pain(edict_t * self,edict_t * other,float kick,int damage)191 player_pain(edict_t * self, edict_t * other, float kick, int damage)
192 {
193 /* player pain is handled at the end of the frame in P_DamageFeedback */
194 }
195
196
197 qboolean
IsFemale(edict_t * ent)198 IsFemale(edict_t * ent)
199 {
200 char *info;
201
202 if (!ent->client)
203 return false;
204
205 info = Info_ValueForKey(ent->client->pers.userinfo, "gender");
206 if (info[0] == 'f' || info[0] == 'F')
207 return true;
208 return false;
209 }
210
211 qboolean
IsNeutral(edict_t * ent)212 IsNeutral(edict_t * ent)
213 {
214 char *info;
215
216 if (!ent->client)
217 return false;
218
219 info = Info_ValueForKey(ent->client->pers.userinfo, "gender");
220 if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M')
221 return true;
222 return false;
223 }
224
225 void
ClientObituary(edict_t * self,edict_t * inflictor,edict_t * attacker)226 ClientObituary(edict_t * self, edict_t * inflictor, edict_t * attacker)
227 {
228 int mod;
229 char *message;
230 char *message2;
231 qboolean ff;
232
233 if (coop->value && attacker->client)
234 meansOfDeath |= MOD_FRIENDLY_FIRE;
235
236 if (deathmatch->value || coop->value) {
237 ff = meansOfDeath & MOD_FRIENDLY_FIRE;
238 mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
239 message = NULL;
240 message2 = "";
241
242 switch (mod) {
243 case MOD_SUICIDE:
244 message = "suicides";
245 break;
246 case MOD_FALLING:
247 message = "cratered";
248 break;
249 case MOD_CRUSH:
250 message = "was squished";
251 break;
252 case MOD_WATER:
253 message = "sank like a rock";
254 break;
255 case MOD_SLIME:
256 message = "melted";
257 break;
258 case MOD_LAVA:
259 message = "does a back flip into the lava";
260 break;
261 case MOD_EXPLOSIVE:
262 case MOD_BARREL:
263 message = "blew up";
264 break;
265 case MOD_EXIT:
266 message = "found a way out";
267 break;
268 case MOD_TARGET_LASER:
269 message = "saw the light";
270 break;
271 case MOD_TARGET_BLASTER:
272 message = "got blasted";
273 break;
274 case MOD_BOMB:
275 case MOD_SPLASH:
276 case MOD_TRIGGER_HURT:
277 message = "was in the wrong place";
278 break;
279 }
280 if (attacker == self) {
281 switch (mod) {
282 case MOD_HELD_GRENADE:
283 message = "tried to put the pin back in";
284 break;
285 case MOD_HG_SPLASH:
286 case MOD_G_SPLASH:
287 if (IsNeutral(self))
288 message = "tripped on its own grenade";
289 else if (IsFemale(self))
290 message = "tripped on her own grenade";
291 else
292 message = "tripped on his own grenade";
293 break;
294 case MOD_R_SPLASH:
295 if (IsNeutral(self))
296 message = "blew itself up";
297 else if (IsFemale(self))
298 message = "blew herself up";
299 else
300 message = "blew himself up";
301 break;
302 case MOD_BFG_BLAST:
303 message = "should have used a smaller gun";
304 break;
305 default:
306 if (IsNeutral(self))
307 message = "killed itself";
308 else if (IsFemale(self))
309 message = "killed herself";
310 else
311 message = "killed himself";
312 break;
313 }
314 }
315 if (message) {
316 #ifdef WITH_ACEBOT
317 safe_bprintf
318 #else
319 gi.bprintf
320 #endif
321 (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message);
322 if (deathmatch->value)
323 self->client->resp.score--;
324 self->enemy = NULL;
325 return;
326 }
327 self->enemy = attacker;
328 if (attacker && attacker->client) {
329 switch (mod) {
330 case MOD_BLASTER:
331 message = "was blasted by";
332 break;
333 case MOD_SHOTGUN:
334 message = "was gunned down by";
335 break;
336 case MOD_SSHOTGUN:
337 message = "was blown away by";
338 message2 = "'s super shotgun";
339 break;
340 case MOD_MACHINEGUN:
341 message = "was machinegunned by";
342 break;
343 case MOD_CHAINGUN:
344 message = "was cut in half by";
345 message2 = "'s chaingun";
346 break;
347 case MOD_GRENADE:
348 message = "was popped by";
349 message2 = "'s grenade";
350 break;
351 case MOD_G_SPLASH:
352 message = "was shredded by";
353 message2 = "'s shrapnel";
354 break;
355 case MOD_ROCKET:
356 message = "ate";
357 message2 = "'s rocket";
358 break;
359 case MOD_R_SPLASH:
360 message = "almost dodged";
361 message2 = "'s rocket";
362 break;
363 case MOD_HYPERBLASTER:
364 message = "was melted by";
365 message2 = "'s hyperblaster";
366 break;
367 case MOD_RAILGUN:
368 message = "was railed by";
369 break;
370 case MOD_BFG_LASER:
371 message = "saw the pretty lights from";
372 message2 = "'s BFG";
373 break;
374 case MOD_BFG_BLAST:
375 message = "was disintegrated by";
376 message2 = "'s BFG blast";
377 break;
378 case MOD_BFG_EFFECT:
379 message = "couldn't hide from";
380 message2 = "'s BFG";
381 break;
382 case MOD_HANDGRENADE:
383 message = "caught";
384 message2 = "'s handgrenade";
385 break;
386 case MOD_HG_SPLASH:
387 message = "didn't see";
388 message2 = "'s handgrenade";
389 break;
390 case MOD_HELD_GRENADE:
391 message = "feels";
392 message2 = "'s pain";
393 break;
394 case MOD_TELEFRAG:
395 message = "tried to invade";
396 message2 = "'s personal space";
397 break;
398 }
399 if (message) {
400 #ifdef WITH_ACEBOT
401 safe_bprintf
402 #else
403 gi.bprintf
404 #endif
405 (PRINT_MEDIUM, "%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
406 if (deathmatch->value) {
407 #ifdef WITH_ACEBOT
408 /* botchat> */
409 if (botchat->value && attacker->client && attacker != self) {
410 bTaunt(attacker, self);
411 bInsult(attacker, self);
412 }
413 /* <botchat */
414 #endif
415 if (ff)
416 attacker->client->resp.score--;
417 else
418 attacker->client->resp.score++;
419 }
420 return;
421 }
422 }
423 }
424 #ifdef WITH_ACEBOT
425 safe_bprintf
426 #else
427 gi.bprintf
428 #endif
429 (PRINT_MEDIUM, "%s died.\n", self->client->pers.netname);
430 if (deathmatch->value)
431 self->client->resp.score--;
432 }
433
434
435 void Touch_Item(edict_t * ent, edict_t * other, cplane_t * plane, csurface_t * surf);
436
437 void
TossClientWeapon(edict_t * self)438 TossClientWeapon(edict_t * self)
439 {
440 gitem_t *item;
441 edict_t *drop;
442 qboolean quad;
443 float spread;
444
445 if (!deathmatch->value)
446 return;
447
448 item = self->client->pers.weapon;
449 if (!self->client->pers.inventory[self->client->ammo_index])
450 item = NULL;
451 if (item && (strcmp(item->pickup_name, "Blaster") == 0))
452 item = NULL;
453
454 if (!((int)(dmflags->value) & DF_QUAD_DROP))
455 quad = false;
456 else
457 quad = (self->client->quad_framenum > (level.framenum + 10));
458
459 if (item && quad)
460 spread = 22.5;
461 else
462 spread = 0.0;
463
464 if (item) {
465 self->client->v_angle[YAW] -= spread;
466 drop = Drop_Item(self, item);
467 self->client->v_angle[YAW] += spread;
468 drop->spawnflags = DROPPED_PLAYER_ITEM;
469 }
470 if (quad) {
471 self->client->v_angle[YAW] += spread;
472 drop = Drop_Item(self, FindItemByClassname("item_quad"));
473 self->client->v_angle[YAW] -= spread;
474 drop->spawnflags |= DROPPED_PLAYER_ITEM;
475
476 drop->touch = Touch_Item;
477 drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME;
478 drop->think = G_FreeEdict;
479 }
480 }
481
482
483 /*
484 * ================== LookAtKiller ==================
485 */
486 void
LookAtKiller(edict_t * self,edict_t * inflictor,edict_t * attacker)487 LookAtKiller(edict_t * self, edict_t * inflictor, edict_t * attacker)
488 {
489 vec3_t dir;
490
491 if (attacker && attacker != world && attacker != self) {
492 VectorSubtract(attacker->s.origin, self->s.origin, dir);
493 } else if (inflictor && inflictor != world && inflictor != self) {
494 VectorSubtract(inflictor->s.origin, self->s.origin, dir);
495 } else {
496 self->client->killer_yaw = self->s.angles[YAW];
497 return;
498 }
499
500 if (dir[0])
501 self->client->killer_yaw = 180 / M_PI * atan2(dir[1], dir[0]);
502 else {
503 self->client->killer_yaw = 0;
504 if (dir[1] > 0)
505 self->client->killer_yaw = 90;
506 else if (dir[1] < 0)
507 self->client->killer_yaw = -90;
508 }
509 if (self->client->killer_yaw < 0)
510 self->client->killer_yaw += 360;
511
512
513 }
514
515 /*
516 * ================== player_die ==================
517 */
518 void
player_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)519 player_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
520 {
521 int n;
522
523 VectorClear(self->avelocity);
524
525 self->takedamage = DAMAGE_YES;
526 self->movetype = MOVETYPE_TOSS;
527
528 self->s.modelindex2 = 0;/* remove linked weapon model */
529
530 self->s.angles[0] = 0;
531 self->s.angles[2] = 0;
532
533 self->s.sound = 0;
534 self->client->weapon_sound = 0;
535
536 self->maxs[2] = -8;
537
538 /* self->solid = SOLID_NOT; */
539 self->svflags |= SVF_DEADMONSTER;
540
541 if (!self->deadflag) {
542 self->client->respawn_time = level.time + 1.0;
543 LookAtKiller(self, inflictor, attacker);
544 self->client->ps.pmove.pm_type = PM_DEAD;
545 ClientObituary(self, inflictor, attacker);
546 TossClientWeapon(self);
547 if (deathmatch->value)
548 Cmd_Help_f(self); /* show scores */
549
550 /* clear inventory */
551
552 /*
553 * this is kind of ugly, but it's how we want to handle keys
554 * in coop
555 */
556 for (n = 0; n < game.num_items; n++) {
557 if (coop->value && itemlist[n].flags & IT_KEY)
558 self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n];
559 self->client->pers.inventory[n] = 0;
560 }
561 }
562 /* remove powerups */
563 self->client->quad_framenum = 0;
564 self->client->invincible_framenum = 0;
565 self->client->breather_framenum = 0;
566 self->client->enviro_framenum = 0;
567 self->flags &= ~FL_POWER_ARMOR;
568
569 if (self->health < -40) { /* gib */
570 gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
571 for (n = 0; n < 4; n++)
572 ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
573 ThrowClientHead(self, damage);
574
575 self->takedamage = DAMAGE_NO;
576 } else { /* normal death */
577 if (!self->deadflag) {
578 static int i;
579
580 i = (i + 1) % 3;
581 /* start a death animation */
582 self->client->anim_priority = ANIM_DEATH;
583 if (self->client->ps.pmove.pm_flags & PMF_DUCKED) {
584 self->s.frame = FRAME_crdeath1 - 1;
585 self->client->anim_end = FRAME_crdeath5;
586 } else
587 switch (i) {
588 case 0:
589 self->s.frame = FRAME_death101 - 1;
590 self->client->anim_end = FRAME_death106;
591 break;
592 case 1:
593 self->s.frame = FRAME_death201 - 1;
594 self->client->anim_end = FRAME_death206;
595 break;
596 case 2:
597 self->s.frame = FRAME_death301 - 1;
598 self->client->anim_end = FRAME_death308;
599 break;
600 }
601 gi.sound(self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand() % 4) + 1)), 1, ATTN_NORM, 0);
602 }
603 }
604
605 self->deadflag = DEAD_DEAD;
606
607 gi.linkentity(self);
608 }
609
610 /* ======================================================================= */
611
612 /*
613 * ============== InitClientPersistant
614 *
615 * This is only called when the game first initializes in single player, but is
616 * called after each death and level change in deathmatch ==============
617 */
618 void
InitClientPersistant(gclient_t * client)619 InitClientPersistant(gclient_t * client)
620 {
621 gitem_t *item;
622
623 memset(&client->pers, 0, sizeof(client->pers));
624
625 item = FindItem("Blaster");
626 client->pers.selected_item = ITEM_INDEX(item);
627 client->pers.inventory[client->pers.selected_item] = 1;
628
629 client->pers.weapon = item;
630
631 client->pers.health = 100;
632 client->pers.max_health = 100;
633
634 client->pers.max_bullets = 200;
635 client->pers.max_shells = 100;
636 client->pers.max_rockets = 50;
637 client->pers.max_grenades = 50;
638 client->pers.max_cells = 200;
639 client->pers.max_slugs = 50;
640
641 client->pers.connected = true;
642 }
643
644
645 void
InitClientResp(gclient_t * client)646 InitClientResp(gclient_t * client)
647 {
648 memset(&client->resp, 0, sizeof(client->resp));
649 client->resp.enterframe = level.framenum;
650 client->resp.coop_respawn = client->pers;
651 }
652
653 /*
654 * ================== SaveClientData
655 *
656 * Some information that should be persistant, like health, is still stored in
657 * the edict structure, so it needs to be mirrored out to the client
658 * structure before all the edicts are wiped. ==================
659 */
660 void
SaveClientData(void)661 SaveClientData(void)
662 {
663 int i;
664 edict_t *ent;
665
666 for (i = 0; i < game.maxclients; i++) {
667 ent = &g_edicts[1 + i];
668 if (!ent->inuse)
669 continue;
670 game.clients[i].pers.health = ent->health;
671 game.clients[i].pers.max_health = ent->max_health;
672 game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE | FL_NOTARGET | FL_POWER_ARMOR));
673 if (coop->value)
674 game.clients[i].pers.score = ent->client->resp.score;
675 }
676 }
677
678 void
FetchClientEntData(edict_t * ent)679 FetchClientEntData(edict_t * ent)
680 {
681 ent->health = ent->client->pers.health;
682 ent->max_health = ent->client->pers.max_health;
683 ent->flags |= ent->client->pers.savedFlags;
684 if (coop->value)
685 ent->client->resp.score = ent->client->pers.score;
686 }
687
688
689
690 /*
691 * =======================================================================
692 *
693 * SelectSpawnPoint
694 *
695 * =======================================================================
696 */
697
698 /*
699 * ================ PlayersRangeFromSpot
700 *
701 * Returns the distance to the nearest player from the given spot
702 * ================
703 */
704 float
PlayersRangeFromSpot(edict_t * spot)705 PlayersRangeFromSpot(edict_t * spot)
706 {
707 edict_t *player;
708 float bestplayerdistance;
709 vec3_t v;
710 int n;
711 float playerdistance;
712
713
714 bestplayerdistance = 9999999;
715
716 for (n = 1; n <= maxclients->value; n++) {
717 player = &g_edicts[n];
718
719 if (!player->inuse)
720 continue;
721
722 if (player->health <= 0)
723 continue;
724
725 VectorSubtract(spot->s.origin, player->s.origin, v);
726 playerdistance = VectorLength(v);
727
728 if (playerdistance < bestplayerdistance)
729 bestplayerdistance = playerdistance;
730 }
731
732 return bestplayerdistance;
733 }
734
735 /*
736 * ================ SelectRandomDeathmatchSpawnPoint
737 *
738 * go to a random point, but NOT the two points closest to other players
739 * ================
740 */
741 edict_t *
SelectRandomDeathmatchSpawnPoint(void)742 SelectRandomDeathmatchSpawnPoint(void)
743 {
744 edict_t *spot, *spot1, *spot2;
745 int count = 0;
746 int selection;
747 float range , range1, range2;
748
749 spot = NULL;
750 range1 = range2 = 99999;
751 spot1 = spot2 = NULL;
752
753 while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
754 count++;
755 range = PlayersRangeFromSpot(spot);
756 if (range < range1) {
757 range1 = range;
758 spot1 = spot;
759 } else if (range < range2) {
760 range2 = range;
761 spot2 = spot;
762 }
763 }
764
765 if (!count)
766 return NULL;
767
768 if (count <= 2) {
769 spot1 = spot2 = NULL;
770 } else
771 count -= 2;
772
773 selection = rand() % count;
774
775 spot = NULL;
776 do {
777 spot = G_Find(spot, FOFS(classname), "info_player_deathmatch");
778 if (spot == spot1 || spot == spot2)
779 selection++;
780 } while (selection--);
781
782 return spot;
783 }
784
785 /*
786 * ================ SelectFarthestDeathmatchSpawnPoint
787 *
788 * ================
789 */
790 edict_t *
SelectFarthestDeathmatchSpawnPoint(void)791 SelectFarthestDeathmatchSpawnPoint(void)
792 {
793 edict_t *bestspot;
794 float bestdistance, bestplayerdistance;
795 edict_t *spot;
796
797
798 spot = NULL;
799 bestspot = NULL;
800 bestdistance = 0;
801 while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
802 bestplayerdistance = PlayersRangeFromSpot(spot);
803
804 if (bestplayerdistance > bestdistance) {
805 bestspot = spot;
806 bestdistance = bestplayerdistance;
807 }
808 }
809
810 if (bestspot) {
811 return bestspot;
812 }
813 /* if there is a player just spawned on each and every start spot */
814 /* we have no choice to turn one into a telefrag meltdown */
815 spot = G_Find(NULL, FOFS(classname), "info_player_deathmatch");
816
817 return spot;
818 }
819
820 edict_t *
SelectDeathmatchSpawnPoint(void)821 SelectDeathmatchSpawnPoint(void)
822 {
823 if ((int)(dmflags->value) & DF_SPAWN_FARTHEST)
824 return SelectFarthestDeathmatchSpawnPoint();
825 else
826 return SelectRandomDeathmatchSpawnPoint();
827 }
828
829
830 edict_t *
SelectCoopSpawnPoint(edict_t * ent)831 SelectCoopSpawnPoint(edict_t * ent)
832 {
833 int index;
834 edict_t *spot = NULL;
835 char *target;
836
837 index = ent->client - game.clients;
838
839 /* player 0 starts in normal player spawn point */
840 if (!index)
841 return NULL;
842
843 spot = NULL;
844
845 /* assume there are four coop spots at each spawnpoint */
846 while (1) {
847 spot = G_Find(spot, FOFS(classname), "info_player_coop");
848 if (!spot)
849 return NULL; /* we didn't have enough... */
850
851 target = spot->targetname;
852 if (!target)
853 target = "";
854 if (Q_stricmp(game.spawnpoint, target) == 0) { /* this is a coop spawn
855 * point for one of the
856 * clients here */
857 index--;
858 if (!index)
859 return spot; /* this is it */
860 }
861 }
862
863
864 return spot;
865 }
866
867
868 /*
869 * =========== SelectSpawnPoint
870 *
871 * Chooses a player start, deathmatch start, coop start, etc ============
872 */
873 void
SelectSpawnPoint(edict_t * ent,vec3_t origin,vec3_t angles)874 SelectSpawnPoint(edict_t * ent, vec3_t origin, vec3_t angles)
875 {
876 edict_t *spot = NULL;
877
878 if (deathmatch->value)
879 spot = SelectDeathmatchSpawnPoint();
880 else if (coop->value)
881 spot = SelectCoopSpawnPoint(ent);
882
883 /* find a single player start spot */
884 if (!spot) {
885 while ((spot = G_Find(spot, FOFS(classname), "info_player_start")) != NULL) {
886 if (!game.spawnpoint[0] && !spot->targetname)
887 break;
888
889 if (!game.spawnpoint[0] || !spot->targetname)
890 continue;
891
892 if (Q_stricmp(game.spawnpoint, spot->targetname) == 0)
893 break;
894 }
895
896 if (!spot) {
897 if (!game.spawnpoint[0]) { /* there wasn't a
898 * spawnpoint without a
899 * target, so use any */
900 spot = G_Find(spot, FOFS(classname), "info_player_start");
901 }
902 if (!spot)
903 gi.error("Couldn't find spawn point %s\n", game.spawnpoint);
904 }
905 }
906 VectorCopy(spot->s.origin, origin);
907 origin[2] += 9;
908 VectorCopy(spot->s.angles, angles);
909 }
910
911 /* ====================================================================== */
912
913
914 void
InitBodyQue(void)915 InitBodyQue(void)
916 {
917 int i;
918 edict_t *ent;
919
920 level.body_que = 0;
921 for (i = 0; i < BODY_QUEUE_SIZE; i++) {
922 ent = G_Spawn();
923 ent->classname = "bodyque";
924 }
925 }
926
927 void
body_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)928 body_die(edict_t * self, edict_t * inflictor, edict_t * attacker, int damage, vec3_t point)
929 {
930 int n;
931
932 if (self->health < -40) {
933 gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
934 for (n = 0; n < 4; n++)
935 ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
936 self->s.origin[2] -= 48;
937 ThrowClientHead(self, damage);
938 self->takedamage = DAMAGE_NO;
939 }
940 }
941
942 void
CopyToBodyQue(edict_t * ent)943 CopyToBodyQue(edict_t * ent)
944 {
945 edict_t *body;
946
947 /* grab a body que and cycle to the next one */
948 body = &g_edicts[(int)maxclients->value + level.body_que + 1];
949 level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;
950
951 /* FIXME: send an effect on the removed body */
952
953 gi.unlinkentity(ent);
954
955 gi.unlinkentity(body);
956 body->s = ent->s;
957 body->s.number = body - g_edicts;
958
959 body->svflags = ent->svflags;
960 VectorCopy(ent->mins, body->mins);
961 VectorCopy(ent->maxs, body->maxs);
962 VectorCopy(ent->absmin, body->absmin);
963 VectorCopy(ent->absmax, body->absmax);
964 VectorCopy(ent->size, body->size);
965 body->solid = ent->solid;
966 body->clipmask = ent->clipmask;
967 body->owner = ent->owner;
968 body->movetype = ent->movetype;
969
970 body->die = body_die;
971 body->takedamage = DAMAGE_YES;
972
973 gi.linkentity(body);
974 }
975
976
977 void
respawn(edict_t * self)978 respawn(edict_t * self)
979 {
980 if (deathmatch->value || coop->value) {
981 #ifdef WITH_ACEBOT
982 /* ACEBOT_ADD special respawning code */
983 if (self->is_bot) {
984 ACESP_Respawn(self);
985 return;
986 }
987 /* ACEBOT_END */
988 #endif
989 /* spectator's don't leave bodies */
990 if (self->movetype != MOVETYPE_NOCLIP)
991 CopyToBodyQue(self);
992 self->svflags &= ~SVF_NOCLIENT;
993 PutClientInServer(self);
994
995 /* add a teleportation effect */
996 self->s.event = EV_PLAYER_TELEPORT;
997
998 /* hold in place briefly */
999 self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
1000 self->client->ps.pmove.pm_time = 14;
1001
1002 self->client->respawn_time = level.time;
1003
1004 return;
1005 }
1006 /* restart the entire server */
1007 gi.AddCommandString("menu_loadgame\n");
1008 }
1009
1010 /*
1011 * only called when pers.spectator changes note that resp.spectator should be
1012 * the opposite of pers.spectator here
1013 */
1014 void
spectator_respawn(edict_t * ent)1015 spectator_respawn(edict_t * ent)
1016 {
1017 int i , numspec;
1018
1019 /* if the user wants to become a spectator, make sure he doesn't */
1020 /* exceed max_spectators */
1021
1022 if (ent->client->pers.spectator) {
1023 char *value = Info_ValueForKey(ent->client->pers.userinfo, "spectator");
1024
1025 if (*spectator_password->string &&
1026 strcmp(spectator_password->string, "none") &&
1027 strcmp(spectator_password->string, value)) {
1028 #ifdef WITH_ACEBOT
1029 safe_cprintf
1030 #else
1031 gi.cprintf
1032 #endif
1033 (ent, PRINT_HIGH, "Spectator password incorrect.\n");
1034 ent->client->pers.spectator = false;
1035 gi.WriteByte(svc_stufftext);
1036 gi.WriteString("spectator 0\n");
1037 gi.unicast(ent, true);
1038 return;
1039 }
1040 /* count spectators */
1041 for (i = 1, numspec = 0; i <= maxclients->value; i++)
1042 if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator)
1043 numspec++;
1044
1045 if (numspec >= maxspectators->value) {
1046 #ifdef WITH_ACEBOT
1047 safe_cprintf
1048 #else
1049 gi.cprintf
1050 #endif
1051 (ent, PRINT_HIGH, "Server spectator limit is full.");
1052 ent->client->pers.spectator = false;
1053 /* reset his spectator var */
1054 gi.WriteByte(svc_stufftext);
1055 gi.WriteString("spectator 0\n");
1056 gi.unicast(ent, true);
1057 return;
1058 }
1059 } else {
1060 /* he was a spectator and wants to join the game */
1061 /* he must have the right password */
1062 char *value = Info_ValueForKey(ent->client->pers.userinfo, "password");
1063
1064 if (*password->string && strcmp(password->string, "none") &&
1065 strcmp(password->string, value)) {
1066 #ifdef WITH_ACEBOT
1067 safe_cprintf
1068 #else
1069 gi.cprintf
1070 #endif
1071 (ent, PRINT_HIGH, "Password incorrect.\n");
1072 ent->client->pers.spectator = true;
1073 gi.WriteByte(svc_stufftext);
1074 gi.WriteString("spectator 1\n");
1075 gi.unicast(ent, true);
1076 return;
1077 }
1078 }
1079
1080 /* clear client on respawn */
1081 ent->client->resp.score = ent->client->pers.score = 0;
1082
1083 ent->svflags &= ~SVF_NOCLIENT;
1084 PutClientInServer(ent);
1085
1086 /* add a teleportation effect */
1087 if (!ent->client->pers.spectator) {
1088 /* send effect */
1089 gi.WriteByte(svc_muzzleflash);
1090 gi.WriteShort(ent - g_edicts);
1091 gi.WriteByte(MZ_LOGIN);
1092 gi.multicast(ent->s.origin, MULTICAST_PVS);
1093
1094 /* hold in place briefly */
1095 ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
1096 ent->client->ps.pmove.pm_time = 14;
1097 }
1098 ent->client->respawn_time = level.time;
1099
1100 if (ent->client->pers.spectator)
1101 #ifdef WITH_ACEBOT
1102 safe_bprintf(PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname);
1103 else
1104 safe_bprintf(PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname);
1105 #else
1106 gi.bprintf(PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname);
1107 else
1108 gi.bprintf(PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname);
1109 #endif
1110 }
1111
1112 /* ============================================================== */
1113
1114
1115 /*
1116 * =========== PutClientInServer
1117 *
1118 * Called when a player connects to a server or respawns in a deathmatch.
1119 * ============
1120 */
1121 void
PutClientInServer(edict_t * ent)1122 PutClientInServer(edict_t * ent)
1123 {
1124 vec3_t mins = {-16, -16, -24};
1125 vec3_t maxs = {16, 16, 32};
1126 int index;
1127 vec3_t spawn_origin, spawn_angles;
1128 gclient_t *client;
1129 int i;
1130 client_persistant_t saved;
1131 client_respawn_t resp;
1132
1133 /* find a spawn point */
1134 /* do it before setting health back up, so farthest */
1135 /* ranging doesn't count this client */
1136 SelectSpawnPoint(ent, spawn_origin, spawn_angles);
1137
1138 index = ent - g_edicts - 1;
1139 client = ent->client;
1140
1141 /* deathmatch wipes most client data every spawn */
1142 if (deathmatch->value) {
1143 char userinfo [MAX_INFO_STRING];
1144
1145 resp = client->resp;
1146 memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
1147 InitClientPersistant(client);
1148 ClientUserinfoChanged(ent, userinfo);
1149 } else if (coop->value) {
1150 /* int n; */
1151 char userinfo [MAX_INFO_STRING];
1152
1153 resp = client->resp;
1154 memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
1155
1156 /*
1157 * this is kind of ugly, but it's how we want to handle keys
1158 * in coop
1159 */
1160 /* for (n = 0; n < game.num_items; n++) */
1161 /* { */
1162 /* if (itemlist[n].flags & IT_KEY) */
1163
1164 /*
1165 * resp.coop_respawn.inventory[n] =
1166 * client->pers.inventory[n];
1167 */
1168 /* } */
1169 resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged;
1170 resp.coop_respawn.helpchanged = client->pers.helpchanged;
1171 client->pers = resp.coop_respawn;
1172 ClientUserinfoChanged(ent, userinfo);
1173 if (resp.score > client->pers.score)
1174 client->pers.score = resp.score;
1175 } else {
1176 memset(&resp, 0, sizeof(resp));
1177 }
1178
1179 /* clear everything but the persistant data */
1180 saved = client->pers;
1181 memset(client, 0, sizeof(*client));
1182 client->pers = saved;
1183 if (client->pers.health <= 0)
1184 InitClientPersistant(client);
1185 client->resp = resp;
1186
1187 /* copy some data from the client to the entity */
1188 FetchClientEntData(ent);
1189
1190 /* clear entity values */
1191 ent->groundentity = NULL;
1192 ent->client = &game.clients[index];
1193 ent->takedamage = DAMAGE_AIM;
1194 ent->movetype = MOVETYPE_WALK;
1195 ent->viewheight = 22;
1196 ent->inuse = true;
1197 ent->classname = "player";
1198 ent->mass = 200;
1199 ent->solid = SOLID_BBOX;
1200 ent->deadflag = DEAD_NO;
1201 ent->air_finished = level.time + 12;
1202 ent->clipmask = MASK_PLAYERSOLID;
1203 ent->model = "players/male/tris.md2";
1204 ent->pain = player_pain;
1205 ent->die = player_die;
1206 ent->waterlevel = 0;
1207 ent->watertype = 0;
1208 ent->flags &= ~FL_NO_KNOCKBACK;
1209 ent->svflags &= ~SVF_DEADMONSTER;
1210 #ifdef WITH_ACEBOT
1211 /* ACEBOT_ADD */
1212 ent->is_bot = false;
1213 ent->last_node = -1;
1214 ent->is_jumping = false;
1215 /* ACEBOT_END */
1216 #endif
1217
1218 VectorCopy(mins, ent->mins);
1219 VectorCopy(maxs, ent->maxs);
1220 VectorClear(ent->velocity);
1221
1222 /* clear playerstate values */
1223 memset(&ent->client->ps, 0, sizeof(client->ps));
1224
1225 client->ps.pmove.origin[0] = spawn_origin[0] * 8;
1226 client->ps.pmove.origin[1] = spawn_origin[1] * 8;
1227 client->ps.pmove.origin[2] = spawn_origin[2] * 8;
1228
1229 if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) {
1230 client->ps.fov = 90;
1231 } else {
1232 client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
1233 if (client->ps.fov < 1)
1234 client->ps.fov = 90;
1235 else if (client->ps.fov > 160)
1236 client->ps.fov = 160;
1237 }
1238
1239 client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
1240
1241 /* clear entity state values */
1242 ent->s.effects = 0;
1243 ent->s.modelindex = 255;/* will use the skin specified model */
1244 ent->s.modelindex2 = 255; /* custom gun model */
1245 /* sknum is player num and weapon number */
1246 /* weapon number will be added in changeweapon */
1247 ent->s.skinnum = ent - g_edicts - 1;
1248
1249 ent->s.frame = 0;
1250 #ifdef WITH_ACEBOT
1251 /* botchat> */
1252 ent->last_insult = level.time;
1253 ent->last_taunt = level.time;
1254 ent->last_chat = level.time;
1255 /* <botchat */
1256 #endif
1257 VectorCopy(spawn_origin, ent->s.origin);
1258 ent->s.origin[2] += 1; /* make sure off ground */
1259 VectorCopy(ent->s.origin, ent->s.old_origin);
1260
1261 /* set the delta angle */
1262 for (i = 0; i < 3; i++) {
1263 client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
1264 }
1265
1266 ent->s.angles[PITCH] = 0;
1267 ent->s.angles[YAW] = spawn_angles[YAW];
1268 ent->s.angles[ROLL] = 0;
1269 VectorCopy(ent->s.angles, client->ps.viewangles);
1270 VectorCopy(ent->s.angles, client->v_angle);
1271
1272 /* spawn a spectator */
1273 if (client->pers.spectator) {
1274 client->chase_target = NULL;
1275
1276 client->resp.spectator = true;
1277
1278 ent->movetype = MOVETYPE_NOCLIP;
1279 ent->solid = SOLID_NOT;
1280 ent->svflags |= SVF_NOCLIENT;
1281 ent->client->ps.gunindex = 0;
1282 gi.linkentity(ent);
1283 return;
1284 } else
1285 client->resp.spectator = false;
1286
1287 if (!KillBox(ent)) { /* could't spawn in? */
1288 }
1289 gi.linkentity(ent);
1290
1291 /* force the current weapon up */
1292 client->newweapon = client->pers.weapon;
1293 ChangeWeapon(ent);
1294 }
1295
1296 /*
1297 * ===================== ClientBeginDeathmatch
1298 *
1299 * A client has just connected to the server in deathmatch mode, so clear
1300 * everything out before starting them. =====================
1301 */
1302 void
ClientBeginDeathmatch(edict_t * ent)1303 ClientBeginDeathmatch(edict_t * ent)
1304 {
1305 #ifdef WITH_ACEBOT
1306 /* ACEBOT_ADD */
1307 static char current_map[55];
1308
1309 /* ACEBOT_END */
1310 #endif
1311 G_InitEdict(ent);
1312 #ifdef WITH_ACEBOT
1313 /* ACEBOT_ADD */
1314 ACEIT_PlayerAdded(ent);
1315 /* ACEBOT_END */
1316 #endif
1317
1318 InitClientResp(ent->client);
1319
1320 /* locate ent at a spawn point */
1321 PutClientInServer(ent);
1322
1323 if (level.intermissiontime) {
1324 MoveClientToIntermission(ent);
1325 } else {
1326 /* send effect */
1327 gi.WriteByte(svc_muzzleflash);
1328 gi.WriteShort(ent - g_edicts);
1329 gi.WriteByte(MZ_LOGIN);
1330 gi.multicast(ent->s.origin, MULTICAST_PVS);
1331 }
1332 #ifdef WITH_ACEBOT
1333 safe_bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
1334 safe_centerprintf(ent, "\nQ2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2\n\nQuake2 Ace Bots\n\n\n'sv addbot ' to add a new bot.\n\n'sv removebot <name>' to remove bot.\n\n\nhttp://qudos.quakedev.com\n\n\nQ2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2 Q2\n\n");
1335 /* If the map changes on us, init and reload the nodes */
1336 if (strcmp(level.mapname, current_map)) {
1337
1338 ACEND_InitNodes();
1339 ACEND_LoadNodes();
1340 if (botauto_respawn->value) {
1341 ACESP_LoadBots();
1342 }
1343 strcpy(current_map, level.mapname);
1344 }
1345 safe_bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
1346 /* ACEBOT_END */
1347 #else
1348 gi.bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
1349 #endif
1350
1351 /* make sure all view stuff is valid */
1352 ClientEndServerFrame(ent);
1353 }
1354
1355
1356 /*
1357 * =========== ClientBegin
1358 *
1359 * called when a client has finished connecting, and is ready to be placed into
1360 * the game. This will happen every level load. ============
1361 */
1362 void
ClientBegin(edict_t * ent)1363 ClientBegin(edict_t * ent)
1364 {
1365 int i;
1366
1367 ent->client = game.clients + (ent - g_edicts - 1);
1368
1369 if (deathmatch->value) {
1370 ClientBeginDeathmatch(ent);
1371 return;
1372 }
1373 /* if there is already a body waiting for us (a loadgame), just */
1374 /* take it, otherwise spawn one from scratch */
1375 if (ent->inuse == true) {
1376 /* the client has cleared the client side viewangles upon */
1377 /* connecting to the server, which is different than the */
1378 /* state when the game is saved, so we need to compensate */
1379 /* with deltaangles */
1380 for (i = 0; i < 3; i++)
1381 ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]);
1382 } else {
1383 /* a spawn point will completely reinitialize the entity */
1384 /* except for the persistant data that was initialized at */
1385 /* ClientConnect() time */
1386 G_InitEdict(ent);
1387 ent->classname = "player";
1388 InitClientResp(ent->client);
1389 PutClientInServer(ent);
1390 }
1391
1392 if (level.intermissiontime) {
1393 MoveClientToIntermission(ent);
1394 } else {
1395 /* send effect if in a multiplayer game */
1396 if (game.maxclients > 1) {
1397 gi.WriteByte(svc_muzzleflash);
1398 gi.WriteShort(ent - g_edicts);
1399 gi.WriteByte(MZ_LOGIN);
1400 gi.multicast(ent->s.origin, MULTICAST_PVS);
1401
1402 #ifdef WITH_ACEBOT
1403 safe_bprintf
1404 #else
1405 gi.bprintf
1406 #endif
1407 (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
1408 }
1409 }
1410
1411 /* make sure all view stuff is valid */
1412 ClientEndServerFrame(ent);
1413 }
1414
1415 /*
1416 * =========== ClientUserInfoChanged
1417 *
1418 * called whenever the player updates a userinfo variable.
1419 *
1420 * The game can override any of the settings in place (forcing skins or names,
1421 * etc) before copying it off. ============
1422 */
1423 void
ClientUserinfoChanged(edict_t * ent,char * userinfo)1424 ClientUserinfoChanged(edict_t * ent, char *userinfo)
1425 {
1426 char *s;
1427 int playernum;
1428
1429 /* check for malformed or illegal info strings */
1430 if (!Info_Validate(userinfo)) {
1431 strcpy(userinfo, "\\name\\badinfo\\skin\\male/grunt");
1432 }
1433 /* set name */
1434 s = Info_ValueForKey(userinfo, "name");
1435 strncpy(ent->client->pers.netname, s, sizeof(ent->client->pers.netname) - 1);
1436
1437 /* set spectator */
1438 s = Info_ValueForKey(userinfo, "spectator");
1439 /* spectators are only supported in deathmatch */
1440 if (deathmatch->value && *s && strcmp(s, "0"))
1441 ent->client->pers.spectator = true;
1442 else
1443 ent->client->pers.spectator = false;
1444
1445 /* set skin */
1446 s = Info_ValueForKey(userinfo, "skin");
1447
1448 playernum = ent - g_edicts - 1;
1449
1450 /* combine name and skin into a configstring */
1451 gi.configstring(CS_PLAYERSKINS + playernum, va("%s\\%s", ent->client->pers.netname, s));
1452
1453 /* fov */
1454 if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) {
1455 ent->client->ps.fov = 90;
1456 } else {
1457 ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
1458 if (ent->client->ps.fov < 1)
1459 ent->client->ps.fov = 90;
1460 else if (ent->client->ps.fov > 160)
1461 ent->client->ps.fov = 160;
1462 }
1463
1464 /* handedness */
1465 s = Info_ValueForKey(userinfo, "hand");
1466 if (strlen(s)) {
1467 ent->client->pers.hand = atoi(s);
1468 }
1469 /* save off the userinfo in case we want to check something later */
1470 strncpy(ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo) - 1);
1471 }
1472
1473
1474 /*
1475 * =========== ClientConnect
1476 *
1477 * Called when a player begins connecting to the server. The game can refuse
1478 * entrance to a client by returning false. If the client is allowed, the
1479 * connection process will continue and eventually get to ClientBegin()
1480 * Changing levels will NOT cause this to be called again, but loadgames
1481 * will. ============
1482 */
1483 qboolean
ClientConnect(edict_t * ent,char * userinfo)1484 ClientConnect(edict_t * ent, char *userinfo)
1485 {
1486 char *value;
1487
1488 /* check to see if they are on the banned IP list */
1489 value = Info_ValueForKey(userinfo, "ip");
1490 if (SV_FilterPacket(value)) {
1491 Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
1492 return false;
1493 }
1494 /* check for a spectator */
1495 value = Info_ValueForKey(userinfo, "spectator");
1496 if (deathmatch->value && *value && strcmp(value, "0")) {
1497 int i , numspec;
1498
1499 if (*spectator_password->string &&
1500 strcmp(spectator_password->string, "none") &&
1501 strcmp(spectator_password->string, value)) {
1502 Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect.");
1503 return false;
1504 }
1505 /* count spectators */
1506 for (i = numspec = 0; i < maxclients->value; i++)
1507 if (g_edicts[i + 1].inuse && g_edicts[i + 1].client->pers.spectator)
1508 numspec++;
1509
1510 if (numspec >= maxspectators->value) {
1511 Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full.");
1512 return false;
1513 }
1514 } else {
1515 /* check for a password */
1516 value = Info_ValueForKey(userinfo, "password");
1517 if (*password->string && strcmp(password->string, "none") &&
1518 strcmp(password->string, value)) {
1519 Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect.");
1520 return false;
1521 }
1522 }
1523
1524
1525 /* they can connect */
1526 ent->client = game.clients + (ent - g_edicts - 1);
1527
1528 /* if there is already a body waiting for us (a loadgame), just */
1529 /* take it, otherwise spawn one from scratch */
1530 if (ent->inuse == false) {
1531 /* clear the respawning variables */
1532 InitClientResp(ent->client);
1533 if (!game.autosaved || !ent->client->pers.weapon)
1534 InitClientPersistant(ent->client);
1535 }
1536 ClientUserinfoChanged(ent, userinfo);
1537
1538 if (game.maxclients > 1)
1539 gi.dprintf("%s connected\n", ent->client->pers.netname);
1540
1541 ent->svflags = 0; /* make sure we start with known default */
1542 ent->client->pers.connected = true;
1543 return true;
1544 }
1545
1546 /*
1547 * =========== ClientDisconnect
1548 *
1549 * Called when a player drops from the server. Will not be called between
1550 * levels. ============
1551 */
1552 void
ClientDisconnect(edict_t * ent)1553 ClientDisconnect(edict_t * ent)
1554 {
1555 int playernum;
1556
1557 if (!ent->client)
1558 return;
1559 #ifdef GAME_MOD
1560 Blinky_OnClientTerminate(ent);
1561 #endif
1562 #ifdef WITH_ACEBOT
1563 safe_bprintf
1564 #else
1565 gi.bprintf
1566 #endif
1567 (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
1568 #ifdef WITH_ACEBOT
1569 /* ACEBOT_ADD */
1570 ACEIT_PlayerRemoved(ent);
1571 /* ACEBOT_END */
1572 #endif
1573
1574 /* send effect */
1575 gi.WriteByte(svc_muzzleflash);
1576 gi.WriteShort(ent - g_edicts);
1577 gi.WriteByte(MZ_LOGOUT);
1578 gi.multicast(ent->s.origin, MULTICAST_PVS);
1579
1580 gi.unlinkentity(ent);
1581 ent->s.modelindex = 0;
1582 ent->solid = SOLID_NOT;
1583 ent->inuse = false;
1584 ent->classname = "disconnected";
1585 ent->client->pers.connected = false;
1586
1587 playernum = ent - g_edicts - 1;
1588 gi.configstring(CS_PLAYERSKINS + playernum, "");
1589 }
1590
1591
1592 /* ============================================================== */
1593
1594
1595 edict_t *pm_passent;
1596
1597 /* pmove doesn't need to know about passent and contentmask */
1598 trace_t
PM_trace(vec3_t start,vec3_t mins,vec3_t maxs,vec3_t end)1599 PM_trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
1600 {
1601 if (pm_passent->health > 0)
1602 return gi.trace(start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
1603 else
1604 return gi.trace(start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
1605 }
1606
1607 unsigned
CheckBlock(void * b,int c)1608 CheckBlock(void *b, int c)
1609 {
1610 int v , i;
1611
1612 v = 0;
1613 for (i = 0; i < c; i++)
1614 v += ((byte *) b)[i];
1615 return v;
1616 }
1617 void
PrintPmove(pmove_t * pm)1618 PrintPmove(pmove_t * pm)
1619 {
1620 unsigned c1 , c2;
1621
1622 c1 = CheckBlock(&pm->s, sizeof(pm->s));
1623 c2 = CheckBlock(&pm->cmd, sizeof(pm->cmd));
1624 Com_Printf("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2);
1625 }
1626
1627 /*
1628 * ============== ClientThink
1629 *
1630 * This will be called once for each client frame, which will usually be a
1631 * couple times for each server frame. ==============
1632 */
1633 void
ClientThink(edict_t * ent,usercmd_t * ucmd)1634 ClientThink(edict_t * ent, usercmd_t * ucmd)
1635 {
1636 gclient_t *client;
1637 edict_t *other;
1638 int i , j;
1639 pmove_t pm;
1640
1641 level.current_entity = ent;
1642 client = ent->client;
1643 #ifdef GAME_MOD
1644 Blinky_BeginClientThink(ent, ucmd);
1645 #endif
1646 if (level.intermissiontime) {
1647 client->ps.pmove.pm_type = PM_FREEZE;
1648 /* can exit intermission after five seconds */
1649 if (level.time > level.intermissiontime + 5.0
1650 && (ucmd->buttons & BUTTON_ANY))
1651 level.exitintermission = true;
1652 return;
1653 }
1654 pm_passent = ent;
1655
1656 if (ent->client->chase_target) {
1657
1658 client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
1659 client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
1660 client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
1661
1662 } else {
1663
1664 /* set up for pmove */
1665 memset(&pm, 0, sizeof(pm));
1666
1667 if (ent->movetype == MOVETYPE_NOCLIP)
1668 client->ps.pmove.pm_type = PM_SPECTATOR;
1669 else if (ent->s.modelindex != 255)
1670 client->ps.pmove.pm_type = PM_GIB;
1671 else if (ent->deadflag)
1672 client->ps.pmove.pm_type = PM_DEAD;
1673 else
1674 client->ps.pmove.pm_type = PM_NORMAL;
1675
1676 client->ps.pmove.gravity = sv_gravity->value;
1677 pm.s = client->ps.pmove;
1678
1679 for (i = 0; i < 3; i++) {
1680 pm.s.origin[i] = ent->s.origin[i] * 8;
1681 pm.s.velocity[i] = ent->velocity[i] * 8;
1682 }
1683
1684 if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) {
1685 pm.snapinitial = true;
1686 /* gi.dprintf ("pmove changed!\n"); */
1687 }
1688 pm.cmd = *ucmd;
1689
1690 pm.trace = PM_trace; /* adds default parms */
1691 pm.pointcontents = gi.pointcontents;
1692
1693 /* perform a pmove */
1694 gi.Pmove(&pm);
1695
1696 /* save results of pmove */
1697 client->ps.pmove = pm.s;
1698 client->old_pmove = pm.s;
1699
1700 for (i = 0; i < 3; i++) {
1701 ent->s.origin[i] = pm.s.origin[i] * 0.125;
1702 ent->velocity[i] = pm.s.velocity[i] * 0.125;
1703 }
1704
1705 VectorCopy(pm.mins, ent->mins);
1706 VectorCopy(pm.maxs, ent->maxs);
1707
1708 client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
1709 client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
1710 client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
1711
1712 if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) {
1713 gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
1714 PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
1715 }
1716 ent->viewheight = pm.viewheight;
1717 ent->waterlevel = pm.waterlevel;
1718 ent->watertype = pm.watertype;
1719 ent->groundentity = pm.groundentity;
1720 if (pm.groundentity)
1721 ent->groundentity_linkcount = pm.groundentity->linkcount;
1722
1723 if (ent->deadflag) {
1724 client->ps.viewangles[ROLL] = 40;
1725 client->ps.viewangles[PITCH] = -15;
1726 client->ps.viewangles[YAW] = client->killer_yaw;
1727 } else {
1728 VectorCopy(pm.viewangles, client->v_angle);
1729 VectorCopy(pm.viewangles, client->ps.viewangles);
1730 }
1731
1732 gi.linkentity(ent);
1733
1734 if (ent->movetype != MOVETYPE_NOCLIP)
1735 G_TouchTriggers(ent);
1736
1737 /* touch other objects */
1738 for (i = 0; i < pm.numtouch; i++) {
1739 other = pm.touchents[i];
1740 for (j = 0; j < i; j++)
1741 if (pm.touchents[j] == other)
1742 break;
1743 if (j != i)
1744 continue; /* duplicated */
1745 if (!other->touch)
1746 continue;
1747 other->touch(other, ent, NULL, NULL);
1748 }
1749
1750 }
1751
1752 client->oldbuttons = client->buttons;
1753 client->buttons = ucmd->buttons;
1754 client->latched_buttons |= client->buttons & ~client->oldbuttons;
1755
1756 /* save light level the player is standing on for */
1757 /* monster sighting AI */
1758 ent->light_level = ucmd->lightlevel;
1759
1760 /* fire weapon from final position if needed */
1761 if (client->latched_buttons & BUTTON_ATTACK) {
1762 if (client->resp.spectator) {
1763
1764 client->latched_buttons = 0;
1765
1766 if (client->chase_target) {
1767 client->chase_target = NULL;
1768 client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
1769 } else
1770 GetChaseTarget(ent);
1771
1772 } else if (!client->weapon_thunk) {
1773 client->weapon_thunk = true;
1774 Think_Weapon(ent);
1775 }
1776 }
1777 #ifdef WITH_ACEBOT
1778 /* ACEBOT_ADD */
1779 if (!ent->is_bot && !ent->deadflag && !ent->client->resp.spectator)
1780 ACEND_PathMap(ent);
1781 /* ACEBOT_END */
1782 #endif
1783
1784 if (client->resp.spectator) {
1785 if (ucmd->upmove >= 10) {
1786 if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)) {
1787 client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
1788 if (client->chase_target)
1789 ChaseNext(ent);
1790 else
1791 GetChaseTarget(ent);
1792 }
1793 } else
1794 client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
1795 }
1796 /* update chase cam if being followed */
1797 for (i = 1; i <= maxclients->value; i++) {
1798 other = g_edicts + i;
1799 if (other->inuse && other->client->chase_target == ent)
1800 UpdateChaseCam(other);
1801 }
1802 }
1803
1804
1805 /*
1806 * ============== ClientBeginServerFrame
1807 *
1808 * This will be called once for each server frame, before running any other
1809 * entities in the world. ==============
1810 */
1811 void
ClientBeginServerFrame(edict_t * ent)1812 ClientBeginServerFrame(edict_t * ent)
1813 {
1814 gclient_t *client;
1815 int buttonMask;
1816
1817 if (level.intermissiontime)
1818 return;
1819
1820 client = ent->client;
1821
1822 if (deathmatch->value &&
1823 client->pers.spectator != client->resp.spectator &&
1824 (level.time - client->respawn_time) >= 5) {
1825 spectator_respawn(ent);
1826 return;
1827 }
1828 /* run weapon animations if it hasn't been done by a ucmd_t */
1829 if (!client->weapon_thunk && !client->resp.spectator)
1830 Think_Weapon(ent);
1831 else
1832 client->weapon_thunk = false;
1833
1834 if (ent->deadflag) {
1835 /* wait for any button just going down */
1836 if (level.time > client->respawn_time) {
1837 /* in deathmatch, only wait for attack button */
1838 if (deathmatch->value)
1839 buttonMask = BUTTON_ATTACK;
1840 else
1841 buttonMask = -1;
1842
1843 if ((client->latched_buttons & buttonMask) ||
1844 (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN))) {
1845 respawn(ent);
1846 client->latched_buttons = 0;
1847 }
1848 }
1849 return;
1850 }
1851 /* add player trail so monsters can follow */
1852 if (!deathmatch->value)
1853 if (!visible(ent, PlayerTrail_LastSpot()))
1854 PlayerTrail_Add(ent->s.old_origin);
1855
1856 client->latched_buttons = 0;
1857 }
1858