1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 Copyright (C) 2010 COR Entertainment, LLC.
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
14 See the GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include "g_local.h"
26 #include "m_player.h"
27
28 /* Number of gibs to throw on death with lots of damage (including Client Head, where applicable) */
29 #define DEATH_GIBS_TO_THROW 5
30 void ClientUserinfoChanged (edict_t *ent, char *userinfo, int whereFrom);
31 void ClientDisconnect (edict_t *ent);
32 void SP_misc_teleporter_dest (edict_t *ent);
33
34 /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
35 The normal starting point for a level.
36 */
SP_info_player_start(edict_t * self)37 void SP_info_player_start(edict_t *self)
38 {
39 return;
40 }
41
42 /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32)
43 potential spawning position for deathmatch games
44 */
SP_info_player_deathmatch(edict_t * self)45 void SP_info_player_deathmatch(edict_t *self)
46 {
47 if (!deathmatch->value)
48 {
49 G_FreeEdict (self);
50 return;
51 }
52 //SP_misc_teleporter_dest (self);
53 }
SP_info_player_red(edict_t * self)54 void SP_info_player_red(edict_t *self)
55 {
56 if (!deathmatch->value)
57 {
58 G_FreeEdict (self);
59 return;
60 }
61 //SP_misc_teleporter_dest (self);
62 }
SP_info_player_blue(edict_t * self)63 void SP_info_player_blue(edict_t *self)
64 {
65 if (!deathmatch->value)
66 {
67 G_FreeEdict (self);
68 return;
69 }
70 //SP_misc_teleporter_dest (self);
71 }
72
73 /*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
74 The deathmatch intermission point will be at one of these
75 Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll'
76 */
SP_info_player_intermission(edict_t * ent)77 void SP_info_player_intermission(edict_t *ent)
78 {
79 }
80
81
82 //=======================================================================
83
84
player_pain(edict_t * self,edict_t * other,float kick,int damage)85 void player_pain (edict_t *self, edict_t *other, float kick, int damage)
86 {
87 // player pain is handled at the end of the frame in P_DamageFeedback
88 if(self->is_bot)
89 self->oldenemy = other;
90 }
91
92
IsFemale(edict_t * ent)93 qboolean IsFemale (edict_t *ent)
94 {
95 char *info;
96
97 if (!ent->client)
98 return false;
99
100 info = Info_ValueForKey (ent->client->pers.userinfo, "skin");
101 if (info[0] == 'f' || info[0] == 'F')
102 return true;
103 return false;
104 }
105
106
ClientObituary(edict_t * self,edict_t * inflictor,edict_t * attacker)107 void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker)
108 {
109 int mod=0, msg;
110 char *message;
111 char *message2;
112 qboolean ff;
113 char *chatmsg;
114 char *tauntmsg;
115 char cleanname[PLAYERNAME_SIZE];
116 char cleanname2[PLAYERNAME_SIZE];
117 int i, pos, total, place;
118 edict_t *cl_ent;
119 gitem_t *it;
120
121 if (deathmatch->value)
122 {
123 ff = meansOfDeath & MOD_FRIENDLY_FIRE;
124 mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
125 message = NULL;
126 chatmsg = NULL;
127 tauntmsg = NULL;
128 message2 = "";
129
130 switch (mod)
131 {
132 case MOD_SUICIDE:
133 message = "suicides";
134 break;
135 case MOD_FALLING:
136 message = "cratered";
137 break;
138 case MOD_CRUSH:
139 message = "was squished";
140 break;
141 case MOD_WATER:
142 message = "sank like a rock";
143 break;
144 case MOD_SLIME:
145 message = "melted";
146 break;
147 case MOD_LAVA:
148 message = "does a back flip into the lava";
149 break;
150 case MOD_EXPLOSIVE:
151 case MOD_BARREL:
152 message = "blew up";
153 break;
154 case MOD_EXIT:
155 message = "found a way out";
156 break;
157 case MOD_TARGET_LASER:
158 message = "saw the light";
159 break;
160 case MOD_TARGET_BLASTER:
161 message = "got blasted";
162 break;
163 case MOD_BOMB:
164 case MOD_SPLASH:
165 case MOD_TRIGGER_HURT:
166 message = "was in the wrong place";
167 break;
168 }
169 if (attacker == self)
170 {
171 switch (mod)
172 {
173 case MOD_CAMPING:
174 message = "was killed for camping";
175 break;
176 case MOD_PLASMA_SPLASH:
177 if (IsFemale(self))
178 message = "melted herself";
179 else
180 message = "melted himself";
181 break;
182 case MOD_R_SPLASH:
183 if (IsFemale(self))
184 message = "blew herself up";
185 else
186 message = "blew himself up";
187 break;
188 case MOD_VAPORIZER:
189 message = "should have used a smaller gun";
190 break;
191 default:
192 if (IsFemale(self))
193 message = "killed herself";
194 else
195 message = "killed himself";
196 break;
197 }
198 }
199 if (message)
200 {
201 safe_bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message);
202 if (deathmatch->value) {
203 self->client->resp.score--;
204 self->client->resp.deaths++;
205 }
206 self->enemy = NULL;
207 self->client->kill_streak = 0; //reset, you are dead
208 return;
209 }
210
211 self->enemy = attacker;
212 if (attacker && attacker->client)
213 {
214 //clean up names, get rid of escape chars
215 G_CleanPlayerName ( self->client->pers.netname , cleanname );
216 G_CleanPlayerName ( attacker->client->pers.netname , cleanname2 );
217
218 if(!attacker->is_bot) {
219 pos = 0;
220 total = 0;
221 for (i=0 ; i<game.maxclients ; i++)
222 {
223 cl_ent = g_edicts + 1 + i;
224 if (!cl_ent->inuse || game.clients[i].resp.spectator)
225 continue;
226
227 if(attacker->client->resp.score+1 >= game.clients[i].resp.score)
228 pos++;
229
230 total++;
231 }
232 place = total - pos;
233 if(place < 3) {
234 switch(place) {
235 case 0:
236 safe_centerprintf(attacker, "You fragged %s\n1st place with %i frags\n", cleanname, attacker->client->resp.score+1);
237 break;
238 case 1:
239 safe_centerprintf(attacker, "You fragged %s\n2nd place with %i frags\n", cleanname, attacker->client->resp.score+1);
240 break;
241 case 2:
242 safe_centerprintf(attacker, "You fragged %s\n3rd place with %i frags\n", cleanname, attacker->client->resp.score+1);
243 break;
244 default:
245 break;
246 }
247 }
248 else
249 safe_centerprintf(attacker, "You fragged %s\n", cleanname);
250
251 }
252
253 switch (mod)
254 {
255 case MOD_BLASTER:
256 message = "was blasted by";
257 break;
258 case MOD_VIOLATOR:
259 message = "was probed by";
260 break;
261 case MOD_CGALTFIRE:
262 message = "was blown away by";
263 message2 = "'s chaingun burst";
264 break;
265 case MOD_CHAINGUN:
266 message = "was cut in half by";
267 message2 = "'s chaingun";
268 break;
269 case MOD_FLAME:
270 message = "was burned by";
271 message2 = "'s napalm";
272 break;
273 case MOD_ROCKET:
274 message = "ate";
275 message2 = "'s rocket";
276 break;
277 case MOD_R_SPLASH:
278 message = "almost dodged";
279 message2 = "'s rocket";
280 break;
281 case MOD_BEAMGUN:
282 message = "was melted by";
283 message2 = "'s beamgun";
284 break;
285 case MOD_DISRUPTOR:
286 message = "was disrupted by";
287 break;
288 case MOD_SMARTGUN:
289 message = "saw the pretty lights from";
290 message2 = "'s smartgun";
291 break;
292 case MOD_VAPORIZER:
293 message = "was disintegrated by";
294 message2 = "'s vaporizer blast";
295 break;
296 case MOD_VAPORALTFIRE:
297 message = "couldn't hide from";
298 message2 = "'s vaporizer";
299 break;
300 case MOD_MINDERASER:
301 message = "had it's mind erased by";
302 message2 = "'s alien seeker";
303 break;
304 case MOD_PLASMA_SPLASH: //blaster splash damage
305 message = "was melted";
306 message2 = "'s plasma";
307 break;
308 case MOD_TELEFRAG:
309 message = "tried to invade";
310 message2 = "'s personal space";
311 break;
312 case MOD_GRAPPLE:
313 message = "was caught by";
314 message2 = "'s grapple";
315 break;
316 case MOD_HEADSHOT:
317 message = "had its head blown off by";
318 }
319 //here is where the bot chat features will be added.
320 //default is on. Setting to 1 turns it off.
321
322 if ( !(dmflags->integer & DF_BOTCHAT) && self->is_bot)
323 {
324 msg = random() * 9;
325 switch(msg){
326 case 1:
327 chatmsg = self->chatmsg1;
328 break;
329 case 2:
330 chatmsg = self->chatmsg2;
331 break;
332 case 3:
333 chatmsg = self->chatmsg3;
334 break;
335 case 4:
336 chatmsg = self->chatmsg4;
337 break;
338 case 5:
339 chatmsg = self->chatmsg5;
340 break;
341 case 6:
342 chatmsg = self->chatmsg6;
343 break;
344 case 7:
345 chatmsg = self->chatmsg7;
346 break;
347 case 8:
348 chatmsg = self->chatmsg8;
349 break;
350 default:
351 chatmsg = "%s: Stop it %s, you punk!";
352 break;
353 }
354 if(chatmsg) {
355 safe_bprintf (PRINT_CHAT, chatmsg, self->client->pers.netname, attacker->client->pers.netname);
356 safe_bprintf (PRINT_CHAT, "\n");
357
358 gi.WriteByte (svc_temp_entity);
359 gi.WriteByte (TE_SAYICON);
360 gi.WritePosition (self->s.origin);
361 gi.multicast (self->s.origin, MULTICAST_PVS);
362 }
363 }
364
365 //bot taunts
366 if(!(dmflags->integer & DF_BOTCHAT) && attacker->is_bot) {
367
368 if(!(attacker->client->ps.pmove.pm_flags & PMF_DUCKED)) {
369 attacker->state = STATE_STAND;
370 attacker->s.frame = FRAME_taunt01-1;
371 attacker->client->anim_end = FRAME_taunt17;
372
373 //print a taunt, or send taunt sound
374 msg = random() * 24;
375 switch(msg){
376 case 1:
377 tauntmsg = "%s: You should have used a bigger gun %s.\n";
378 break;
379 case 2:
380 tauntmsg = "%s: You fight like your mom %s.\n";
381 break;
382 case 3:
383 tauntmsg = "%s: And stay down %s!\n";
384 break;
385 case 4:
386 tauntmsg = "%s: %s = pwned!\n";
387 break;
388 case 5:
389 tauntmsg = "%s: All too easy, %s, all too easy.\n";
390 break;
391 case 6:
392 tauntmsg = "%s: Ack! %s Ack! Ack!\n";
393 break;
394 case 7:
395 tauntmsg = "%s: What a loser you are %s!\n";
396 break;
397 case 8:
398 tauntmsg = "%s: %s, could you BE any more dead?\n";
399 break;
400 case 9:
401 case 10:
402 case 11:
403 case 12:
404 case 13:
405 case 14:
406 case 15:
407 case 16:
408 case 17:
409 case 18:
410 case 19:
411 case 20:
412 case 21:
413 case 22:
414 case 23:
415 case 24:
416 Cmd_VoiceTaunt_f(attacker);
417 break;
418 default:
419 tauntmsg = "%s: You are useless to me, %s\n";
420 break;
421 }
422 if(tauntmsg) {
423 safe_bprintf (PRINT_CHAT, tauntmsg, attacker->client->pers.netname, self->client->pers.netname);
424 //send an effect to show that the bot is taunting
425 gi.WriteByte (svc_temp_entity);
426 gi.WriteByte (TE_SAYICON);
427 gi.WritePosition (attacker->s.origin);
428 gi.multicast (attacker->s.origin, MULTICAST_PVS);
429 }
430 }
431 }
432
433 if (message)
434 {
435 safe_bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
436
437 if (deathmatch->value)
438 {
439 if(mod == MOD_MINDERASER)
440 {
441 self->client->resp.reward_pts = 0;
442 self->client->resp.powered = false;
443 }
444
445 if (ff)
446 {
447 attacker->client->resp.score--;
448 attacker->client->resp.deaths++;
449 if ((dmflags->integer & DF_SKINTEAMS) && !ctf->value) {
450 if(attacker->dmteam == RED_TEAM)
451 red_team_score--;
452 else
453 blue_team_score--;
454 }
455 }
456 else
457 {
458 attacker->client->resp.score++;
459
460 if(!self->groundentity)
461 {
462 attacker->client->resp.reward_pts+=3;
463 safe_centerprintf(attacker, "Midair shot!\n");
464 }
465 else
466 attacker->client->resp.reward_pts++;
467
468 if(mod == MOD_HEADSHOT)
469 {
470 //3 more pts for a headshot
471 attacker->client->resp.reward_pts+=3;
472 safe_centerprintf(attacker, "HEADSHOT!\n");
473 gi.sound(attacker, CHAN_AUTO, gi.soundindex("misc/headshot.wav"), 1, ATTN_STATIC, 0);
474 }
475
476 //mutators
477 if(vampire->value)
478 {
479 attacker->health+=20;
480 if(attacker->health > attacker->max_health)
481 attacker->health = attacker->max_health;
482 }
483 self->client->resp.deaths++;
484
485 if ((dmflags->integer & DF_SKINTEAMS) && !ctf->value)
486 {
487 if(attacker->dmteam == RED_TEAM)
488 {
489 red_team_score++;
490 safe_bprintf(PRINT_MEDIUM, "Red Team scores!\n");
491 gi.sound (self, CHAN_AUTO, gi.soundindex("misc/red_scores.wav"), 1, ATTN_NONE, 0);
492 }
493 else
494 {
495 blue_team_score++;
496 safe_bprintf(PRINT_MEDIUM, "Blue Team scores!\n");
497 gi.sound (self, CHAN_AUTO, gi.soundindex("misc/blue_scores.wav"), 1, ATTN_NONE, 0);
498
499 }
500 }
501 //kill streaks
502 attacker->client->kill_streak++;
503 switch(attacker->client->kill_streak)
504 {
505 case 3:
506 for (i=0 ; i<g_maxclients->value ; i++)
507 {
508 cl_ent = g_edicts + 1 + i;
509 if (!cl_ent->inuse || cl_ent->is_bot)
510 continue;
511 safe_centerprintf(cl_ent, "%s is on a killing spree!\n", cleanname2);
512 }
513 break;
514 case 5:
515 for (i=0 ; i<g_maxclients->value ; i++)
516 {
517 cl_ent = g_edicts + 1 + i;
518 if (!cl_ent->inuse || cl_ent->is_bot)
519 continue;
520 safe_centerprintf(cl_ent, "%s is on a rampage!\n", cleanname2);
521 }
522 gi.sound (self, CHAN_AUTO, gi.soundindex("misc/rampage.wav"), 1, ATTN_NONE, 0);
523 attacker->client->resp.reward_pts+=10;
524 break;
525 case 8:
526 for (i=0 ; i<g_maxclients->value ; i++)
527 {
528 cl_ent = g_edicts + 1 + i;
529 if (!cl_ent->inuse || cl_ent->is_bot)
530 continue;
531 safe_centerprintf(cl_ent, "%s is unstoppable!\n", cleanname2);
532 }
533 break;
534 case 10:
535 for (i=0 ; i<g_maxclients->value ; i++)
536 {
537 cl_ent = g_edicts + 1 + i;
538 if (!cl_ent->inuse || cl_ent->is_bot)
539 continue;
540 safe_centerprintf(cl_ent, "%s is a god!\n", cleanname2);
541 }
542 gi.sound (self, CHAN_AUTO, gi.soundindex("misc/godlike.wav"), 1, ATTN_NONE, 0);
543 attacker->client->resp.reward_pts+=20;
544 break;
545 default:
546 break;
547 }
548 if(self->client->kill_streak >=3)
549 {
550 for (i=0 ; i<g_maxclients->value ; i++)
551 {
552 cl_ent = g_edicts + 1 + i;
553 if (!cl_ent->inuse || cl_ent->is_bot)
554 continue;
555 safe_centerprintf(cl_ent, "%s's killing spree\nended by %s!\n", cleanname, cleanname2);
556 }
557 }
558 }
559
560 }
561 if(attacker->client->resp.reward_pts >= g_reward->integer && !attacker->client->resp.powered)
562 {
563 //give them speed and invis powerups
564 it = FindItem("Invisibility");
565 attacker->client->pers.inventory[ITEM_INDEX(it)] += 1;
566
567 it = FindItem("Sproing");
568 attacker->client->pers.inventory[ITEM_INDEX(it)] += 1;
569
570 it = FindItem("Haste");
571 attacker->client->pers.inventory[ITEM_INDEX(it)] += 1;
572
573 attacker->client->resp.powered = true;
574
575 gi.sound (attacker, CHAN_AUTO, gi.soundindex("misc/pc_up.wav"), 1, ATTN_STATIC, 0);
576 }
577 self->client->kill_streak = 0; //reset, you are dead
578 return;
579 }
580 }
581
582 }
583
584 if(mod == MOD_DEATHRAY)
585 {
586 safe_bprintf(PRINT_MEDIUM, "%s killed by Deathray!\n", self->client->pers.netname);
587
588 //immune player (activator) gets score increase
589 for (i=0 ; i<g_maxclients->value ; i++)
590 {
591 cl_ent = g_edicts + 1 + i;
592 if (!cl_ent->inuse || cl_ent->is_bot)
593 continue;
594 if(cl_ent->client)
595 if(cl_ent->client->rayImmunity)
596 cl_ent->client->resp.score++;
597 }
598 return;
599 }
600
601 if(mod == MOD_SPIDER)
602 {
603 safe_bprintf(PRINT_MEDIUM, "%s killed by Spiderbot!\n", self->client->pers.netname);
604
605 self->client->resp.reward_pts = 0;
606 self->client->resp.powered = false;
607
608 if(inflictor->owner->client)
609 inflictor->owner->client->resp.score++;
610 return;
611 }
612
613 safe_bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname);
614 if (deathmatch->value)
615 {
616 self->client->resp.score--;
617 self->client->resp.deaths++;
618 }
619
620 }
621
622
623 void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
624
TossClientWeapon(edict_t * self)625 void TossClientWeapon (edict_t *self)
626 {
627 gitem_t *item;
628 edict_t *drop;
629 qboolean quad;
630 qboolean sproing;
631 qboolean haste;
632 float spread;
633
634 if ((!deathmatch->value) || instagib->integer || rocket_arena->integer || insta_rockets->value)
635 {
636 return;
637 }
638
639 item = self->client->pers.weapon;
640 if (!self->client->pers.inventory[self->client->ammo_index] )
641 item = NULL;
642
643 #ifdef ALTERIA
644 if (item && (strcmp (item->pickup_name, "Warriorpunch") == 0))
645 item = NULL;
646 if (item && (strcmp (item->pickup_name, "Wizardpunch") == 0))
647 item = NULL;
648 #else
649 if (item && (strcmp (item->pickup_name, "Blaster") == 0))
650 item = NULL;
651 if (item && (strcmp (item->pickup_name, "Alien Blaster") == 0))
652 item = NULL;
653 if (item && (strcmp (item->pickup_name, "Violator") == 0))
654 item = NULL;
655 if (item && (strcmp (item->pickup_name, "Minderaser") == 0))
656 item = NULL;
657 if (g_tactical->integer && item && (strcmp (item->pickup_name, "Alien Vaporizer") == 0))
658 item = NULL;
659 #endif
660
661 if(g_tactical->integer)
662 {
663 //always drop your weapon on death, even if it isn't the current held item.
664 if(self->client->pers.inventory[ITEM_INDEX(FindItem("Flame Thrower"))] == 1)
665 {
666 item = FindItem( "Flame Thrower" );
667 if(item)
668 {
669 spread = 0;
670 self->client->v_angle[YAW] -= spread;
671 drop = Drop_Item (self, item);
672 self->client->v_angle[YAW] += spread;
673 drop->spawnflags = DROPPED_PLAYER_ITEM;
674 }
675 }
676 if(self->client->pers.inventory[ITEM_INDEX(FindItem("Rocket Launcher"))] == 1)
677 {
678 item = FindItem( "Rocket Launcher" );
679 if(item)
680 {
681 spread = 35;
682 self->client->v_angle[YAW] -= spread;
683 drop = Drop_Item (self, item);
684 self->client->v_angle[YAW] += spread;
685 drop->spawnflags = DROPPED_PLAYER_ITEM;
686 }
687 }
688 if(self->client->pers.inventory[ITEM_INDEX(FindItem("Pulse Rifle"))] == 1)
689 {
690 item = FindItem( "Pulse Rifle" );
691 if(item)
692 {
693 spread = 70;
694 self->client->v_angle[YAW] -= spread;
695 drop = Drop_Item (self, item);
696 self->client->v_angle[YAW] += spread;
697 drop->spawnflags = DROPPED_PLAYER_ITEM;
698 }
699 }
700 if(self->client->pers.inventory[ITEM_INDEX(FindItem("Disruptor"))] == 1)
701 {
702 item = FindItem( "Disruptor" );
703 if(item)
704 {
705 spread = 105;
706 self->client->v_angle[YAW] -= spread;
707 drop = Drop_Item (self, item);
708 self->client->v_angle[YAW] += spread;
709 drop->spawnflags = DROPPED_PLAYER_ITEM;
710 }
711 }
712 if(self->client->pers.inventory[ITEM_INDEX(FindItem("Alien Disruptor"))] == 1)
713 {
714 item = FindItem( "Alien Disruptor" );
715 if(item)
716 {
717 spread = 140;
718 self->client->v_angle[YAW] -= spread;
719 drop = Drop_Item (self, item);
720 self->client->v_angle[YAW] += spread;
721 drop->spawnflags = DROPPED_PLAYER_ITEM;
722 }
723 }
724 if(self->client->pers.inventory[ITEM_INDEX(FindItem("Alien Smartgun"))] == 1)
725 {
726 item = FindItem( "Alien Smartgun" );
727 if(item)
728 {
729 spread = 175;
730 self->client->v_angle[YAW] -= spread;
731 drop = Drop_Item (self, item);
732 self->client->v_angle[YAW] += spread;
733 drop->spawnflags = DROPPED_PLAYER_ITEM;
734 }
735 }
736 }
737 else
738 {
739 if (!(dmflags->integer & DF_QUAD_DROP))
740 quad = false;
741 else
742 quad = (self->client->quad_framenum > (level.framenum + 10));
743
744 sproing = (self->client->sproing_framenum > (level.framenum + 10));
745 haste = (self->client->haste_framenum > (level.framenum + 10));
746
747 if ((item && quad) || (item && haste) || (item && sproing))
748 spread = 22.5;
749 else
750 spread = 0.0;
751
752 if (item)
753 {
754 self->client->v_angle[YAW] -= spread;
755 drop = Drop_Item (self, item);
756 self->client->v_angle[YAW] += spread;
757 drop->spawnflags = DROPPED_PLAYER_ITEM;
758 }
759
760 if (quad)
761 {
762 self->client->v_angle[YAW] += spread;
763 drop = Drop_Item (self, FindItemByClassname ("item_quad"));
764 self->client->v_angle[YAW] -= spread;
765 drop->spawnflags |= DROPPED_PLAYER_ITEM;
766
767 drop->touch = Touch_Item;
768 drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME;
769 drop->think = G_FreeEdict;
770 }
771 if (sproing && !self->client->resp.powered)
772 {
773 self->client->v_angle[YAW] += spread;
774 drop = Drop_Item (self, FindItemByClassname ("item_sproing"));
775 self->client->v_angle[YAW] -= spread;
776 drop->spawnflags |= DROPPED_PLAYER_ITEM;
777
778 drop->touch = Touch_Item;
779 drop->nextthink = level.time + (self->client->sproing_framenum - level.framenum) * FRAMETIME;
780 drop->think = G_FreeEdict;
781 }
782 if (haste && !self->client->resp.powered)
783 {
784 self->client->v_angle[YAW] += spread;
785 drop = Drop_Item (self, FindItemByClassname ("item_haste"));
786 self->client->v_angle[YAW] -= spread;
787 drop->spawnflags |= DROPPED_PLAYER_ITEM;
788
789 drop->touch = Touch_Item;
790 drop->nextthink = level.time + (self->client->haste_framenum - level.framenum) * FRAMETIME;
791 drop->think = G_FreeEdict;
792 }
793 }
794 }
795
796
797 /*
798 ==================
799 LookAtKiller
800 ==================
801 */
LookAtKiller(edict_t * self,edict_t * inflictor,edict_t * attacker)802 void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker)
803 {
804 vec3_t dir;
805
806 if (attacker && attacker != world && attacker != self)
807 {
808 VectorSubtract (attacker->s.origin, self->s.origin, dir);
809 }
810 else if (inflictor && inflictor != world && inflictor != self)
811 {
812 VectorSubtract (inflictor->s.origin, self->s.origin, dir);
813 }
814 else
815 {
816 self->client->killer_yaw = self->s.angles[YAW];
817 return;
818 }
819
820 self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]);
821 }
822
823 /*
824 ==================
825 player_die
826 ==================
827 */
player_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)828 void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
829 {
830 int n;
831 char *info;
832 int got_vehicle = 0;
833 int number_of_gibs = 0;
834 int gib_effect = EF_GREENGIB;
835 int hasFlag = false;
836 gitem_t *it, *flag1_item, *flag2_item;
837 int mod;
838
839 mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
840
841 if (self->in_vehicle) {
842 Reset_player(self); //get the player out of the vehicle
843 Jet_Explosion(self); //blow that bitch up!
844 got_vehicle = 1; //so we know how to handle dropping it
845 }
846
847 VectorClear (self->avelocity);
848
849 self->takedamage = DAMAGE_YES;
850 self->movetype = MOVETYPE_TOSS;
851
852 info = Info_ValueForKey (self->client->pers.userinfo, "skin");
853
854 self->s.modelindex2 = 0; // remove linked weapon model
855
856 if(ctf->value)
857 self->s.modelindex4 = 0; // remove linked ctf flag
858
859 self->s.angles[0] = 0;
860 self->s.angles[2] = 0;
861
862 self->s.sound = 0;
863 self->client->weapon_sound = 0;
864
865 self->maxs[2] = -8;
866
867 self->svflags |= SVF_DEADMONSTER;
868
869 if (!self->deadflag)
870 {
871 self->client->respawn_time = level.time + 3.8;
872
873 //go into 3rd person view
874 if (deathmatch->value)
875 if(!self->is_bot)
876 DeathcamStart(self);
877
878 self->client->ps.pmove.pm_type = PM_DEAD;
879 ClientObituary (self, inflictor, attacker);
880 if(got_vehicle) //special for vehicles
881 VehicleDeadDrop(self);
882 else
883 {
884 if(!excessive->value)
885 TossClientWeapon (self);
886 }
887
888 if(ctf->value)
889 {
890 //check to see if they had a flag
891 flag1_item = flag2_item = NULL;
892
893 flag1_item = FindItemByClassname("item_flag_red");
894 flag2_item = FindItemByClassname("item_flag_blue");
895
896 if (self->client->pers.inventory[ITEM_INDEX(flag1_item)] || self->client->pers.inventory[ITEM_INDEX(flag1_item)])
897 hasFlag = true;
898
899 CTFDeadDropFlag(self, attacker);
900 if(anticamp->value && meansOfDeath == MOD_SUICIDE && hasFlag)
901 {
902 //make campers really pay for hiding flags
903 if(self->dmteam == BLUE_TEAM)
904 CTFResetFlag(RED_TEAM);
905 else
906 CTFResetFlag(BLUE_TEAM);
907 }
908 }
909 if(self->in_deathball)
910 DeadDropDeathball(self);
911
912 CTFPlayerResetGrapple(self);
913
914 if (deathmatch->integer && !self->is_bot)
915 {
916 ACESP_UpdateBots();
917 self->client->showscores = false; // override toggle
918 Cmd_Score_f( self );
919 }
920
921 if(attacker)
922 {
923 if(self->health < -40 && attacker->client)
924 {
925 attacker->client->resp.reward_pts++;
926 if(attacker->client->resp.reward_pts >= g_reward->integer && !attacker->client->resp.powered)
927 { //give them speed and invis powerups
928 it = FindItem("Invisibility");
929 attacker->client->pers.inventory[ITEM_INDEX(it)] += 1;
930
931 it = FindItem("Sproing");
932 attacker->client->pers.inventory[ITEM_INDEX(it)] += 1;
933
934 it = FindItem("Haste");
935 attacker->client->pers.inventory[ITEM_INDEX(it)] += 1;
936
937 attacker->client->resp.powered = true;
938
939 gi.sound (attacker, CHAN_VOICE, gi.soundindex("misc/pc_up.wav"), 1, ATTN_STATIC, 0);
940 }
941 }
942 }
943 }
944
945 // remove powerups
946 self->client->quad_framenum = 0;
947 self->client->invincible_framenum = 0;
948 self->client->haste_framenum = 0;
949 self->client->sproing_framenum = 0;
950 self->client->invis_framenum = 0;
951
952 // clear inventory
953 memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory));
954
955 if (self->health < -40)
956 { // gib
957 self->takedamage = DAMAGE_NO;
958 self->s.modelindex3 = 0; //remove helmet, if a martian
959
960 if(self->client->chasetoggle == 1)
961 {
962 /* If deathcam is active, switch client model to nothing */
963 self->s.modelindex = 0;
964 self->s.effects = 0;
965 self->solid = SOLID_NOT;
966
967 number_of_gibs = DEATH_GIBS_TO_THROW;
968 }
969 else
970 {
971 /* No deathcam, handle player's view and model with ThrowClientHead() */
972 ThrowClientHead (self, damage);
973 number_of_gibs = DEATH_GIBS_TO_THROW - 1;
974 }
975
976 if(self->ctype == 0)
977 { //alien
978 gi.WriteByte (svc_temp_entity);
979 gi.WriteByte (TE_DEATHFIELD);
980 gi.WritePosition (self->s.origin);
981 gi.multicast (self->s.origin, MULTICAST_PVS);
982
983 for (n= 0; n < number_of_gibs; n++) {
984 if(mod == MOD_R_SPLASH || mod == MOD_ROCKET)
985 ThrowGib (self, "models/objects/gibs/mart_gut/tris.md2", damage, GIB_METALLIC, EF_SHIPEXHAUST);
986 else
987 ThrowGib (self, "models/objects/gibs/mart_gut/tris.md2", damage, GIB_METALLIC, EF_GREENGIB);
988 ThrowGib (self, "models/objects/debris2/tris.md2", damage, GIB_METALLIC, 0);
989 }
990 }
991 else if(self->ctype == 2)
992 { //robot
993 gib_effect = 0;
994 for (n= 0; n < number_of_gibs; n++)
995 {
996 ThrowGib (self, "models/objects/debris3/tris.md2", damage, GIB_METALLIC, 0);
997 ThrowGib (self, "models/objects/debris1/tris.md2", damage, GIB_METALLIC, 0);
998 }
999 //blow up too :)
1000 gi.WriteByte (svc_temp_entity);
1001 gi.WriteByte (TE_ROCKET_EXPLOSION);
1002 gi.WritePosition (self->s.origin);
1003 gi.multicast (self->s.origin, MULTICAST_PHS);
1004 }
1005 else
1006 { //human
1007 gi.WriteByte (svc_temp_entity);
1008 gi.WriteByte (TE_DEATHFIELD2);
1009 gi.WritePosition (self->s.origin);
1010 gi.WriteDir (self->s.angles);
1011 gi.multicast (self->s.origin, MULTICAST_PVS);
1012
1013 gib_effect = EF_GIB;
1014 for (n= 0; n < number_of_gibs; n++)
1015 {
1016 if(mod == MOD_R_SPLASH || mod == MOD_ROCKET)
1017 ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_METALLIC, EF_SHIPEXHAUST);
1018 else
1019 ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_METALLIC, EF_GIB);
1020 }
1021 }
1022
1023 if(self->usegibs)
1024 {
1025 if(mod == MOD_R_SPLASH || mod == MOD_ROCKET)
1026 gib_effect = EF_SHIPEXHAUST;
1027 ThrowGib (self, self->head, damage, GIB_ORGANIC, gib_effect);
1028 ThrowGib (self, self->leg, damage, GIB_ORGANIC, gib_effect);
1029 ThrowGib (self, self->leg, damage, GIB_ORGANIC, gib_effect);
1030 ThrowGib (self, self->body, damage, GIB_ORGANIC, gib_effect);
1031 ThrowGib (self, self->arm, damage, GIB_ORGANIC, gib_effect);
1032 ThrowGib (self, self->arm, damage, GIB_ORGANIC, gib_effect);
1033 }
1034 }
1035 else
1036 { // normal death
1037 if (!self->deadflag)
1038 {
1039 static int i;
1040
1041 i = (i+1)%3;
1042 // start a death animation
1043 self->client->anim_priority = ANIM_DEATH;
1044
1045 switch (i)
1046 {
1047 //all player models are now using the longer set of death frames only
1048 case 0:
1049 self->s.frame = FRAME_death501-1;
1050 self->client->anim_end = FRAME_death518;
1051 break;
1052 case 1:
1053 self->s.frame = FRAME_death601-1;
1054 self->client->anim_end = FRAME_death620;
1055 break;
1056 case 2:
1057 self->s.frame = FRAME_death401-1;
1058 self->client->anim_end = FRAME_death422;
1059 break;
1060
1061 }
1062 gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0);
1063 }
1064 }
1065
1066 gi.sound (self, CHAN_VOICE, gi.soundindex("misc/death.wav"), 1, ATTN_STATIC, 0);
1067
1068 self->deadflag = DEAD_DEAD;
1069 self->takedamage = DAMAGE_NO;
1070
1071 gi.linkentity (self);
1072 }
1073
1074 //=======================================================================
1075
1076 /*
1077 ==============
1078 InitClientPersistant
1079
1080 This is only called when the game first initializes in single player,
1081 but is called after each death and level change in deathmatch
1082 ==============
1083 */
InitClientPersistant(gclient_t * client)1084 void InitClientPersistant (gclient_t *client)
1085 {
1086 gitem_t *item;
1087 int queue=0;
1088
1089 if(g_duel->integer) //need to save this off in duel mode. Potentially dangerous?
1090 queue = client->pers.queue;
1091
1092 memset (&client->pers, 0, sizeof(client->pers));
1093
1094 if(g_duel->integer)
1095 client->pers.queue = queue;
1096
1097 #ifdef ALTERIA
1098 item = FindItem("Warriorpunch");
1099 #else
1100 if(!rocket_arena->integer && !g_tactical->integer)
1101 { //gets a violator, unless RA or Tactical
1102 item = FindItem("Violator");
1103 client->pers.selected_item = ITEM_INDEX(item);
1104 client->pers.inventory[client->pers.selected_item] = 1;
1105 client->pers.weapon = item;
1106 }
1107
1108 //mutator - will need to have item
1109 if(instagib->integer)
1110 {
1111 client->pers.inventory[ITEM_INDEX(FindItem("Alien Disruptor"))] = 1;
1112 client->pers.inventory[ITEM_INDEX(FindItem("cells"))] = g_maxcells->value;
1113 item = FindItem("Alien Disruptor");
1114 }
1115 else if(rocket_arena->integer)
1116 {
1117 client->pers.inventory[ITEM_INDEX(FindItem("Rocket Launcher"))] = 1;
1118 client->pers.inventory[ITEM_INDEX(FindItem("rockets"))] = g_maxrockets->value;
1119 item = FindItem("Rocket Launcher");
1120 }
1121 else if (insta_rockets->integer )
1122 {
1123 client->pers.inventory[ITEM_INDEX(FindItem("Rocket Launcher"))] = 1;
1124 client->pers.inventory[ITEM_INDEX(FindItem("rockets"))] = g_maxrockets->value;
1125 item = FindItem("Rocket Launcher");
1126 client->pers.inventory[ITEM_INDEX(FindItem("Alien Disruptor"))] = 1;
1127 client->pers.inventory[ITEM_INDEX(FindItem("cells"))] = g_maxcells->value;
1128 item = FindItem("Alien Disruptor");
1129 }
1130 else
1131 item = FindItem("Blaster");
1132 #endif
1133 client->pers.selected_item = ITEM_INDEX(item);
1134 client->pers.inventory[client->pers.selected_item] = 1;
1135
1136 client->pers.weapon = item;
1137
1138 if(excessive->value)
1139 {
1140 //Allow custom health, even in excessive.
1141 client->pers.health = g_spawnhealth->value * 3;
1142 client->pers.max_bullets = g_maxbullets->value * 2.5;
1143 client->pers.max_shells = g_maxshells->value * 5;
1144 client->pers.max_rockets = g_maxrockets->value * 10;
1145 client->pers.max_grenades = g_maxgrenades->value * 10;
1146 client->pers.max_cells = g_maxcells->value * 2.5;
1147 client->pers.max_slugs = g_maxslugs->value * 10;
1148 client->pers.max_seekers = g_maxseekers->value * 2;
1149 client->pers.max_bombs = g_maxbombs->value * 2;
1150
1151 client->pers.inventory[ITEM_INDEX(FindItem("Rocket Launcher"))] = 1;
1152 client->pers.inventory[ITEM_INDEX(FindItem("rockets"))] = g_maxrockets->value * 10;
1153 client->pers.inventory[ITEM_INDEX(FindItem("Pulse Rifle"))] = 1;
1154 client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] = g_maxbullets->value * 2.5;
1155 client->pers.inventory[ITEM_INDEX(FindItem("Alien Disruptor"))] = 1;
1156 client->pers.inventory[ITEM_INDEX(FindItem("Disruptor"))] = 1;
1157 client->pers.inventory[ITEM_INDEX(FindItem("cells"))] = g_maxcells->value * 2.5;
1158 client->pers.inventory[ITEM_INDEX(FindItem("Alien Smartgun"))] = 1;
1159 client->pers.inventory[ITEM_INDEX(FindItem("alien smart grenade"))] = g_maxshells->value * 5;
1160 client->pers.inventory[ITEM_INDEX(FindItem("Alien Vaporizer"))] = 1;
1161 client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] = g_maxslugs->value * 10;
1162 client->pers.inventory[ITEM_INDEX(FindItem("Flame Thrower"))] = 1;
1163 client->pers.inventory[ITEM_INDEX(FindItem("napalm"))] = g_maxgrenades->value * 10;
1164 client->pers.inventory[ITEM_INDEX(FindItem("Minderaser"))] = 1;
1165 client->pers.inventory[ITEM_INDEX(FindItem("seekers"))] = g_maxseekers->value * 2;
1166 client->pers.inventory[ITEM_INDEX(FindItem("bombs"))] = g_maxbombs->value * 2;
1167 }
1168 else
1169 {
1170 client->pers.health = g_spawnhealth->value;
1171 client->pers.max_bullets = g_maxbullets->value;
1172 client->pers.max_shells = g_maxshells->value;
1173 client->pers.max_rockets = g_maxrockets->value;
1174 client->pers.max_grenades = g_maxgrenades->value;
1175 client->pers.max_cells = g_maxcells->value;
1176 client->pers.max_slugs = g_maxslugs->value;
1177 client->pers.max_seekers = g_maxseekers->value;
1178 client->pers.max_bombs = g_maxbombs->value;
1179 }
1180
1181 if(vampire->value)
1182 client->pers.max_health = g_maxhealth->value * 2;
1183 else if(excessive->value)
1184 client->pers.max_health = g_maxhealth->value * 3;
1185 else
1186 client->pers.max_health = g_maxhealth->value;
1187
1188 if(grapple->value)
1189 {
1190 item = FindItem("Grapple");
1191 client->pers.inventory[ITEM_INDEX(item)] = 1;
1192 }
1193
1194 //if powered up, give back powerups
1195 if(client->resp.powered)
1196 {
1197 item = FindItem("Invisibility");
1198 client->pers.inventory[ITEM_INDEX(item)] += 1;
1199
1200 item = FindItem("Sproing");
1201 client->pers.inventory[ITEM_INDEX(item)] += 1;
1202
1203 item = FindItem("Haste");
1204 client->pers.inventory[ITEM_INDEX(item)] += 1;
1205 }
1206
1207 client->pers.connected = true;
1208 }
1209
1210
InitClientResp(gclient_t * client)1211 void InitClientResp (gclient_t *client)
1212 {
1213 memset (&client->resp, 0, sizeof(client->resp));
1214 client->resp.enterframe = level.framenum;
1215 }
1216
1217 /*
1218 ==================
1219 SaveClientData
1220
1221 Some information that should be persistant, like health,
1222 is still stored in the edict structure, so it needs to
1223 be mirrored out to the client structure before all the
1224 edicts are wiped.
1225 ==================
1226 */
SaveClientData(void)1227 void SaveClientData (void)
1228 {
1229 int i;
1230 edict_t *ent;
1231
1232 for (i=0 ; i<game.maxclients ; i++)
1233 {
1234 ent = &g_edicts[1+i];
1235 if (!ent->inuse)
1236 continue;
1237 game.clients[i].pers.health = ent->health;
1238 game.clients[i].pers.max_health = ent->max_health;
1239 }
1240 }
1241
FetchClientEntData(edict_t * ent)1242 void FetchClientEntData (edict_t *ent)
1243 {
1244 ent->health = ent->client->pers.health;
1245 ent->max_health = ent->client->pers.max_health;
1246 }
1247
1248
1249
1250 /*
1251 =======================================================================
1252
1253 SelectSpawnPoint
1254
1255 =======================================================================
1256 */
1257
1258 /*
1259 ================
1260 PlayersRangeFromSpot
1261
1262 Returns the distance to the nearest player from the given spot
1263 ================
1264 */
PlayersRangeFromSpot(edict_t * spot)1265 float PlayersRangeFromSpot (edict_t *spot)
1266 {
1267 edict_t *player;
1268 float bestplayerdistance;
1269 vec3_t v;
1270 int n;
1271 float playerdistance;
1272
1273
1274 bestplayerdistance = 9999999;
1275
1276 for (n = 1; n <= g_maxclients->value; n++)
1277 {
1278 player = &g_edicts[n];
1279
1280 if (!player->inuse)
1281 continue;
1282
1283 if (player->health <= 0)
1284 continue;
1285
1286 VectorSubtract (spot->s.origin, player->s.origin, v);
1287 playerdistance = VectorLength (v);
1288
1289 if (playerdistance < bestplayerdistance)
1290 bestplayerdistance = playerdistance;
1291 }
1292
1293 return bestplayerdistance;
1294 }
1295
1296 /*
1297 ================
1298 SelectRandomDeathmatchSpawnPoint
1299
1300 go to a random point, but NOT the two points closest
1301 to other players
1302 ================
1303 */
SelectRandomDeathmatchSpawnPoint(void)1304 edict_t *SelectRandomDeathmatchSpawnPoint (void)
1305 {
1306 edict_t *spot, *spot1, *spot2;
1307 int count = 0;
1308 int selection;
1309 float range, range1, range2;
1310
1311 spot = NULL;
1312 range1 = range2 = 99999;
1313 spot1 = spot2 = NULL;
1314
1315 while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
1316 {
1317 count++;
1318 range = PlayersRangeFromSpot(spot);
1319 if (range < range1)
1320 {
1321 range1 = range;
1322 spot1 = spot;
1323 }
1324 else if (range < range2)
1325 {
1326 range2 = range;
1327 spot2 = spot;
1328 }
1329 }
1330
1331 if (!count)
1332 return NULL;
1333
1334 if (count <= 2)
1335 {
1336 spot1 = spot2 = NULL;
1337 }
1338 else
1339 {
1340 if ( spot1 ) {
1341 count--;
1342 }
1343 if ( spot2 ) {
1344 count--;
1345 }
1346 }
1347
1348 selection = rand() % count;
1349
1350 spot = NULL;
1351 do
1352 {
1353 spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
1354 if (spot == spot1 || spot == spot2)
1355 selection++;
1356 } while(selection--);
1357
1358 return spot;
1359 }
1360 /*
1361 ================
1362 SelectRandomDeathmatchSpawnPoint
1363
1364 go to a random point, but NOT the two points closest
1365 to other players
1366 ================
1367 */
SelectRandomCTFSpawnPoint(void)1368 edict_t *SelectRandomCTFSpawnPoint (void)
1369 {
1370 edict_t *spot, *spot1, *spot2;
1371 int count = 0;
1372 int selection;
1373 float range, range1, range2;
1374 char whichteam[32];
1375
1376 spot = NULL;
1377 range1 = range2 = 99999;
1378 spot1 = spot2 = NULL;
1379
1380 if(random() < 0.5)
1381 strcpy(whichteam, "info_player_red");
1382 else
1383 strcpy(whichteam, "info_player_blue");
1384
1385 while ((spot = G_Find (spot, FOFS(classname), whichteam)) != NULL)
1386 {
1387 count++;
1388 range = PlayersRangeFromSpot(spot);
1389 if (range < range1)
1390 {
1391 range1 = range;
1392 spot1 = spot;
1393 }
1394 else if (range < range2)
1395 {
1396 range2 = range;
1397 spot2 = spot;
1398 }
1399 }
1400
1401 if (!count)
1402 return NULL;
1403
1404 if (count <= 2)
1405 {
1406 spot1 = spot2 = NULL;
1407 }
1408 else
1409 {
1410 if ( spot1 ) {
1411 count--;
1412 }
1413 if ( spot2 ) {
1414 count--;
1415 }
1416 }
1417
1418 selection = rand() % count;
1419
1420 spot = NULL;
1421 do
1422 {
1423 spot = G_Find (spot, FOFS(classname), whichteam);
1424 if (spot == spot1 || spot == spot2)
1425 selection++;
1426 } while(selection--);
1427
1428 return spot;
1429 }
1430 /*
1431 ================
1432 SelectFarthestDeathmatchSpawnPoint
1433
1434 ================
1435 */
SelectFarthestDeathmatchSpawnPoint(void)1436 edict_t *SelectFarthestDeathmatchSpawnPoint (void)
1437 {
1438 edict_t *bestspot;
1439 float bestdistance, bestplayerdistance;
1440 edict_t *spot;
1441
1442
1443 spot = NULL;
1444 bestspot = NULL;
1445 bestdistance = 0;
1446 while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
1447 {
1448 bestplayerdistance = PlayersRangeFromSpot (spot);
1449
1450 if (bestplayerdistance > bestdistance)
1451 {
1452 bestspot = spot;
1453 bestdistance = bestplayerdistance;
1454 }
1455 }
1456
1457 if (bestspot)
1458 {
1459 return bestspot;
1460 }
1461
1462 // if there is a player just spawned on each and every start spot
1463 // we have no choice to turn one into a telefrag meltdown
1464 spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
1465
1466 return spot;
1467 }
1468
SelectDeathmatchSpawnPoint(void)1469 edict_t *SelectDeathmatchSpawnPoint (void)
1470 {
1471 if ( dmflags->integer & DF_SPAWN_FARTHEST)
1472 return SelectFarthestDeathmatchSpawnPoint ();
1473 else
1474 return SelectRandomDeathmatchSpawnPoint ();
1475 }
1476
1477 /*
1478 ================
1479 SelectCTFSpawnPoint
1480
1481 go to a ctf point, but NOT the two points closest
1482 to other players
1483 ================
1484 */
SelectCTFSpawnPoint(edict_t * ent)1485 edict_t *SelectCTFSpawnPoint (edict_t *ent)
1486 {
1487 edict_t *spot, *spot1, *spot2;
1488 int count = 0;
1489 int selection;
1490 float range, range1, range2;
1491 char *cname;
1492
1493 if(g_tactical->value)
1494 {
1495 if(ent->ctype == 1)
1496 cname = "info_player_red";
1497 else
1498 cname = "info_player_blue";
1499 }
1500 else
1501 {
1502 switch (ent->dmteam)
1503 {
1504 case RED_TEAM:
1505 cname = "info_player_red";
1506 break;
1507 case BLUE_TEAM:
1508 cname = "info_player_blue";
1509 break;
1510 case NO_TEAM:
1511 default:
1512 return SelectRandomCTFSpawnPoint();
1513 }
1514 }
1515
1516 spot = NULL;
1517 range1 = range2 = 99999;
1518 spot1 = spot2 = NULL;
1519
1520 while ((spot = G_Find (spot, FOFS(classname), cname)) != NULL)
1521 {
1522 count++;
1523 range = PlayersRangeFromSpot(spot);
1524 if (range < range1)
1525 {
1526 range1 = range;
1527 spot1 = spot;
1528 }
1529 else if (range < range2)
1530 {
1531 range2 = range;
1532 spot2 = spot;
1533 }
1534 }
1535
1536 if (!count)
1537 return SelectRandomDeathmatchSpawnPoint();
1538
1539 if (count <= 2)
1540 {
1541 spot1 = spot2 = NULL;
1542 }
1543 else
1544 count -= 2;
1545
1546 selection = rand() % count;
1547
1548 spot = NULL;
1549 do
1550 {
1551 spot = G_Find (spot, FOFS(classname), cname);
1552 if (spot == spot1 || spot == spot2)
1553 selection++;
1554 } while(selection--);
1555
1556 return spot;
1557 }
1558
1559
1560 /*
1561 ===========
1562 SelectSpawnPoint
1563
1564 Chooses a player start, deathmatch start, coop start, etc
1565 ============
1566 */
SelectSpawnPoint(edict_t * ent,vec3_t origin,vec3_t angles)1567 void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles)
1568 {
1569 edict_t *spot = NULL;
1570
1571 if (deathmatch->value)
1572 {
1573 if (g_tactical->value || ctf->value || tca->value || cp->value || (dmflags->integer & DF_SKINTEAMS))
1574 {
1575 spot = SelectCTFSpawnPoint(ent);
1576 if(!spot)
1577 spot = SelectDeathmatchSpawnPoint ();
1578 }
1579 else
1580 {
1581 spot = SelectDeathmatchSpawnPoint ();
1582 if(!spot)
1583 spot = SelectCTFSpawnPoint(ent); //dm on team based maps
1584 }
1585 }
1586
1587 // find a single player start spot
1588 if (!spot)
1589 {
1590 spot = G_Find (spot, FOFS(classname), "info_player_start");
1591 if (!spot)
1592 gi.error ("Couldn't find spawn point!");
1593 }
1594
1595 VectorCopy (spot->s.origin, origin);
1596 origin[2] += 9;
1597 VectorCopy (spot->s.angles, angles);
1598 }
1599
1600 //======================================================================
1601
1602
InitBodyQue(void)1603 void InitBodyQue (void)
1604 {
1605 int i;
1606 edict_t *ent;
1607
1608 level.body_que = 0;
1609 for (i=0; i<BODY_QUEUE_SIZE ; i++)
1610 {
1611 ent = G_Spawn();
1612 ent->classname = "bodyque";
1613 }
1614 }
1615
1616 /*
1617 =============
1618 BodySink
1619
1620 After sitting around for five seconds, fall into the ground and dissapear
1621 =============
1622 */
BodySink(edict_t * ent)1623 void BodySink( edict_t *ent ) {
1624 if ( level.time - ent->timestamp > 10.5 ) {
1625 // the body ques are never actually freed, they are just unlinked
1626 gi.unlinkentity( ent );
1627 ent->s.modelindex = 0; //for good measure
1628 ent->s.modelindex2 = 0;
1629 ent->s.modelindex3 = 0;
1630 ent->s.modelindex4 = 0;
1631 ent->s.effects = 0;
1632 return;
1633 }
1634 ent->nextthink = level.time + .1;
1635 ent->s.origin[2] -= 1;
1636 ent->s.effects |= EF_COLOR_SHELL;
1637 ent->s.renderfx |= RF_SHELL_GREEN;
1638 ent->solid = SOLID_NOT; //don't gib sinking bodies
1639 }
1640
body_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)1641 void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
1642 {
1643 self->s.modelindex3 = 0;
1644 self->s.modelindex4 = 0;
1645
1646 self->takedamage = DAMAGE_NO;
1647 self->solid = SOLID_NOT;
1648 self->s.effects = EF_GIB;
1649 self->s.sound = 0;
1650 self->flags |= FL_NO_KNOCKBACK;
1651
1652 if (self->client) // bodies in the queue don't have a client anymore
1653 {
1654 self->client->anim_priority = ANIM_DEATH;
1655 self->client->anim_end = self->s.frame;
1656 }
1657 else
1658 {
1659 self->think = NULL;
1660 self->nextthink = 0;
1661 }
1662
1663 gi.linkentity (self);
1664
1665 }
1666
1667
CopyToBodyQue(edict_t * ent)1668 void CopyToBodyQue (edict_t *ent)
1669 {
1670 edict_t *body;
1671
1672 // grab a body que and cycle to the next one
1673 body = &g_edicts[g_maxclients->integer + level.body_que + 1];
1674 level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;
1675
1676 gi.unlinkentity (ent);
1677
1678 gi.unlinkentity (body);
1679 body->s = ent->s;
1680 body->s.number = body - g_edicts;
1681
1682 body->svflags = ent->svflags;
1683
1684 VectorCopy (ent->mins, body->mins);
1685 VectorCopy (ent->maxs, body->maxs);
1686 VectorCopy (ent->absmin, body->absmin);
1687 VectorCopy (ent->absmax, body->absmax);
1688 VectorCopy (ent->size, body->size);
1689 body->solid = SOLID_NOT;
1690 body->clipmask = ent->clipmask;
1691 body->owner = ent->owner;
1692 body->movetype = ent->movetype;
1693 body->die = body_die;
1694 body->takedamage = DAMAGE_YES;
1695 body->ctype = ent->ctype;
1696 body->timestamp = level.time;
1697 body->nextthink = level.time + 5;
1698 body->think = BodySink;
1699
1700 gi.linkentity (body);
1701 }
1702
1703
respawn(edict_t * self)1704 void respawn (edict_t *self)
1705 {
1706 if (deathmatch->value)
1707 {
1708
1709 // ACEBOT_ADD special respawning code
1710 if (self->is_bot)
1711 {
1712 ACESP_Respawn (self);
1713 return;
1714 }
1715 // ACEBOT_END
1716
1717 //spectator mode
1718 // spectator's don't leave bodies
1719 if (self->movetype != MOVETYPE_NOCLIP)
1720 CopyToBodyQue (self);
1721 //end spectator mode
1722 self->svflags &= ~SVF_NOCLIENT;
1723 PutClientInServer (self);
1724
1725 // add a teleportation effect
1726 self->s.event = EV_PLAYER_TELEPORT;
1727
1728 // hold in place briefly
1729 self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
1730 self->client->ps.pmove.pm_time = 14;
1731
1732 self->client->respawn_time = level.time;
1733
1734 return;
1735 }
1736
1737 // restart the entire server
1738 gi.AddCommandString ("menu_loadgame\n");
1739 }
1740
1741 //spectator mode
spectator_respawn(edict_t * ent)1742 void spectator_respawn (edict_t *ent)
1743 {
1744 int i, numspec;
1745
1746 // if the user wants to become a spectator, make sure he doesn't
1747 // exceed max_spectators
1748
1749 if (ent->client->pers.spectator) {
1750 char *value = Info_ValueForKey (ent->client->pers.userinfo, "spectator_password");
1751 if (*spectator_password->string &&
1752 strcmp(spectator_password->string, "none") &&
1753 strcmp(spectator_password->string, value)) {
1754 gi.cprintf(ent, PRINT_HIGH, "%s", "Spectator password incorrect.\n");
1755 ent->client->pers.spectator = 0;
1756 gi.WriteByte (svc_stufftext);
1757 gi.WriteString ("spectator 0\n");
1758 gi.unicast(ent, true);
1759 return;
1760 }
1761
1762 // count spectators
1763 for (i = 1, numspec = 0; i <= g_maxclients->value; i++)
1764 if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator)
1765 numspec++;
1766
1767 if (numspec >= maxspectators->value) {
1768 gi.cprintf(ent, PRINT_HIGH, "%s", "Server spectator limit is full.");
1769 ent->client->pers.spectator = 0;
1770 // reset his spectator var
1771 gi.WriteByte (svc_stufftext);
1772 gi.WriteString ("spectator 0\n");
1773 gi.unicast(ent, true);
1774 return;
1775 }
1776 } else {
1777 // he was a spectator and wants to join the game
1778 // he must have the right password
1779 char *value = Info_ValueForKey (ent->client->pers.userinfo, "password");
1780 if (*password->string && strcmp(password->string, "none") &&
1781 strcmp(password->string, value))
1782 {
1783 gi.cprintf(ent, PRINT_HIGH, "%s", "Password incorrect.\n");
1784 ent->client->pers.spectator = 1;
1785 gi.WriteByte (svc_stufftext);
1786 gi.WriteString ("spectator 1\n");
1787 gi.unicast(ent, true);
1788 return;
1789 }
1790 }
1791
1792 /* Remove deathcam if changed to spectator after death */
1793 if (ent->client->pers.spectator && ent->deadflag)
1794 DeathcamRemove (ent, "off");
1795
1796 // clear client on respawn
1797 ent->client->resp.score = 0;
1798
1799 ent->svflags &= ~SVF_NOCLIENT;
1800 PutClientInServer (ent);
1801
1802 // add a teleportation effect
1803 if (!ent->client->pers.spectator) {
1804 // send effect
1805 gi.WriteByte (svc_muzzleflash);
1806 gi.WriteShort (ent-g_edicts);
1807 gi.WriteByte (MZ_LOGIN);
1808 gi.multicast (ent->s.origin, MULTICAST_PVS);
1809
1810 // hold in place briefly
1811 ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
1812 ent->client->ps.pmove.pm_time = 14;
1813 }
1814
1815 ent->client->respawn_time = level.time;
1816
1817 if (ent->client->pers.spectator)
1818 { // pers.spectator > 0, entering spectator mode
1819 safe_bprintf (PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname);
1820 }
1821 else
1822 { // pers.spectator==0, leaving spectator mode
1823 safe_bprintf (PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname);
1824 }
1825 if ( sv_botkickthreshold && sv_botkickthreshold->integer )
1826 { // player entered or left playing field, update for auto botkick
1827 ACESP_LoadBots( ent );
1828 }
1829
1830
1831 }
1832 //end spectator mode
1833
1834 //==============================================================
1835
1836 /*
1837 ===========
1838 ParseClassFile
1839
1840 Used in tactical mode for setting class items
1841 ===========
1842 */
1843
ParseClassFile(char * config_file,edict_t * ent)1844 void ParseClassFile( char *config_file, edict_t *ent )
1845 {
1846 char full_path[MAX_OSPATH];
1847 FILE *fp;
1848 int length;
1849 char a_string[128];
1850 char *buffer;
1851 char *s;
1852 size_t result;
1853
1854 if ( gi.FullPath( full_path, sizeof(full_path), config_file ) )
1855 {
1856
1857 if((fp = fopen(full_path, "rb" )) == NULL)
1858 {
1859 return;
1860 }
1861 if ( fseek(fp, 0, SEEK_END) )
1862 { // seek error
1863 fclose( fp );
1864 return;
1865 }
1866 if ( (length = ftell(fp)) == (size_t)-1L )
1867 { // tell error
1868 fclose( fp );
1869 return;
1870 }
1871 if ( fseek(fp, 0, SEEK_SET) )
1872 { // seek error
1873 fclose( fp );
1874 return;
1875 }
1876
1877 buffer = malloc( length + 1 );
1878 if ( buffer != NULL )
1879 {
1880 buffer[length] = 0;
1881 result = fread( buffer, length, 1, fp );
1882 if ( result == 1 )
1883 {
1884 s = buffer;
1885
1886 strcpy( a_string, COM_Parse( &s ) );
1887 ent->max_health = atoi(a_string);
1888 strcpy( a_string, COM_Parse( &s ) );
1889 ent->armor_type = atoi(a_string);
1890 strcpy( a_string, COM_Parse( &s ) );
1891 ent->has_bomb = atoi(a_string);
1892 strcpy( a_string, COM_Parse( &s ) );
1893 ent->has_detonator = atoi(a_string);
1894 strcpy( a_string, COM_Parse( &s ) );
1895 ent->has_minderaser = atoi(a_string);
1896 strcpy( a_string, COM_Parse( &s ) );
1897 ent->has_vaporizor = atoi(a_string);
1898
1899 //note - we may or may not need the ent vars, for now keep them
1900 if(ent->max_health > 0)
1901 ent->health = ent->client->pers.max_health = ent->client->pers.health = ent->max_health;
1902 else
1903 ent->max_health = 100;
1904
1905 switch(ent->armor_type)
1906 {
1907 default:
1908 case 0:
1909 break;
1910 case 1:
1911 ent->client->pers.inventory[ITEM_INDEX(FindItem("Jacket Armor"))] += 30;
1912 break;
1913 case 2:
1914 ent->client->pers.inventory[ITEM_INDEX(FindItem("Body Armor"))] += 50;
1915 break;
1916 case 3:
1917 ent->client->pers.inventory[ITEM_INDEX(FindItem("Jacket Armor"))] += 30;
1918 break;
1919 }
1920
1921 if(ent->has_minderaser)
1922 {
1923 ent->client->pers.inventory[ITEM_INDEX(FindItem("Minderaser"))] = 1;
1924 ent->client->pers.inventory[ITEM_INDEX(FindItem("seekers"))] = 1;
1925 }
1926
1927 if(ent->has_vaporizor)
1928 {
1929 ent->client->pers.inventory[ITEM_INDEX(FindItem("Alien Vaporizer"))] = 1;
1930 ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] = 4;
1931 }
1932 }
1933
1934 free( buffer );
1935 }
1936 fclose( fp );
1937 }
1938
1939 return;
1940 }
1941
1942 /*
1943 ===========
1944 PutClientInServer
1945
1946 Called when a player connects to a server or respawns in
1947 a deathmatch.
1948 ============
1949 */
PutClientInServer(edict_t * ent)1950 void PutClientInServer (edict_t *ent)
1951 {
1952 vec3_t mins = {-16, -16, -24};
1953 vec3_t maxs = {16, 16, 32};
1954 int index, armor_index;
1955 vec3_t spawn_origin, spawn_angles;
1956 gclient_t *client;
1957 gitem_t *item;
1958 int i, done;
1959 client_persistant_t saved;
1960 client_respawn_t resp;
1961 char *info;
1962 char playermodel[MAX_OSPATH] = " ";
1963 char modelpath[MAX_OSPATH] = " ";
1964 FILE *file;
1965 char userinfo[MAX_INFO_STRING];
1966
1967 // find a spawn point
1968 // do it before setting health back up, so farthest
1969 // ranging doesn't count this client
1970 if(!g_tactical->integer)
1971 SelectSpawnPoint (ent, spawn_origin, spawn_angles);
1972
1973 index = ent-g_edicts-1;
1974 client = ent->client;
1975 client->is_bot = 0;
1976 client->kill_streak = 0;
1977 client->homing_shots = 0;
1978 client->mapvote = 0;
1979 client->lasttaunttime = 0;
1980 client->rayImmunity = false;
1981
1982 resp = client->resp;
1983 memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
1984 InitClientPersistant (client);
1985 ClientUserinfoChanged (ent, userinfo, SPAWN);
1986
1987 // clear everything but the persistant data
1988 saved = client->pers;
1989 memset (client, 0, sizeof(*client));
1990 client->pers = saved;
1991 if (client->pers.health <= 0)
1992 InitClientPersistant(client);
1993 client->resp = resp;
1994
1995 // copy some data from the client to the entity
1996 FetchClientEntData (ent);
1997
1998 // clear entity values
1999 ent->groundentity = NULL;
2000 ent->client = &game.clients[index];
2001 if(g_spawnprotect->value)
2002 ent->client->spawnprotected = true;
2003 ent->takedamage = DAMAGE_AIM;
2004 ent->movetype = MOVETYPE_WALK;
2005 ent->viewheight = 22;
2006 ent->inuse = true;
2007 ent->classname = "player";
2008 ent->mass = 200;
2009 ent->solid = SOLID_BBOX;
2010 ent->deadflag = DEAD_NO;
2011 ent->air_finished = level.time + 12;
2012 ent->clipmask = MASK_PLAYERSOLID;
2013 ent->model = "players/martianenforcer/tris.md2";
2014 ent->pain = player_pain;
2015 ent->die = player_die;
2016 ent->waterlevel = 0;
2017 ent->watertype = 0;
2018 ent->flags &= ~FL_NO_KNOCKBACK;
2019 ent->svflags &= ~SVF_DEADMONSTER;
2020 // ACEBOT_ADD
2021 ent->is_bot = false;
2022 ent->last_node = -1;
2023 ent->is_jumping = false;
2024 // ACEBOT_END
2025 //vehicles
2026 ent->in_vehicle = false;
2027
2028 //deathball
2029 ent->in_deathball = false;
2030
2031 //anti-camp
2032 ent->suicide_timeout = level.time + 10.0;
2033 VectorClear (ent->velocity_accum);
2034 ent->old_velocities_current = -1;
2035 ent->old_velocities_count = 0;
2036
2037 VectorCopy (mins, ent->mins);
2038 VectorCopy (maxs, ent->maxs);
2039 VectorClear (ent->velocity);
2040
2041 // clear playerstate values
2042 memset (&ent->client->ps, 0, sizeof(client->ps));
2043
2044 //remove these if there are there
2045 if(ent->client->oldplayer)
2046 G_FreeEdict (ent->client->oldplayer);
2047 if(ent->client->chasecam)
2048 G_FreeEdict (ent->client->chasecam);
2049
2050 client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
2051 if (client->ps.fov < 1)
2052 client->ps.fov = 90;
2053 else if (client->ps.fov > 160)
2054 client->ps.fov = 160;
2055
2056 client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
2057
2058 // clear entity state values
2059 ent->s.effects = 0;
2060 ent->s.skinnum = ent - g_edicts - 1;
2061 ent->s.modelindex = 255; // will use the skin specified model
2062 ent->s.modelindex2 = 255; // custom gun model
2063 info = Info_ValueForKey (ent->client->pers.userinfo, "skin");
2064
2065 i = 0;
2066 done = false;
2067 strcpy(playermodel, " ");
2068 while(!done)
2069 {
2070 if((info[i] == '/') || (info[i] == '\\'))
2071 done = true;
2072 playermodel[i] = info[i];
2073 if(i > 62)
2074 done = true;
2075 i++;
2076 }
2077 playermodel[i-1] = 0;
2078
2079 sprintf(modelpath, "players/%s/helmet.md2", playermodel);
2080 Q2_FindFile (modelpath, &file); //does a helmet exist?
2081 if(file)
2082 {
2083 sprintf(modelpath, "players/%s/helmet.md2", playermodel);
2084 ent->s.modelindex3 = gi.modelindex(modelpath);
2085 fclose(file);
2086 }
2087 else
2088 ent->s.modelindex3 = 0;
2089
2090 ent->s.modelindex4 = 0;
2091
2092 //check for class file
2093 #ifdef ALTERIA
2094 ent->ctype = 0; //wizard is default
2095 sprintf(modelpath, "players/%s/human", playermodel);
2096 Q2_FindFile (modelpath, &file);
2097 if(file)
2098 { //warrior
2099 ent->ctype = 1;
2100
2101 ent->health = ent->max_health = client->pers.max_health = client->pers.health = 100;
2102 armor_index = ITEM_INDEX(FindItem("Jacket Armor"));
2103 client->pers.inventory[armor_index] += 30;
2104 client->pers.inventory[ITEM_INDEX(FindItem("Warriorpunch"))] = 1;
2105 item = FindItem("Warriorpunch");
2106 client->pers.selected_item = ITEM_INDEX(item);
2107 client->pers.inventory[client->pers.selected_item] = 1;
2108 client->pers.weapon = item;
2109
2110 fclose(file);
2111 }
2112 else
2113 { //wizard
2114
2115 ent->health = ent->max_health = client->pers.max_health = client->pers.health = 150;
2116 client->pers.inventory[ITEM_INDEX(FindItem("Wizardpunch"))] = 1;
2117 //client->pers.inventory[ITEM_INDEX(FindItem("cells"))] = 100; //to to - blue or yellow mana
2118 item = FindItem("Wizardpunch");
2119 client->pers.selected_item = ITEM_INDEX(item);
2120 client->pers.inventory[client->pers.selected_item] = 1;
2121 client->pers.weapon = item;
2122 }
2123 #else
2124 ent->ctype = 0; //alien is default
2125 sprintf(modelpath, "players/%s/human", playermodel);
2126 sprintf(ent->charModel, playermodel);
2127 Q2_FindFile (modelpath, &file);
2128 if(file)
2129 {
2130 fclose(file);
2131
2132 //human
2133 ent->ctype = 1;
2134 if(g_tactical->integer || (classbased->value && !(rocket_arena->integer || instagib->integer || insta_rockets->value || excessive->value)))
2135 {
2136 if(g_tactical->integer)
2137 {
2138 //read class file(tactical only)
2139 //example:
2140 //100-150 (health)
2141 //0-3 (armor type)
2142 //0-1 (has bomb)
2143 //0-1 (has detonator)
2144 //0-1 (has mind eraser)
2145 //0-1 (has vaporizor)
2146
2147 ParseClassFile(modelpath, ent);
2148 if(ent->has_bomb)
2149 {
2150 ent->client->pers.inventory[ITEM_INDEX(FindItem("Human Bomb"))] = 1;
2151 ent->client->pers.inventory[ITEM_INDEX(FindItem("bombs"))] = 1; //tactical note - humans will use same ammo, etc, just different weapons
2152 }
2153 item = FindItem("Blaster");
2154 }
2155 else
2156 {
2157 ent->health = ent->max_health = client->pers.max_health = client->pers.health = 100;
2158 armor_index = ITEM_INDEX(FindItem("Jacket Armor"));
2159 client->pers.inventory[armor_index] += 30;
2160
2161 client->pers.inventory[ITEM_INDEX(FindItem("Rocket Launcher"))] = 1;
2162 client->pers.inventory[ITEM_INDEX(FindItem("rockets"))] = 10;
2163 item = FindItem("Rocket Launcher");
2164 }
2165 client->pers.selected_item = ITEM_INDEX(item);
2166 client->pers.inventory[client->pers.selected_item] = 1;
2167 client->pers.weapon = item;
2168 }
2169 }
2170 else
2171 {
2172 //robot - not used in tactical - should we kick them out at this point, or just let them continue on the alien team?
2173 sprintf(modelpath, "players/%s/robot", playermodel);
2174 Q2_FindFile (modelpath, &file);
2175 if(file && !g_tactical->integer)
2176 {
2177 ent->ctype = 2;
2178 if(classbased->value && !(rocket_arena->integer || instagib->integer || insta_rockets->value || excessive->value))
2179 {
2180 ent->health = ent->max_health = client->pers.max_health = client->pers.health = 85;
2181 armor_index = ITEM_INDEX(FindItem("Combat Armor"));
2182 client->pers.inventory[armor_index] += 175;
2183 }
2184 fclose(file);
2185 }
2186 else
2187 {
2188 //alien
2189 ent->ctype = 0;
2190 if(g_tactical->integer || (classbased->value && !(rocket_arena->integer || instagib->integer || insta_rockets->value || excessive->value)))
2191 {
2192 ent->health = ent->max_health = client->pers.max_health = client->pers.health = 150;
2193 if(g_tactical->integer)
2194 {
2195 sprintf(modelpath, "players/%s/alien", playermodel);
2196 Q2_FindFile (modelpath, &file);
2197 if(file)
2198 {
2199 ParseClassFile(modelpath, ent);
2200 if(ent->has_bomb)
2201 {
2202 ent->client->pers.inventory[ITEM_INDEX(FindItem("Alien Bomb"))] = 1;
2203 ent->client->pers.inventory[ITEM_INDEX(FindItem("bombs"))] = 1; //tactical note - humans will use same ammo, etc, just different weapons
2204 }
2205 }
2206 item = FindItem("Blaster");
2207 client->pers.selected_item = ITEM_INDEX(item);
2208 client->pers.inventory[client->pers.selected_item] = 0;
2209
2210 item = FindItem("Alien Blaster");
2211 }
2212 else
2213 {
2214 client->pers.inventory[ITEM_INDEX(FindItem("Alien Disruptor"))] = 1;
2215 client->pers.inventory[ITEM_INDEX(FindItem("cells"))] = 100;
2216 item = FindItem("Alien Disruptor");
2217 }
2218 client->pers.selected_item = ITEM_INDEX(item);
2219 client->pers.inventory[client->pers.selected_item] = 1;
2220 client->pers.weapon = item;
2221 }
2222 }
2223 }
2224 #endif
2225
2226 //has to be done after determining the class/team - note - we don't care about spawn distances in tactical
2227 if(g_tactical->integer)
2228 SelectSpawnPoint (ent, spawn_origin, spawn_angles);
2229
2230 client->ps.pmove.origin[0] = spawn_origin[0]*8;
2231 client->ps.pmove.origin[1] = spawn_origin[1]*8;
2232 client->ps.pmove.origin[2] = spawn_origin[2]*8;
2233
2234 ent->s.frame = 0;
2235 VectorCopy (spawn_origin, ent->s.origin);
2236 ent->s.origin[2] += 1; // make sure off ground
2237 VectorCopy (ent->s.origin, ent->s.old_origin);
2238
2239 // set the delta angle
2240 for (i=0 ; i<3 ; i++)
2241 client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
2242
2243 ent->s.angles[PITCH] = 0;
2244 ent->s.angles[YAW] = spawn_angles[YAW];
2245 ent->s.angles[ROLL] = 0;
2246 VectorCopy (ent->s.angles, client->ps.viewangles);
2247 VectorCopy (ent->s.angles, client->v_angle);
2248
2249 //spectator mode
2250 // spawn a spectator
2251 if ( client->pers.spectator != 0 )
2252 {
2253 client->chase_target = NULL;
2254 client->resp.spectator = client->pers.spectator;
2255 ent->movetype = MOVETYPE_NOCLIP;
2256 ent->solid = SOLID_NOT;
2257 ent->svflags |= SVF_NOCLIENT;
2258 ent->client->ps.gunindex = 0;
2259 gi.linkentity (ent);
2260 return;
2261 }
2262 else if ( !g_duel->integer )
2263 client->resp.spectator = 0;
2264 //end spectator mode
2265
2266 if (!KillBox (ent))
2267 { // could't spawn in?
2268 }
2269 ent->s.event = EV_OTHER_TELEPORT; //to fix "player flash" bug
2270 gi.linkentity (ent);
2271
2272 // force the current weapon up
2273 client->newweapon = client->pers.weapon;
2274 ChangeWeapon (ent);
2275
2276 client->spawnprotecttime = level.time;
2277
2278 //unlagged
2279 if ( g_antilag->integer)
2280 {
2281 G_ResetHistory( ent );
2282 // and this is as good a time as any to clear the saved state
2283 client->saved.leveltime = 0;
2284 }
2285 }
2286
2287 //DUEL MODE
ClientPlaceInQueue(edict_t * ent)2288 void ClientPlaceInQueue(edict_t *ent)
2289 {
2290 int i;
2291 int highestpos, induel, numplayers;
2292
2293 highestpos = induel = numplayers = 0;
2294
2295 for (i = 0; i < g_maxclients->value; i++)
2296 {
2297 if(g_edicts[i+1].inuse && g_edicts[i+1].client)
2298 {
2299 if(g_edicts[i+1].client->pers.queue > highestpos) //only count players that are actually in
2300 highestpos = g_edicts[i+1].client->pers.queue;
2301 if(g_edicts[i+1].client->pers.queue && g_edicts[i+1].client->pers.queue < 3)
2302 induel++;
2303 if(g_edicts[i+1].client->pers.queue) //only count players that are actually in
2304 numplayers++;
2305 }
2306 }
2307
2308 if(induel > 1) //make sure no more than two are in the duel at once
2309 if(highestpos < 2)
2310 highestpos = 2; //in case two people somehow managed to have pos 1
2311 if(highestpos < numplayers)
2312 highestpos = numplayers;
2313
2314 if(!ent->client->pers.queue)
2315 ent->client->pers.queue = highestpos+1;
2316 }
ClientCheckQueue(edict_t * ent)2317 void ClientCheckQueue(edict_t *ent)
2318 {
2319 int i;
2320 int numplayers = 0;
2321
2322 if(ent->client->pers.queue > 2)
2323 {
2324 //everyone in line remains a spectator
2325 ent->client->pers.spectator = ent->client->resp.spectator = 1;
2326 ent->client->chase_target = NULL;
2327 ent->movetype = MOVETYPE_NOCLIP;
2328 ent->solid = SOLID_NOT;
2329 ent->svflags |= SVF_NOCLIENT;
2330 ent->client->ps.gunindex = 0;
2331 gi.linkentity (ent);
2332 }
2333 else
2334 {
2335 for (i = 0; i < g_maxclients->value; i++)
2336 {
2337 if(g_edicts[i+1].inuse && g_edicts[i+1].client)
2338 {
2339 if(!g_edicts[i+1].client->pers.spectator && g_edicts[i+1].client->pers.queue)
2340 numplayers++;
2341 }
2342 }
2343 if(numplayers < 3) //only put him in if there are less than two
2344 ent->client->pers.spectator = ent->client->resp.spectator = 0;
2345 }
2346 }
MoveClientsDownQueue(edict_t * ent)2347 void MoveClientsDownQueue(edict_t *ent)
2348 {
2349 int i;
2350 qboolean putonein = false;
2351
2352 for (i = 0; i < g_maxclients->value; i++)
2353 {
2354 //move everyone down
2355 if(g_edicts[i+1].inuse && g_edicts[i+1].client)
2356 {
2357 if(g_edicts[i+1].client->pers.queue > ent->client->pers.queue)
2358 g_edicts[i+1].client->pers.queue--;
2359
2360 if(!putonein && g_edicts[i+1].client->pers.queue == 2 && g_edicts[i+1].client->resp.spectator)
2361 {
2362 //make sure those who should be in game are
2363 g_edicts[i+1].client->pers.spectator = g_edicts[i+1].client->resp.spectator = false;
2364 g_edicts[i+1].svflags &= ~SVF_NOCLIENT;
2365 g_edicts[i+1].movetype = MOVETYPE_WALK;
2366 g_edicts[i+1].solid = SOLID_BBOX;
2367 if(!g_edicts[i+1].is_bot)
2368 PutClientInServer(g_edicts+i+1);
2369 else
2370 ACESP_PutClientInServer( g_edicts+i+1, true ); // respawn
2371 safe_bprintf(PRINT_HIGH, "%s has entered the duel!\n", g_edicts[i+1].client->pers.netname);
2372 putonein = true;
2373 }
2374 }
2375
2376 }
2377 if(ent->client)
2378 ent->client->pers.queue = 0;
2379 }
2380 //END DUEL MODE
2381
2382 #define MAX_MOTD_SIZE 500
2383
2384 /** @brief Read the MOTD file and send it to the client
2385 *
2386 * Try to open the MOTD file (first from the CVar setting, then from the
2387 * default location). If it is found, read it and send it to the client.
2388 *
2389 * @param ent the entity to send to
2390 *
2391 * @return true if the message was found and sent
2392 */
SendMessageOfTheDay(edict_t * ent)2393 static qboolean SendMessageOfTheDay( edict_t * ent )
2394 {
2395 FILE *file;
2396 char name[ MAX_OSPATH ];
2397 qboolean found;
2398 int size;
2399 char motd[ MAX_MOTD_SIZE ];
2400
2401 found = false;
2402 if ( motdfile && motdfile->string && motdfile->string[0] ) {
2403 // look for custom message of the day file
2404 found = gi.FullPath( name, sizeof( name ),
2405 motdfile->string );
2406 }
2407 if ( !found ) {
2408 // look for default message of the day file
2409 found = gi.FullPath( name, sizeof( name ), "motd.txt" );
2410 }
2411 if ( !found || (file = fopen( name, "rb" )) == NULL ) {
2412 // No MOTD at all, or we can't read it
2413 return false;
2414 }
2415
2416 // We successfully opened the file "motd.txt" - read it and close it
2417 size = fread( motd , 1 , MAX_MOTD_SIZE - 1 , file );
2418 fclose( file );
2419
2420 // Make sure what we read is NULL-terminated
2421 motd[ size ] = 0;
2422
2423 // If the file did contain data, print to the client
2424 if ( size ) {
2425 gi.centerprintf( ent , "%s" , motd );
2426 }
2427 return ( size > 0 );
2428 }
2429
2430
2431 /*
2432 =====================
2433 ClientBeginDeathmatch
2434
2435 A client has just connected to the server in
2436 deathmatch mode, so clear everything out before starting them.
2437 =====================
2438 */
ClientBeginDeathmatch(edict_t * ent)2439 void ClientBeginDeathmatch (edict_t *ent)
2440 {
2441 G_InitEdict (ent);
2442
2443 InitClientResp (ent->client);
2444
2445 ent->dmteam = NO_TEAM;
2446
2447 // locate ent at a spawn point
2448 if(!ent->client->pers.spectator) //fixes invisible player bugs caused by leftover svf_noclients
2449 ent->svflags &= ~SVF_NOCLIENT;
2450 PutClientInServer (ent);
2451
2452 //kick and blackhole a player in tactical that is not using an authorized character!
2453 if(g_tactical->integer)
2454 {
2455 //we want to actually check their model to be one of the valid ones we use
2456 if(strcmp("martianenforcer", ent->charModel) && strcmp("martianwarrior", ent->charModel) && strcmp("martianoverlord", ent->charModel)
2457 && strcmp("lauren", ent->charModel) && strcmp("enforcer", ent->charModel) && strcmp("commander", ent->charModel))
2458 {
2459 if ( ent->is_bot )
2460 {
2461 ACESP_KickBot( ent );
2462 }
2463 else
2464 {
2465 safe_bprintf(PRINT_HIGH, "%s was kicked for using invalid character class!\n", ent->client->pers.netname);
2466 ClientDisconnect (ent);
2467 }
2468 }
2469 }
2470
2471 //in ctf, initially start in chase mode, and allow them to choose a team
2472 if( TEAM_GAME )
2473 {
2474 ent->client->pers.spectator = 1;
2475 ent->client->chase_target = NULL;
2476 ent->client->resp.spectator = 1;
2477 ent->movetype = MOVETYPE_NOCLIP;
2478 ent->solid = SOLID_NOT;
2479 ent->svflags |= SVF_NOCLIENT;
2480 ent->client->ps.gunindex = 0;
2481 gi.linkentity (ent);
2482 //bring up scoreboard if not on a team
2483 if(ent->dmteam == NO_TEAM)
2484 {
2485 ent->client->showscores = true;
2486 CTFScoreboardMessage (ent, NULL, false);
2487 gi.unicast (ent, true);
2488 ent->teamset = true; // used to error check team skin
2489 }
2490 }
2491
2492 //if duel mode, then check number of existing players. If more there are already two in the game, force
2493 //this player to spectator mode, and assign a queue position(we can use the spectator cvar for this)
2494 else if(g_duel->integer)
2495 {
2496 ClientPlaceInQueue(ent);
2497 ClientCheckQueue(ent);
2498 }
2499
2500 // send effect
2501 gi.WriteByte (svc_muzzleflash);
2502 gi.WriteShort (ent-g_edicts);
2503 gi.WriteByte (MZ_LOGIN);
2504 gi.multicast (ent->s.origin, MULTICAST_PVS);
2505
2506 safe_bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
2507
2508 // Send MOTD and enable MOTD protection if necessary
2509 if ( SendMessageOfTheDay( ent ) ) {
2510 ent->client->motd_frames = motdforce->integer;
2511 if ( ent->client->motd_frames < 0 ) {
2512 ent->client->motd_frames = 0;
2513 }
2514 } else {
2515 ent->client->motd_frames = 0;
2516 }
2517
2518 if(g_callvote->value)
2519 safe_cprintf(ent, PRINT_HIGH, "Call voting is ^2ENABLED\n");
2520 else
2521 safe_cprintf(ent, PRINT_HIGH, "Call voting is ^1DISABLED\n");
2522
2523 if(g_antilag->value)
2524 safe_cprintf(ent, PRINT_HIGH, "Antilag is ^2ENABLED\n");
2525 else
2526 safe_cprintf(ent, PRINT_HIGH, "Antilag is ^1DISABLED\n");
2527
2528 //check bots with each player connect
2529 ACESP_LoadBots( ent );
2530
2531 // make sure all view stuff is valid
2532 ClientEndServerFrame (ent);
2533 }
2534
2535
2536 /*
2537 ===========
2538 ClientBegin
2539
2540 called when a client has finished connecting, and is ready
2541 to be placed into the game. This will happen every level load.
2542 ============
2543 */
ClientBegin(edict_t * ent)2544 void ClientBegin (edict_t *ent)
2545 {
2546 int i;
2547
2548 ent->client = game.clients + (ent - g_edicts - 1);
2549
2550 for(i = 0; i < 9; i++) {
2551 ent->client->resp.weapon_shots[i] = 0;
2552 ent->client->resp.weapon_hits[i] = 0;
2553 }
2554 ent->client->kill_streak = 0;
2555
2556 ent->client->homing_shots = 0;
2557
2558 ClientBeginDeathmatch (ent);
2559
2560 }
2561
2562 /*
2563 ===========
2564 ClientUserInfoChanged
2565
2566 called whenever the player updates a userinfo variable.
2567
2568 The game can override any of the settings in place
2569 (forcing skins or names, etc) before copying it off.
2570 ============
2571 */
ClientUserinfoChanged(edict_t * ent,char * userinfo,int whereFrom)2572 void ClientUserinfoChanged (edict_t *ent, char *userinfo, int whereFrom)
2573 {
2574 char *s;
2575 edict_t *cl_ent;
2576 int playernum;
2577 int i, j, k;
2578 qboolean duplicate, done, copychar;
2579 char playermodel[MAX_OSPATH] = " ";
2580 char playerskin[MAX_INFO_STRING] = " ";
2581 char modelpath[MAX_OSPATH] = " ";
2582 char slot[4];
2583 char tmp_playername[PLAYERNAME_SIZE];
2584 FILE *file;
2585 teamcensus_t teamcensus;
2586
2587 // check for malformed or illegal info strings
2588 if (!Info_Validate(userinfo))
2589 {
2590 if(ent->dmteam == RED_TEAM)
2591 strcpy (userinfo, "\\name\\badinfo\\skin\\martianenforcer/red");
2592 else if(ent->dmteam == BLUE_TEAM)
2593 strcpy (userinfo, "\\name\\badinfo\\skin\\martianenforcer/blue");
2594 else
2595 strcpy (userinfo, "\\name\\badinfo\\skin\\martianenforcer/default");
2596
2597 ent->s.modelindex3 = gi.modelindex("players/martianenforcer/helmet.md2");
2598 }
2599
2600 if(whereFrom != SPAWN && whereFrom != CONNECT)
2601 whereFrom = INGAME;
2602
2603 if((playervote.called || g_tactical->integer ) && whereFrom == INGAME)
2604 return; //do not allow people to change info during votes or in tactical mode(yet)
2605
2606 /*if(((dmflags->integer & DF_SKINTEAMS) || ctf->value || tca->value || cp->value) && !whereFrom && (ent->dmteam != NO_TEAM)) {
2607 safe_bprintf (PRINT_MEDIUM, "Changing settings not allowed during a team game\n");
2608 return;
2609 }*/
2610
2611 // validate and set name
2612 s = Info_ValueForKey (userinfo, "name");
2613 if ( s == NULL || *s == 0 )
2614 s = "Player";
2615 Q_strncpyz2( tmp_playername, s , sizeof(tmp_playername) );
2616 ValidatePlayerName( tmp_playername, sizeof(tmp_playername) );
2617 Q_strncpyz2( ent->client->pers.netname, tmp_playername,
2618 sizeof(ent->client->pers.netname));
2619 // end name validate
2620
2621 //spectator mode
2622 if ( !g_duel->integer )
2623 {
2624 // never fool with spectating in duel mode
2625 // set spectator
2626 s = Info_ValueForKey (userinfo, "spectator");
2627 if ( TEAM_GAME )
2628 { //
2629 if ( strcmp( s, "2" ) )
2630 {
2631 // not special team game spectate
2632 // so make sure it stays 0
2633 Info_SetValueForKey( userinfo, "spectator", "0");
2634 }
2635 else
2636 {
2637 ent->client->pers.spectator = 2;
2638 }
2639 }
2640 else
2641 {
2642 if ( deathmatch->value && *s && strcmp(s, "0") )
2643 ent->client->pers.spectator = atoi(s);
2644 else
2645 ent->client->pers.spectator = 0;
2646 }
2647 }
2648 //end spectator mode
2649
2650 // set skin
2651 s = Info_ValueForKey (userinfo, "skin");
2652
2653 // do the team skin check
2654 if ( TEAM_GAME
2655 && ent->client->pers.spectator != 2 && ent->client->resp.spectator != 2 )
2656 {
2657 copychar = false;
2658 strcpy( playerskin, " " );
2659 strcpy( playermodel, " " );
2660 j = k = 0;
2661 for ( i = 0; i <= strlen( s ) && i < MAX_OSPATH; i++ )
2662 {
2663 if(copychar){
2664 playerskin[k] = s[i];
2665 k++;
2666 }
2667 else {
2668 playermodel[j] = s[i];
2669 j++;
2670 }
2671 if ( s[i] == '/' )
2672 copychar = true;
2673 }
2674 playermodel[j] = 0;
2675
2676 if ( whereFrom != SPAWN && whereFrom != CONNECT && ent->teamset )
2677 { // ingame and team is supposed to be set, error check skin
2678 if ( strcmp( playerskin, "red" ) && strcmp( playerskin, "blue" ) )
2679 { // skin error, fix team assignment
2680 ent->dmteam = NO_TEAM;
2681 TeamCensus( &teamcensus ); // apply team balancing rules
2682 ent->dmteam = teamcensus.team_for_real;
2683 safe_bprintf( PRINT_MEDIUM,
2684 "Invalid Team Skin! Assigning to %s Team...\n",
2685 (ent->dmteam == RED_TEAM ? "Red" : "Blue") );
2686 ClientBegin( ent ); // this might work
2687 }
2688 }
2689 strcpy( playerskin, (ent->dmteam == RED_TEAM ? "red" : "blue") );
2690
2691 if(strlen(playermodel) > 32) //something went wrong, or somebody is being malicious
2692 strcpy(playermodel, "martianenforcer/");
2693 strcpy(s, playermodel);
2694 strcat(s, playerskin);
2695 Info_SetValueForKey (userinfo, "skin", s);
2696 }
2697 // end skin check
2698
2699 playernum = ent-g_edicts-1;
2700
2701 //check for duplicates(but not on respawns)
2702 duplicate = false;
2703 if(whereFrom != SPAWN)
2704 {
2705 for (j=0; j<g_maxclients->value ; j++)
2706 {
2707 cl_ent = g_edicts + 1 + j;
2708 if (!cl_ent->inuse)
2709 continue;
2710
2711 if(!strcmp(ent->client->pers.netname, cl_ent->client->pers.netname)) {
2712
2713 if(playernum != j)
2714 duplicate = true;
2715 }
2716 }
2717
2718 if(duplicate && playernum < 100)
2719 {
2720 //just paranoia, should never be more than 64
2721
2722 #if defined WIN32_VARIANT
2723 i = sprintf_s(slot, sizeof(slot), "%i", playernum);
2724 #else
2725 i = snprintf(slot, sizeof(slot), "%i", playernum);
2726 #endif
2727 if ( strlen(ent->client->pers.netname) < (PLAYERNAME_SIZE - i) )
2728 { //small enough, just add to end
2729 strcat(ent->client->pers.netname, slot);
2730 }
2731 else
2732 { //need to lop off end first TODO: technically, should look for color escapes
2733 ent->client->pers.netname[ (PLAYERNAME_SIZE-1) - i ] = 0;
2734 strcat(ent->client->pers.netname, slot);
2735 }
2736
2737 Info_SetValueForKey (userinfo, "name", ent->client->pers.netname);
2738 safe_bprintf(PRINT_HIGH, "Was a duplicate, changing name to %s\n", ent->client->pers.netname);
2739 }
2740 }
2741 // end duplicate check
2742
2743 // combine name and skin into a configstring
2744 gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) );
2745
2746 s = Info_ValueForKey (userinfo, "skin");
2747
2748 i = 0;
2749 done = false;
2750 strcpy(playermodel, " ");
2751 while(!done)
2752 {
2753 if((s[i] == '/') || (s[i] == '\\'))
2754 done = true;
2755 playermodel[i] = s[i];
2756 if(i > 62)
2757 done = true;
2758 i++;
2759 }
2760 playermodel[i-1] = 0;
2761
2762 sprintf(modelpath, "players/%s/helmet.md2", playermodel);
2763 Q2_FindFile (modelpath, &file); //does a helmet exist?
2764 if(file)
2765 {
2766 sprintf(modelpath, "players/%s/helmet.md2", playermodel);
2767 ent->s.modelindex3 = gi.modelindex(modelpath);
2768 fclose(file);
2769 }
2770 else
2771 ent->s.modelindex3 = 0;
2772
2773 ent->s.modelindex4 = 0;
2774
2775 //do gib checking here - to do - let's replace these model specific gibs with class type specific(alien/robot/human)
2776 //check for gib file
2777 ent->usegibs = 0; //alien is default
2778 sprintf(modelpath, "players/%s/usegibs", playermodel);
2779 Q2_FindFile (modelpath, &file);
2780 if(file)
2781 {
2782 //use model specific gibs
2783 ent->usegibs = 1;
2784 sprintf(ent->head, "players/%s/head.md2", playermodel);
2785 sprintf(ent->body, "players/%s/body.md2", playermodel);
2786 sprintf(ent->leg, "players/%s/leg.md2", playermodel);
2787 sprintf(ent->arm, "players/%s/arm.md2", playermodel);
2788 fclose(file);
2789 }
2790
2791 // fov
2792 ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
2793 if (ent->client->ps.fov < 1)
2794 ent->client->ps.fov = 90;
2795 else if (ent->client->ps.fov > 160)
2796 ent->client->ps.fov = 160;
2797
2798 // handedness
2799 s = Info_ValueForKey (userinfo, "hand");
2800 if (strlen(s))
2801 {
2802 ent->client->pers.hand = atoi(s);
2803 }
2804
2805 // save off the userinfo in case we want to check something later
2806 Q_strncpyz2( ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo) );
2807
2808 }
2809
ClientChangeSkin(edict_t * ent)2810 void ClientChangeSkin (edict_t *ent)
2811 {
2812 char *s;
2813 int playernum;
2814 int i, j, k, copychar;
2815 char playermodel[MAX_OSPATH] = " ";
2816 char playerskin[MAX_INFO_STRING] = " ";
2817 char userinfo[MAX_INFO_STRING];
2818 char tmp_playername[PLAYERNAME_SIZE];
2819
2820 //get the userinfo
2821 memcpy (userinfo, ent->client->pers.userinfo, sizeof(userinfo));
2822
2823 // check for malformed or illegal info strings
2824 if (!Info_Validate(userinfo))
2825 {
2826 if(ent->dmteam == RED_TEAM)
2827 strcpy (userinfo, "\\name\\badinfo\\skin\\martianenforcer/red");
2828 else if(ent->dmteam == BLUE_TEAM)
2829 strcpy (userinfo, "\\name\\badinfo\\skin\\martianenforcer/blue");
2830 else
2831 strcpy (userinfo, "\\name\\badinfo\\skin\\martianenforcer/default");
2832
2833 ent->s.modelindex3 = gi.modelindex("players/martianenforcer/helmet.md2");
2834 }
2835
2836 // set name
2837 s = Info_ValueForKey (userinfo, "name");
2838
2839 // fix player name if corrupted
2840 if ( s != NULL && s[0] )
2841 {
2842
2843 Q_strncpyz2( tmp_playername, s, sizeof(tmp_playername ) );
2844 ValidatePlayerName( tmp_playername, sizeof(tmp_playername ) );
2845 Q_strncpyz2(ent->client->pers.netname, tmp_playername,
2846 sizeof(ent->client->pers.netname) );
2847 }
2848
2849 // set skin
2850 s = Info_ValueForKey (userinfo, "skin");
2851
2852 copychar = false;
2853 strcpy(playerskin, " ");
2854 strcpy(playermodel, " ");
2855 j = k = 0;
2856 for(i = 0; i <= strlen(s) && i < MAX_OSPATH; i++)
2857 {
2858 if(copychar){
2859 playerskin[k] = s[i];
2860 k++;
2861 }
2862 else {
2863 playermodel[j] = s[i];
2864 j++;
2865 }
2866 if(s[i] == '/')
2867 copychar = true;
2868
2869 }
2870 playermodel[j] = 0;
2871
2872
2873 if( ent->dmteam == BLUE_TEAM)
2874 {
2875 if ( !ent->is_bot )
2876 safe_bprintf (PRINT_MEDIUM, "Joined Blue Team...\n");
2877 strcpy(playerskin, "blue");
2878 }
2879 else
2880 {
2881 if ( !ent->is_bot )
2882 safe_bprintf (PRINT_MEDIUM, "Joined Red Team...\n");
2883 strcpy(playerskin, "red");
2884 }
2885 if(strlen(playermodel) > 32) //something went wrong, or somebody is being malicious
2886 strcpy(playermodel, "martianenforcer/");
2887 strcpy(s, playermodel);
2888 strcat(s, playerskin);
2889 Info_SetValueForKey (userinfo, "skin", s);
2890
2891 playernum = ent-g_edicts-1;
2892
2893 // combine name and skin into a configstring
2894 gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) );
2895
2896 // fov
2897 ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
2898 if (ent->client->ps.fov < 1)
2899 ent->client->ps.fov = 90;
2900 else if (ent->client->ps.fov > 160)
2901 ent->client->ps.fov = 160;
2902
2903 // handedness
2904 s = Info_ValueForKey (userinfo, "hand");
2905 if (strlen(s))
2906 {
2907 ent->client->pers.hand = atoi(s);
2908 }
2909
2910 // save off the userinfo in case we want to check something later
2911 strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1);
2912
2913 }
2914
2915 /*
2916 ===========
2917 ClientConnect
2918
2919 Called when a player begins connecting to the server.
2920 The game can refuse entrance to a client by returning false.
2921 If the client is allowed, the connection process will continue
2922 and eventually get to ClientBegin()
2923 Changing levels will NOT cause this to be called again, but
2924 loadgames will.
2925 ============
2926 */
2927
ClientConnect(edict_t * ent,char * userinfo)2928 qboolean ClientConnect (edict_t *ent, char *userinfo)
2929 {
2930 char *value;
2931 int i, numspec;
2932
2933 // check to see if they are on the banned IP list
2934 value = Info_ValueForKey (userinfo, "ip");
2935 if (SV_FilterPacket(value)) {
2936 Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
2937 return false;
2938 }
2939
2940 //spectator mode
2941 // check for a spectator
2942
2943 value = Info_ValueForKey (userinfo, "spectator");
2944
2945 if ( TEAM_GAME && strcmp(value, "0") )
2946 { // for team game, force spectator off
2947 Info_SetValueForKey( userinfo, "spectator", "0" );
2948 ent->client->pers.spectator = 0;
2949 }
2950
2951 if (deathmatch->value && *value && strcmp(value, "0")) {
2952
2953 value = Info_ValueForKey( userinfo, "spectator_password" );
2954
2955 if (*spectator_password->string &&
2956 strcmp(spectator_password->string, "none") &&
2957 strcmp(spectator_password->string, value)) {
2958 Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect.");
2959 return false;
2960 }
2961
2962 // count spectators
2963 for (i = numspec = 0; i < g_maxclients->value; i++)
2964 if (g_edicts[i+1].inuse && g_edicts[i+1].client->pers.spectator)
2965 numspec++;
2966
2967 if (numspec >= maxspectators->value) {
2968 Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full.");
2969 return false;
2970 }
2971 } else if(!ent->is_bot){
2972 // check for a password
2973 value = Info_ValueForKey (userinfo, "password");
2974 if (*password->string && strcmp(password->string, "none") &&
2975 strcmp(password->string, value)) {
2976 Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect.");
2977 return false;
2978 }
2979 }
2980 //end specator mode
2981
2982 // they can connect
2983 ent->client = game.clients + (ent - g_edicts - 1);
2984
2985 // if there is already a body waiting for us (a loadgame), just
2986 // take it, otherwise spawn one from scratch
2987 if (ent->inuse == false)
2988 {
2989 // clear the respawning variables
2990 InitClientResp (ent->client);
2991 if (!game.autosaved || !ent->client->pers.weapon)
2992 InitClientPersistant (ent->client);
2993 }
2994
2995 // for real players in team games, team is to be selected
2996 ent->dmteam = NO_TEAM;
2997 ent->teamset = false;
2998
2999 ClientUserinfoChanged (ent, userinfo, CONNECT);
3000
3001 if (game.maxclients > 1)
3002 gi.dprintf ("%s connected\n", ent->client->pers.netname);
3003
3004 ent->client->pers.connected = true;
3005
3006 return true;
3007 }
3008
3009 /*
3010 ===========
3011 ClientDisconnect
3012
3013 Called when a player drops from the server.
3014 Will not be called between levels.
3015 ============
3016 */
ClientDisconnect(edict_t * ent)3017 void ClientDisconnect (edict_t *ent)
3018 {
3019 int playernum, i;
3020
3021 if (!ent->client)
3022 return;
3023
3024 safe_bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
3025
3026 if(ctf->value)
3027 { //if carrying flag, don't take it with you! no attacker points.
3028 CTFDeadDropFlag(ent, NULL);
3029 }
3030
3031 DeadDropDeathball(ent);
3032
3033 if(ent->deadflag && ent->client->chasetoggle == 1)
3034 DeathcamRemove(ent, "off");
3035
3036 //if in duel mode, we need to bump people down the queue if its the player in game leaving
3037 if(g_duel->integer) {
3038 MoveClientsDownQueue(ent);
3039 if(!ent->client->resp.spectator) {
3040 for (i = 0; i < g_maxclients->value; i++) //clear scores if player was in duel
3041 if(g_edicts[i+1].inuse && g_edicts[i+1].client && !g_edicts[i+1].is_bot)
3042 g_edicts[i+1].client->resp.score = 0;
3043 }
3044 }
3045 // send effect
3046 gi.WriteByte (svc_muzzleflash);
3047 gi.WriteShort (ent-g_edicts);
3048 gi.WriteByte (MZ_LOGOUT);
3049 gi.multicast (ent->s.origin, MULTICAST_PVS);
3050
3051 gi.unlinkentity (ent);
3052 ent->s.modelindex = 0;
3053 ent->s.effects = 0;
3054 ent->s.sound = 0;
3055 ent->solid = SOLID_NOT;
3056 ent->inuse = false;
3057 ent->classname = "disconnected";
3058 ent->client->pers.connected = false;
3059
3060 playernum = ent-g_edicts-1;
3061 gi.configstring (CS_PLAYERSKINS+playernum, "");
3062
3063 // if using bot thresholds, put the bot back in(duel always uses them)
3064 if ( sv_botkickthreshold->integer || g_duel->integer )
3065 {
3066 ACESP_LoadBots( ent );
3067 }
3068
3069 }
3070
3071
3072 //==============================================================
3073
3074
3075 edict_t *pm_passent;
3076
3077 // pmove doesn't need to know about passent and contentmask
PM_trace(vec3_t start,vec3_t mins,vec3_t maxs,vec3_t end)3078 trace_t PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
3079 {
3080 if (pm_passent->health > 0)
3081 return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
3082 else
3083 return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
3084 }
3085
CheckBlock(void * b,int c)3086 unsigned CheckBlock (void *b, int c)
3087 {
3088 int v,i;
3089 v = 0;
3090 for (i=0 ; i<c ; i++)
3091 v+= ((byte *)b)[i];
3092 return v;
3093 }
PrintPmove(pmove_t * pm)3094 void PrintPmove (pmove_t *pm)
3095 {
3096 unsigned c1, c2;
3097
3098 c1 = CheckBlock (&pm->s, sizeof(pm->s));
3099 c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd));
3100 Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2);
3101 }
3102
3103 /*
3104 ======
3105 TeamCensus
3106
3107 tallies up players and bots in game
3108 used by auto bot kick, and auto team selection
3109 implements team balancing rules
3110 ======
3111 */
TeamCensus(teamcensus_t * teamcensus)3112 void TeamCensus( teamcensus_t *teamcensus )
3113 {
3114 int i;
3115 int diff;
3116 int dmteam;
3117 int red_sum;
3118 int blue_sum;
3119
3120 int real_red = 0;
3121 int real_blue = 0;
3122 int bots_red = 0;
3123 int bots_blue = 0;
3124 int team_for_real = NO_TEAM;
3125 int team_for_bot = NO_TEAM;
3126
3127 for ( i = 1; i <= game.maxclients; i++ )
3128 { // count only clients that have joined teams
3129 if ( g_edicts[i].inuse )
3130 {
3131 dmteam = g_edicts[i].dmteam;
3132 if ( g_edicts[i].is_bot )
3133 {
3134 if ( dmteam == RED_TEAM )
3135 {
3136 ++bots_red;
3137 }
3138 else if ( dmteam == BLUE_TEAM )
3139 {
3140 ++bots_blue;
3141 }
3142 }
3143 else
3144 {
3145 if ( dmteam == RED_TEAM )
3146 {
3147 ++real_red;
3148 }
3149 else if ( dmteam == BLUE_TEAM )
3150 {
3151 ++real_blue;
3152 }
3153 }
3154 }
3155 }
3156 red_sum = real_red + bots_red;
3157 blue_sum = real_blue + bots_blue;
3158
3159 // team balancing rules
3160 if ( red_sum == blue_sum )
3161 { // teams are of equal size
3162 if ( bots_blue == bots_red )
3163 { // with equal number of both real players and bots
3164 // assign by scoring or random selection
3165 if ( red_team_score > blue_team_score )
3166 { // leader gets the bot
3167 // real player being a good sport joins the team that is behind
3168 if ( tca->integer )
3169 {
3170 team_for_bot = BLUE_TEAM;
3171 team_for_real = RED_TEAM;
3172 }
3173 else
3174 {
3175 team_for_bot = RED_TEAM;
3176 team_for_real = BLUE_TEAM;
3177 }
3178 }
3179 else if ( blue_team_score > red_team_score )
3180 {
3181 if ( tca->integer )
3182 {
3183 team_for_bot = RED_TEAM;
3184 team_for_real = BLUE_TEAM;
3185 }
3186 else
3187 {
3188 team_for_bot = BLUE_TEAM;
3189 team_for_real = RED_TEAM;
3190 }
3191 }
3192 else if ( rand() & 1 )
3193 {
3194 team_for_real = team_for_bot = RED_TEAM;
3195 }
3196 else
3197 {
3198 team_for_real = team_for_bot = BLUE_TEAM;
3199 }
3200 }
3201 else if ( bots_blue > bots_red )
3202 { // with more blue bots than red
3203 // put bot on team with fewer bots (red)
3204 // real player on team with fewer real players (blue)
3205 team_for_bot = RED_TEAM;
3206 team_for_real = BLUE_TEAM;
3207 }
3208 else
3209 { // with more red bots than blue
3210 // put bot on team with fewer bots (blue)
3211 // real player on team with fewer real players (red)
3212 team_for_bot = BLUE_TEAM;
3213 team_for_real = RED_TEAM;
3214 }
3215 }
3216 else
3217 { // teams are of unequal size
3218 diff = red_sum - blue_sum;
3219 if ( diff > 1 )
3220 { // red is 2 or more larger
3221 team_for_real = team_for_bot = BLUE_TEAM;
3222 }
3223 else if ( diff < -1 )
3224 { // blue is 2 or more larger
3225 team_for_real = team_for_bot = RED_TEAM;
3226 }
3227 else if ( real_blue == real_red )
3228 { // with equal numbers of real players
3229 if ( bots_blue > bots_red )
3230 { // blue team is larger by 1 bot
3231 team_for_real = team_for_bot = RED_TEAM;
3232 }
3233 else
3234 { // red_team is larger by 1 bot
3235 team_for_real = team_for_bot = BLUE_TEAM;
3236 }
3237 }
3238 else
3239 { // with equal numbers of bots
3240 if ( real_blue > real_red )
3241 { // blue team is larger by 1 real player
3242 team_for_real = team_for_bot = RED_TEAM;
3243 }
3244 else
3245 { // red team is larger by 1 real player
3246 team_for_real = team_for_bot = BLUE_TEAM;
3247 }
3248 }
3249 }
3250
3251 teamcensus->total = red_sum + blue_sum; // sum of all
3252 teamcensus->real = real_red + real_blue; // sum of real players
3253 teamcensus->bots = bots_red + bots_blue; // sum of bots
3254 teamcensus->red = real_red + bots_red; // sum of red team members
3255 teamcensus->blue = real_blue + bots_blue; // sum of blue team members
3256 teamcensus->real_red = real_red; // red team players in game
3257 teamcensus->real_blue = real_blue; // blue team players in game
3258 teamcensus->bots_red = bots_red; // red team bots in game
3259 teamcensus->bots_blue = bots_blue; // blue team bots in game
3260 teamcensus->team_for_real = team_for_real; // team for real player to join
3261 teamcensus->team_for_bot = team_for_bot; // team for bot to join
3262 }
3263
3264
3265 /** @brief Handle clients that are in "team selection" mode
3266 *
3267 * @todo this function needs cleaning up - if we set spectator mode to 0, we
3268 * can return from the function; if that is done, then we no longer need
3269 * to make sure that the client is not in a team in the rest of the
3270 * function.
3271 *
3272 * @param ent entity of the client
3273 * @param client the client itself
3274 * @param ucmd user command from the client
3275 */
ClientTeamSelection(edict_t * ent,gclient_t * client,usercmd_t * ucmd)3276 static inline void ClientTeamSelection( edict_t * ent ,
3277 gclient_t * client ,
3278 usercmd_t * ucmd )
3279 {
3280 teamcensus_t teamcensus;
3281
3282 if ( client->pers.spectator == 2 )
3283 { // entered with special team spectator mode on
3284 // force it off, does not appear to help much. (?)
3285 ent->dmteam = NO_TEAM;
3286 client->pers.spectator = 0;
3287 }
3288
3289 if ( ent->dmteam == RED_TEAM || ent->dmteam == BLUE_TEAM )
3290 {
3291 client->pers.spectator = 0;
3292 }
3293
3294 if ( level.time / 2 == ceil( level.time / 2 )
3295 && client->pers.spectator == 1
3296 && ent->dmteam == NO_TEAM ) {
3297 // send "how to join" message
3298 if ( g_autobalance->integer )
3299 {
3300 safe_centerprintf( ent,
3301 "\n\n\nPress <fire> to join\n"
3302 "autobalanced team\n" );
3303 }
3304 else
3305 {
3306 safe_centerprintf( ent,
3307 "\n\n\nPress <fire> to autojoin\n"
3308 "or <jump> to join BLUE\n"
3309 "or <crouch> to join RED\n" );
3310 }
3311 }
3312
3313 if ( client->latched_buttons & BUTTON_ATTACK )
3314 { // <fire> to auto join
3315 client->latched_buttons = 0;
3316 if ( client->pers.spectator == 1 && ent->dmteam == NO_TEAM)
3317 {
3318 TeamCensus( &teamcensus ); // apply team balance rules
3319 ent->dmteam = teamcensus.team_for_real;
3320 client->pers.spectator = 0;
3321 ClientChangeSkin( ent );
3322 }
3323 }
3324
3325 if ( ucmd->upmove >= 10 && ent->dmteam == NO_TEAM )
3326 {
3327 if ( !g_autobalance->integer )
3328 { // jump to join blue
3329 ent->dmteam = BLUE_TEAM;
3330 client->pers.spectator = 0;
3331 ClientChangeSkin( ent );
3332 }
3333 }
3334 else if ( ucmd->upmove < 0 && ent->dmteam == NO_TEAM )
3335 {
3336 if ( !g_autobalance->integer )
3337 { // crouch to join red
3338 ent->dmteam = RED_TEAM;
3339 client->pers.spectator = 0;
3340 ClientChangeSkin( ent );
3341 }
3342 }
3343 else
3344 {
3345 client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
3346 }
3347 }
3348
3349
3350 /*
3351 ==============
3352 ClientThink
3353
3354 This will be called once for each client frame, which will
3355 usually be a couple times for each server frame.
3356 ==============
3357 */
ClientThink(edict_t * ent,usercmd_t * ucmd)3358 void ClientThink (edict_t *ent, usercmd_t *ucmd)
3359 {
3360 gclient_t *client;
3361 edict_t *other;
3362 int i, j, mostvotes, n_candidates;
3363 int map_candidates[4];
3364 pmove_t pm;
3365 qboolean sproing, haste;
3366 vec3_t addspeed, forward, up, right;
3367
3368 level.current_entity = ent;
3369 client = ent->client;
3370
3371 // If the MOTD is being forced on, decrease frame counter
3372 // and re-send the file if necessary
3373 if ( client->motd_frames )
3374 {
3375 if ( level.time - floor( level.time ) != 0 )
3376 {
3377 SendMessageOfTheDay( ent );
3378 }
3379 client->motd_frames --;
3380 }
3381
3382 //unlagged
3383 if ( g_antilag->integer)
3384 client->attackTime = gi.Sys_Milliseconds();
3385
3386 if (level.intermissiontime)
3387 {
3388 client->ps.pmove.pm_type = PM_FREEZE;
3389
3390 // can exit intermission after 10 seconds, or 20 if map voting enables
3391 // (voting will only work if g_mapvote wasn't modified during intermission)
3392 if (g_mapvote->value && ! g_mapvote->modified)
3393 {
3394 //print out results, track winning map
3395 mostvotes = 0;
3396
3397 for (j = 0; j < 4; j++)
3398 {
3399 if (votedmap[j].tally > mostvotes)
3400 mostvotes = votedmap[j].tally;
3401 }
3402
3403 if ( g_voterand && g_voterand->value )
3404 {
3405 // we're using a random value for the next map
3406 // if a choice needs to be done
3407 n_candidates = 0;
3408 for ( j = 0 ; j < 4 ; j ++ )
3409 {
3410 if ( votedmap[j].tally < mostvotes )
3411 continue;
3412 map_candidates[n_candidates ++] = j;
3413 }
3414
3415 j = random() * (n_candidates - 1);
3416 level.changemap = votedmap[map_candidates[j]].mapname;
3417 }
3418 else
3419 {
3420 // "old" voting system, take the first map that
3421 // has enough votes
3422 for ( j = 0 ; j < 4 ; j ++ )
3423 {
3424 i = (j + 1) % 4;
3425 if ( votedmap[i].tally < mostvotes )
3426 continue;
3427 level.changemap = votedmap[i].mapname;
3428 break;
3429 }
3430 }
3431
3432 if (level.time > level.intermissiontime + 20.0
3433 && (ucmd->buttons & BUTTON_ANY) )
3434 level.exitintermission = true;
3435 }
3436 else
3437 {
3438 if (level.time > level.intermissiontime + 10.0
3439 && (ucmd->buttons & BUTTON_ANY) )
3440 level.exitintermission = true;
3441 }
3442 return;
3443 }
3444 else if ( g_mapvote && g_mapvote->modified )
3445 {
3446 g_mapvote->modified = false;
3447 }
3448
3449 pm_passent = ent;
3450
3451 if (ent->client->chase_target)
3452 {
3453 client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
3454 client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
3455 client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
3456
3457 } else
3458 {
3459 // set up for pmove
3460 memset (&pm, 0, sizeof(pm));
3461
3462 if (ent->movetype == MOVETYPE_NOCLIP)
3463 client->ps.pmove.pm_type = PM_SPECTATOR;
3464 else if (ent->s.modelindex != 255 && !(ent->in_vehicle) && !(client->chasetoggle)) //for vehicles or deathcam
3465 client->ps.pmove.pm_type = PM_GIB;
3466 else if (ent->deadflag)
3467 client->ps.pmove.pm_type = PM_DEAD;
3468 else
3469 client->ps.pmove.pm_type = PM_NORMAL;
3470
3471 if(!client->chasetoggle)
3472 {
3473 client->ps.pmove.gravity = sv_gravity->value;
3474 }
3475 else
3476 { /* No gravity to move the deathcam */
3477 client->ps.pmove.gravity = 0;
3478 }
3479
3480 //vehicles
3481 if ( Jet_Active(ent) )
3482 Jet_ApplyJet( ent, ucmd );
3483
3484 pm.s = client->ps.pmove;
3485
3486 for (i=0 ; i<3 ; i++)
3487 {
3488 pm.s.origin[i] = ent->s.origin[i]*8;
3489 pm.s.velocity[i] = ent->velocity[i]*8;
3490 }
3491
3492 if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
3493 {
3494 pm.snapinitial = true;
3495 }
3496
3497 if(g_tactical->integer)
3498 {
3499 //limit acceleration
3500 if(ucmd->forwardmove > 200)
3501 ucmd->forwardmove = 200;
3502 if(ucmd->forwardmove < -200)
3503 ucmd->forwardmove = -200;
3504 if(ucmd->sidemove > 200)
3505 ucmd->sidemove = 200;
3506 if(ucmd->sidemove < -200)
3507 ucmd->sidemove = -200;
3508 }
3509
3510 ucmd->forwardmove *= 1.3;
3511
3512 //tactical
3513 if(g_tactical->integer)
3514 {
3515 client->dodge = false;
3516 }
3517 else
3518 {
3519 //dodging
3520 client->dodge = false;
3521
3522 if((level.time - client->lastdodge) > 1.0 && ent->groundentity && ucmd->forwardmove == 0 && ucmd->sidemove != 0 && client->moved == false
3523 && client->keydown < 10 && ((level.time - client->lastmovetime) < .15))
3524 {
3525 if((ucmd->sidemove < 0 && client->lastsidemove < 0) || (ucmd->sidemove > 0 && client->lastsidemove > 0))
3526 {
3527 if(ucmd->sidemove > 0)
3528 client->dodge = 1;
3529 else
3530 client->dodge = -1;
3531 ucmd->upmove += 100;
3532 }
3533 }
3534 if((level.time - client->lastdodge) > 1.0 && ent->groundentity && ucmd->forwardmove != 0 && ucmd->sidemove == 0 && client->moved == false
3535 && client->keydown < 10 && ((level.time - client->lastmovetime) < .15))
3536 {
3537 if((ucmd->forwardmove < 0 && client->lastforwardmove < 0) || (ucmd->forwardmove > 0 && client->lastforwardmove > 0))
3538 {
3539 if(ucmd->forwardmove > 0)
3540 client->dodge = 2;
3541 else
3542 client->dodge = -2;
3543 ucmd->upmove += 100;
3544 }
3545 }
3546 }
3547
3548 //checking previous frame's movement
3549 if(client->moved == true && (ucmd->buttons & BUTTON_ANY))
3550 {
3551 client->keydown++;
3552 }
3553 else if (ucmd->sidemove != 0 || ucmd->forwardmove != 0)
3554 {
3555 client->keydown = 0;
3556 }
3557
3558 if(ucmd->sidemove != 0 || ucmd->forwardmove != 0)
3559 {
3560 client->lastmovetime = level.time;
3561 client->lastsidemove = ucmd->sidemove;
3562 client->lastforwardmove = ucmd->forwardmove;
3563 client->moved = true;
3564 }
3565 else //we had a frame with no movement
3566 client->moved = false;
3567
3568 pm.cmd = *ucmd;
3569
3570 pm.trace = PM_trace; // adds default parms
3571 pm.pointcontents = gi.pointcontents;
3572
3573 //joust mode
3574 if(joustmode->value)
3575 {
3576 if(ent->groundentity)
3577 client->joustattempts = 0;
3578 if(pm.cmd.upmove >= 10)
3579 {
3580 client->joustattempts++;
3581 pm.joustattempts = client->joustattempts;
3582 if(pm.joustattempts == 10 || pm.joustattempts == 20)
3583 {
3584 gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
3585 PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
3586 }
3587 }
3588 }
3589
3590 // perform a pmove
3591 gi.Pmove (&pm);
3592
3593 // save results of pmove
3594 client->ps.pmove = pm.s;
3595 client->old_pmove = pm.s;
3596
3597 for (i=0 ; i<3 ; i++)
3598 {
3599 ent->s.origin[i] = pm.s.origin[i]*0.125;
3600 ent->velocity[i] = pm.s.velocity[i]*0.125;
3601 }
3602
3603 //check for a dodge, and peform if true
3604 if(client->dodge != 0)
3605 {
3606 //check for dodge direction
3607 if(client->dodge == 2 || client->dodge == -2)
3608 {
3609 //was forward or backward
3610 AngleVectors (ent->s.angles, addspeed, right, up);
3611 client->dodge /= 2;
3612 }
3613 else
3614 {
3615 //was sideways
3616 AngleVectors (ent->s.angles, forward, addspeed, up);
3617 }
3618
3619 addspeed[0] *= 300*client->dodge;
3620 addspeed[1] *= 300*client->dodge;
3621
3622 VectorAdd(ent->velocity, addspeed, ent->velocity);
3623
3624 //check velocity
3625 SV_CheckVelocity(ent);
3626
3627 client->dodge = false;
3628 client->lastdodge = client->lastmovetime = level.time;
3629 }
3630
3631 VectorCopy (pm.mins, ent->mins);
3632 VectorCopy (pm.maxs, ent->maxs);
3633
3634 client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
3635 client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
3636 client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
3637
3638 //vehicles
3639 if ( Jet_Active(ent) )
3640 if( pm.groundentity ) /*are we on ground*/
3641 if ( Jet_AvoidGround(ent) ) /*then lift us if possible*/
3642 pm.groundentity = NULL; /*now we are no longer on ground*/
3643
3644 if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0))
3645 {
3646 sproing = client->sproing_framenum > level.framenum;
3647 haste = client->haste_framenum > level.framenum;
3648 if(sproing)
3649 {
3650 gi.sound(ent, CHAN_VOICE, gi.soundindex("items/sproing.wav"), 1, ATTN_NORM, 0);
3651 ent->velocity[2] += 400;
3652 }
3653 if(haste && ucmd->forwardmove > 0)
3654 {
3655 AngleVectors (ent->s.angles, addspeed, right, up);
3656 addspeed[0] *= 400;
3657 addspeed[1] *= 400;
3658 for(i = 0; i < 2; i++)
3659 {
3660 if(addspeed[i] > 200)
3661 addspeed[i] = 200;
3662 }
3663 VectorAdd(ent->velocity, addspeed, ent->velocity);
3664
3665 gi.sound(ent, CHAN_VOICE, gi.soundindex("items/haste.wav"), 1, ATTN_NORM, 0);
3666 gi.WriteByte (svc_temp_entity);
3667 gi.WriteByte (TE_EXPLOSION2);
3668 gi.WritePosition (ent->s.origin);
3669 gi.multicast (ent->s.origin, MULTICAST_PVS);
3670 }
3671 else
3672 gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
3673 PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
3674 }
3675
3676 ent->viewheight = pm.viewheight;
3677 ent->waterlevel = pm.waterlevel;
3678 ent->watertype = pm.watertype;
3679 ent->groundentity = pm.groundentity;
3680 if (pm.groundentity)
3681 ent->groundentity_linkcount = pm.groundentity->linkcount;
3682
3683 if (ent->deadflag)
3684 {
3685 client->ps.viewangles[ROLL] = 40;
3686 client->ps.viewangles[PITCH] = -15;
3687 client->ps.viewangles[YAW] = client->killer_yaw;
3688 }
3689 else
3690 {
3691 VectorCopy (pm.viewangles, client->v_angle);
3692 VectorCopy (pm.viewangles, client->ps.viewangles);
3693 }
3694
3695 if (client->ctf_grapple)
3696 CTFGrapplePull(client->ctf_grapple);
3697
3698 gi.linkentity (ent);
3699
3700 if (ent->movetype != MOVETYPE_NOCLIP)
3701 G_TouchTriggers (ent);
3702
3703 // touch other objects
3704 for (i=0 ; i<pm.numtouch ; i++)
3705 {
3706 other = pm.touchents[i];
3707 for (j=0 ; j<i ; j++)
3708 if (pm.touchents[j] == other)
3709 break;
3710 if (j != i)
3711 continue; // duplicated
3712 if (!other->touch)
3713 continue;
3714 other->touch (other, ent, NULL, NULL);
3715 }
3716 }
3717
3718 client->oldbuttons = client->buttons;
3719 client->buttons = ucmd->buttons;
3720 client->latched_buttons |= client->buttons & ~client->oldbuttons;
3721
3722 // save light level the player is standing on for
3723 // monster sighting AI
3724 ent->light_level = ucmd->lightlevel;
3725
3726 if ( client->resp.spectator == 0 )
3727 { // regular (non-spectator) mode
3728 if (client->latched_buttons & BUTTON_ATTACK)
3729 {
3730 if (!client->weapon_thunk)
3731 {
3732 client->weapon_thunk = true;
3733 Think_Weapon (ent);
3734 }
3735 }
3736 }
3737 else
3738 { // spectator mode
3739 if ( TEAM_GAME && client->resp.spectator < 2 )
3740 { // team selection state
3741 ClientTeamSelection( ent , client , ucmd );
3742 } /* team selection state */
3743 else
3744 { // regular spectator
3745 if ( client->latched_buttons & BUTTON_ATTACK )
3746 {
3747 client->latched_buttons = 0;
3748 if ( client->chase_target )
3749 {
3750 client->chase_target = NULL;
3751 client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
3752 }
3753 else
3754 {
3755 GetChaseTarget( ent );
3756 }
3757 }
3758 if ( ucmd->upmove >= 10 )
3759 {
3760 if ( !(client->ps.pmove.pm_flags & PMF_JUMP_HELD) )
3761 {
3762 client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
3763 if ( client->chase_target )
3764 ChaseNext( ent );
3765 else
3766 GetChaseTarget( ent );
3767 }
3768 }
3769 else
3770 {
3771 client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
3772 }
3773 } /* regular spectator */
3774 } /* spectator mode */
3775
3776 // update chase cam if being followed
3777 for (i = 1; i <= g_maxclients->value; i++)
3778 {
3779 other = g_edicts + i;
3780 if (other->inuse && other->client->chase_target == ent)
3781 UpdateChaseCam(other);
3782 }
3783
3784 //mutators
3785 if((regeneration->value || excessive->value) && !ent->deadflag)
3786 {
3787 if((ent->health < ent->max_health) && (client->regen_framenum < level.framenum))
3788 {
3789 client->regen_framenum = level.framenum + 5;
3790 ent->health+=2;
3791 }
3792 }
3793
3794 //spawn protection has run out
3795 if(level.time > ent->client->spawnprotecttime + g_spawnprotect->integer)
3796 ent->client->spawnprotected = false;
3797
3798 //lose one health every second
3799 if(g_losehealth->value && !ent->deadflag)
3800 {
3801 if(regeneration->value || excessive->value || vampire->value)
3802 return;
3803 if((ent->health > g_losehealth_num->value) && (client->losehealth_framenum < level.framenum))
3804 {
3805 client->losehealth_framenum = level.framenum + 10;
3806 ent->health-=1;
3807 }
3808 }
3809 }
3810
3811
3812 /** @brief Update the anti-camp timeout
3813 *
3814 * The anti-camp computation accumulates players' velocities, averages them
3815 * and updates the "suicide_timeout" field if the length of that average
3816 * vector is above some threshold.
3817 *
3818 * It is controlled by the camptime, ac_frames and ac_threshold CVars.
3819 */
_UpdateAntiCamp(edict_t * ent)3820 static inline void _UpdateAntiCamp( edict_t * ent )
3821 {
3822 int n_frames = ac_frames->integer;
3823 float thresh;
3824 vec3_t avg;
3825
3826 // Make sure we have a valid ac_frames
3827 if ( n_frames < 1 || n_frames > 100 ) {
3828 n_frames = G_ANTICAMP_FRAMES;
3829 }
3830
3831 if ( ent->old_velocities_count < n_frames ) {
3832 // Not enough frames yet
3833 ent->old_velocities_count ++;
3834 ent->old_velocities_current ++;
3835 } else {
3836 // Enough frames - remove oldest known velocity from
3837 // accumulator
3838 ent->old_velocities_current = ( ent->old_velocities_current
3839 + 1 ) % n_frames;
3840 VectorSubtract( ent->velocity_accum ,
3841 ent->old_velocities[ ent->old_velocities_current ] ,
3842 ent->velocity_accum );
3843 }
3844
3845 // Store current velocity into history and add its value to the
3846 // accumulator
3847 VectorCopy( ent->velocity ,
3848 ent->old_velocities[ ent->old_velocities_current ] );
3849 VectorAdd( ent->velocity_accum , ent->velocity , ent->velocity_accum );
3850 if ( ent->old_velocities_count < n_frames ) {
3851 return;
3852 }
3853
3854 // Get and adjust speed threshold
3855 thresh = ac_threshold->value;
3856 if ( thresh <= 0 || thresh > 500 ) {
3857 thresh = G_ANTICAMP_THRESHOLD;
3858 }
3859 if ( excessive->integer ) {
3860 thresh *= 1.5;
3861 }
3862
3863 // Check average velocity lengths against threshold
3864 VectorCopy( ent->velocity_accum , avg );
3865 avg[0] /= n_frames; avg[1] /= n_frames; avg[2] /= n_frames;
3866 if ( VectorLength( avg ) > thresh ) {
3867 ent->suicide_timeout = level.time + camptime->integer;
3868 }
3869
3870 // Inflict anti-camp damage
3871 if ( ent->suicide_timeout < level.time && ent->takedamage == DAMAGE_AIM
3872 && ! ent->client->resp.spectator) {
3873 T_Damage (ent, world, world, vec3_origin, ent->s.origin,
3874 vec3_origin, ent->dmg, 0, DAMAGE_NO_ARMOR,
3875 MOD_SUICIDE);
3876 safe_centerprintf(ent, "Anticamp: move or die!\n");
3877 }
3878 }
3879
3880
3881 /*
3882 ==============
3883 ClientBeginServerFrame
3884
3885 This will be called once for each server frame, before running
3886 any other entities in the world.
3887 ==============
3888 */
ClientBeginServerFrame(edict_t * ent)3889 void ClientBeginServerFrame (edict_t *ent)
3890 {
3891 gclient_t *client;
3892 int buttonMask;
3893
3894 if (level.intermissiontime)
3895 return;
3896
3897 client = ent->client;
3898
3899 //spectator mode
3900 if ( deathmatch->value && client->pers.spectator != client->resp.spectator
3901 && (level.time - client->respawn_time) >= 5 )
3902 {
3903 if ( TEAM_GAME && client->pers.spectator == 2 )
3904 { // special team game spectator mode (has problems)
3905 if ( client->resp.spectator == 1 )
3906 { // special team spectator mode
3907 spectator_respawn( ent );
3908 client->resp.spectator = 2;
3909 }
3910 }
3911 else if ( TEAM_GAME && client->resp.spectator == 2 )
3912 { // pers != resp, exit spectator mode not allowed
3913 client->pers.spectator = 2;
3914 }
3915 else
3916 { // enter or exit spectator mode
3917 spectator_respawn( ent );
3918 }
3919 return;
3920 }
3921 //end spectator mode
3922
3923 //anti-camp
3924 // do not apply to godmode cheat or bots.
3925 // bots have other suicidal tendencies which may (or may not) conflict.
3926 if ( anticamp->integer && !(ent->flags & FL_GODMODE) && !(ent->is_bot) )
3927 {
3928 _UpdateAntiCamp( ent );
3929 }
3930
3931 //spectator mode
3932
3933 if (!client->weapon_thunk && !client->resp.spectator)
3934 //end spectator mode
3935
3936 Think_Weapon (ent);
3937 else
3938 client->weapon_thunk = false;
3939
3940 if (ent->deadflag)
3941 {
3942 // wait for any button just going down
3943 if ( level.time > client->respawn_time)
3944 {
3945 // in deathmatch, only wait for attack button
3946 if (deathmatch->value)
3947 buttonMask = BUTTON_ATTACK | BUTTON_ATTACK2;
3948 else
3949 buttonMask = -1;
3950
3951 //should probably add in a force respawn option
3952 if (( client->latched_buttons & buttonMask ) ||
3953 (deathmatch->value && (dmflags->integer & DF_FORCE_RESPAWN) ) )
3954 {
3955
3956 if(!ent->is_bot)
3957 DeathcamRemove (ent, "off");
3958
3959 respawn(ent);
3960 client->latched_buttons = 0;
3961 }
3962 }
3963 return;
3964 }
3965
3966 // add player trail so monsters can follow
3967 if (!deathmatch->value)
3968 if (!visible (ent, PlayerTrail_LastSpot() ) )
3969 PlayerTrail_Add (ent->s.old_origin);
3970
3971 client->latched_buttons = 0;
3972 }
3973