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