1 /*
2 * Copyright(c) 1997-2001 Id Software, Inc.
3 * Copyright(c) 2002 The Quakeforge Project.
4 * Copyright(c) 2006 Quetoo.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or(at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 *
15 * See the GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21
22 #include "g_local.h"
23 #include "m_player.h"
24
25 void ClientUserinfoChanged(edict_t *ent, char *userinfo);
26
27 void SP_misc_teleporter_dest(edict_t *ent);
28
29 /*QUAKED info_player_start(1 0 0)(-16 -16 -24)(16 16 32)
30 The normal starting point for a level.
31 */
SP_info_player_start(edict_t * self)32 void SP_info_player_start(edict_t *self){}
33
34 /*QUAKED info_player_deathmatch(1 0 1)(-16 -16 -24)(16 16 32)
35 potential spawning position for deathmatch games
36 */
SP_info_player_deathmatch(edict_t * self)37 void SP_info_player_deathmatch(edict_t *self){
38 SP_misc_teleporter_dest(self);
39 }
40
41 /*QUAKED info_player_coop(1 0 1)(-16 -16 -24)(16 16 32)
42 potential spawning position for coop games
43 */
44
SP_info_player_coop(edict_t * self)45 void SP_info_player_coop(edict_t *self){
46 G_FreeEdict(self);
47 }
48
49
50 /*QUAKED info_player_intermission(1 0 1)(-16 -16 -24)(16 16 32)
51 The deathmatch intermission point will be at one of these
52 Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll'
53 */
SP_info_player_intermission(void)54 void SP_info_player_intermission(void){}
55
player_pain(edict_t * self,edict_t * other,float kick,int damage)56 void player_pain(edict_t *self, edict_t *other, float kick, int damage){}
57
IsFemale(edict_t * ent)58 qboolean IsFemale(edict_t *ent){
59 char *info;
60
61 if(!ent->client)
62 return false;
63
64 info = Info_ValueForKey(ent->client->pers.userinfo, "gender");
65 if(info[0] == 'f' || info[0] == 'F')
66 return true;
67 return false;
68 }
69
IsNeutral(edict_t * ent)70 qboolean IsNeutral(edict_t *ent){
71 char *info;
72
73 if(!ent->client)
74 return false;
75
76 info = Info_ValueForKey(ent->client->pers.userinfo, "gender");
77 if(info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M')
78 return true;
79 return false;
80 }
81
ClientObituary(edict_t * self,edict_t * inflictor,edict_t * attacker)82 void ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker){
83 int mod;
84 char *message;
85 char *message2;
86 qboolean ff;
87
88 char *killer;
89
90 ff = meansOfDeath & MOD_FRIENDLY_FIRE;
91 mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
92 message = NULL;
93 message2 = "";
94
95 killer = attacker->client ? attacker->client->pers.netname :
96 self->client->pers.netname; //resolve killer string
97
98 if(fraglog != NULL){ //write fraglog
99
100 fprintf(fraglog, "\\%s\\%s\\\n", killer, self->client->pers.netname);
101
102 fflush(fraglog);
103 }
104
105 #ifdef HAVE_MYSQL
106 if(mysql != NULL){ //insert to db
107
108 snprintf(sql, sizeof(sql) - 1, "insert into frag values(null, now(), '%s', '%s', '%s', %d)",
109 level.mapname, killer, self->client->pers.netname, mod
110 );
111
112 sql[sizeof(sql) - 1] = 0;
113 mysql_query(mysql, sql);
114 }
115 #endif
116
117 switch(mod){
118 case MOD_SUICIDE:
119 message = "sucks at life";
120 break;
121 case MOD_FALLING:
122 message = "challenged gravity";
123 break;
124 case MOD_CRUSH:
125 message = "likes it tight";
126 break;
127 case MOD_WATER:
128 message = "took a drink";
129 break;
130 case MOD_SLIME:
131 message = "got slimed";
132 break;
133 case MOD_LAVA:
134 message = "did a back flip into the lava";
135 break;
136 case MOD_EXPLOSIVE:
137 case MOD_BARREL:
138 message = "went pop";
139 break;
140 case MOD_EXIT:
141 message = "found a way out";
142 break;
143 case MOD_TARGET_LASER:
144 message = "saw the light";
145 break;
146 case MOD_TARGET_BLASTER:
147 message = "got blasted";
148 break;
149 case MOD_BOMB:
150 case MOD_SPLASH:
151 case MOD_TRIGGER_HURT:
152 message = "was in the wrong place";
153 break;
154 }
155
156 if(attacker == self){
157 switch(mod){
158 case MOD_HELD_GRENADE:
159 message = "couldn't let go";
160 break;
161 case MOD_HG_SPLASH:
162 case MOD_G_SPLASH:
163 message = "went pop";
164 break;
165 case MOD_R_SPLASH:
166 message = "needs glasses";
167 break;
168 case MOD_BFG_BLAST:
169 message = "should have used a smaller gun";
170 break;
171 default:
172 message = "sucks at life";
173 break;
174 }
175 }
176
177 if(message){ // suicide
178 gi.bprintf(PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message);
179 self->client->resp.score--;
180
181 if(teams->value && self->client->pers.team)
182 self->client->pers.team->score--;
183
184 self->enemy = NULL;
185 return;
186 }
187
188 self->enemy = attacker;
189 if(attacker && attacker->client){
190 switch(mod){
191 case MOD_BLASTER:
192 message = "was humiliated by";
193 break;
194 case MOD_SHOTGUN:
195 message = "was gunned down by";
196 message2 = "'s pea shooter";
197 break;
198 case MOD_SSHOTGUN:
199 message = "was blown away by";
200 message2 = "'s super shotgun";
201 break;
202 case MOD_MACHINEGUN:
203 message = "was chewed up by";
204 break;
205 case MOD_CHAINGUN:
206 message = "was cut in half by";
207 message2 = "'s chaingun";
208 break;
209 case MOD_GRENADE:
210 message = "was popped by";
211 message2 = "'s grenade";
212 break;
213 case MOD_G_SPLASH:
214 message = "was shredded by";
215 message2 = "'s shrapnel";
216 break;
217 case MOD_ROCKET:
218 message = "was dry-anal-powerslammed by";
219 message2 = "'s rocket";
220 break;
221 case MOD_R_SPLASH:
222 message = "almost dodged";
223 message2 = "'s rocket";
224 break;
225 case MOD_HYPERBLASTER:
226 message = "was melted by";
227 message2 = "'s hyperblaster";
228 break;
229 case MOD_RAILGUN:
230 message = "was poked by";
231 message2 = "'s needledick";
232 break;
233 case MOD_BFG_LASER:
234 message = "saw the pretty lights from";
235 message2 = "'s BFG";
236 break;
237 case MOD_BFG_BLAST:
238 message = "was disintegrated by";
239 message2 = "'s BFG blast";
240 break;
241 case MOD_BFG_EFFECT:
242 message = "couldn't hide from";
243 message2 = "'s BFG";
244 break;
245 case MOD_HANDGRENADE:
246 message = "catches for";
247 message2 = "'s team";
248 break;
249 case MOD_HG_SPLASH:
250 message = "didn't see";
251 message2 = "'s handgrenade";
252 break;
253 case MOD_HELD_GRENADE:
254 message = "feels";
255 message2 = "'s pain";
256 break;
257 case MOD_TELEFRAG:
258 message = "tried to invade";
259 message2 = "'s personal space";
260 break;
261 case MOD_HOOK:
262 message = "was snared by";
263 message2 = "'s hook";
264 break;
265 }
266
267 if(message){
268
269 gi.bprintf(PRINT_MEDIUM, "%s%s %s %s%s\n", (ff ? "***TEAMKILL*** " : ""),
270 self->client->pers.netname, message,
271 attacker->client->pers.netname, message2
272 );
273
274 if(ff) attacker->client->resp.score--;
275 else attacker->client->resp.score++;
276
277 if(teams->value && attacker->client->pers.team){ // handle team scores too
278 if(ff) attacker->client->pers.team->score--;
279 else attacker->client->pers.team->score++;
280 }
281 }
282 }
283 }
284
285
286 void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
287
TossClientWeapon(edict_t * self)288 void TossClientWeapon(edict_t *self){
289 gitem_t *item;
290 edict_t *drop;
291 qboolean quad;
292 float spread;
293
294 item = self->client->pers.weapon;
295 if(!self->client->pers.inventory[self->client->ammo_index])
296 item = NULL;
297 if(item &&(strcmp(item->pickup_name, "Blaster") == 0))
298 item = NULL;
299
300 if(!((int)(dmflags->value) & DF_QUAD_DROP))
301 quad = false;
302 else
303 quad = (self->client->quad_framenum >(level.framenum + 10));
304
305 if(item && quad)
306 spread = 22.5;
307 else
308 spread = 0.0;
309
310 if(item){
311 self->client->v_angle[YAW] -= spread;
312 drop = Drop_Item(self, item);
313 self->client->v_angle[YAW] += spread;
314 drop->spawnflags = DROPPED_PLAYER_ITEM;
315 }
316
317 if(quad){
318 self->client->v_angle[YAW] += spread;
319 drop = Drop_Item(self, FindItemByClassname("item_quad"));
320 self->client->v_angle[YAW] -= spread;
321 drop->spawnflags |= DROPPED_PLAYER_ITEM;
322
323 drop->touch = Touch_Item;
324 drop->nextthink = level.time +(self->client->quad_framenum - level.framenum) * FRAMETIME;
325 drop->think = G_FreeEdict;
326 }
327 }
328
329
330 /*
331 LookAtKiller
332 */
LookAtKiller(edict_t * self,edict_t * inflictor,edict_t * attacker)333 void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker){
334 vec3_t dir;
335
336 if(attacker && attacker != world && attacker != self){
337 VectorSubtract(attacker->s.origin, self->s.origin, dir);
338 } else if(inflictor && inflictor != world && inflictor != self){
339 VectorSubtract(inflictor->s.origin, self->s.origin, dir);
340 } else {
341 self->client->killer_yaw = self->s.angles[YAW];
342 return;
343 }
344
345 if(dir[0])
346 self->client->killer_yaw = 180 / M_PI * atan2(dir[1], dir[0]);
347 else {
348 self->client->killer_yaw = 0;
349 if(dir[1] > 0)
350 self->client->killer_yaw = 90;
351 else if(dir[1] < 0)
352 self->client->killer_yaw = -90;
353 }
354 if(self->client->killer_yaw < 0)
355 self->client->killer_yaw += 360;
356 }
357
358 /*
359 player_die
360 */
player_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)361 void player_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point){
362 int n;
363
364 VectorClear(self->avelocity);
365
366 self->takedamage = DAMAGE_YES;
367 self->movetype = MOVETYPE_TOSS;
368
369 self->s.modelindex2 = 0; // remove linked weapon model
370
371 self->s.angles[0] = 0;
372 self->s.angles[2] = 0;
373
374 self->s.sound = 0;
375 self->client->weapon_sound = 0;
376
377 self->maxs[2] = -8;
378
379 self->svflags |= SVF_DEADMONSTER;
380
381 if(!self->deadflag){
382 self->client->respawn_time = level.time + 1.0;
383 LookAtKiller(self, inflictor, attacker);
384 self->client->ps.pmove.pm_type = PM_DEAD;
385 ClientObituary(self, inflictor, attacker);
386 if(!gameplay->value) // drop weapon in dm
387 TossClientWeapon(self);
388 Cmd_Score_f(self); // show scores
389 }
390
391 // remove powerups
392 self->client->quad_framenum = 0;
393 self->client->invincible_framenum = 0;
394 self->client->breather_framenum = 0;
395 self->client->enviro_framenum = 0;
396 self->flags &= ~FL_POWER_ARMOR;
397
398 if(self->health < -40){ // gib
399 gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
400 for(n = 0; n < 4; n++)
401 ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
402 ThrowClientHead(self, damage);
403
404 self->takedamage = DAMAGE_NO;
405 } else { // normal death
406 if(!self->deadflag){
407 static int i;
408
409 i = (i + 1) % 3;
410 // start a death animation
411 self->client->anim_priority = ANIM_DEATH;
412 if(self->client->ps.pmove.pm_flags & PMF_DUCKED){
413 self->s.frame = FRAME_crdeath1 - 1;
414 self->client->anim_end = FRAME_crdeath5;
415 } else
416 switch(i){
417 case 0:
418 self->s.frame = FRAME_death101 - 1;
419 self->client->anim_end = FRAME_death106;
420 break;
421 case 1:
422 self->s.frame = FRAME_death201 - 1;
423 self->client->anim_end = FRAME_death206;
424 break;
425 case 2:
426 self->s.frame = FRAME_death301 - 1;
427 self->client->anim_end = FRAME_death308;
428 break;
429 }
430 gi.sound(self, CHAN_VOICE, gi.soundindex(va("*death%i.wav",(rand() % 4) + 1)), 1, ATTN_NORM, 0);
431 }
432 }
433
434 hook_reset(self->client->hook); // hook
435
436 self->deadflag = DEAD_DEAD;
437
438 gi.linkentity(self);
439 }
440
441 /*
442 * Stocks client's inventory with specified item. Weapons receive
443 * full ammo, while powerups are set to a reasonable quantiy.
444 */
Give(gclient_t * client,char * it)445 void Give(gclient_t *client, char *it){
446 gitem_t *item;
447 int index;
448
449 item = FindItem(it);
450 index = ITEM_INDEX(item);
451
452 if(item->flags & IT_WEAPON){ //give ammo too
453 client->pers.inventory[index] = 1;
454
455 item = FindItem(item->ammo);
456 index = ITEM_INDEX(item);
457
458 client->pers.inventory[index] = 1000;
459 return;
460 }
461
462 client->pers.inventory[index] = 200;
463 }
464
465
466 /*
467 InitClientPersistant
468
469 This is only called when the game first initializes in single player,
470 but is called after each death and level change in deathmatch
471 */
InitClientPersistant(gclient_t * client)472 void InitClientPersistant(gclient_t *client){
473 gitem_t *item;
474 char skin[16];
475 team_t *team;
476 vote_t vote;
477
478 strncpy(skin, client->pers.skin, sizeof(skin) - 1); //retain skin
479 skin[sizeof(skin) - 1] = 0;
480
481 team = client->pers.team;
482 vote = client->pers.vote;
483
484 memset(&client->pers, 0, sizeof(client->pers));
485
486 if((int)gameplay->value & INSTAGIB){ //railgun and slugs
487 Give(client, "Railgun");
488 item = FindItem("Railgun");
489 }
490 else if((int)gameplay->value & ROCKET_ARENA){ //all weapons, ammo, health..
491 Give(client, "Railgun");
492 Give(client, "HyperBlaster");
493 Give(client, "Rocket Launcher");
494 Give(client, "Grenade Launcher");
495 Give(client, "Chaingun");
496 Give(client, "Machinegun");
497 Give(client, "Super Shotgun");
498 Give(client, "Shotgun");
499
500 Give(client, "Body Armor");
501
502 item = FindItem("Rocket Launcher");
503 }
504 else item = FindItem("Blaster");
505
506 client->pers.selected_item = ITEM_INDEX(item);
507 client->pers.inventory[client->pers.selected_item] = 1;
508
509 client->pers.weapon = item;
510
511 if((int)gameplay->value & ROCKET_ARENA){ //200 health for arena
512 client->pers.health = 200;
513 client->pers.max_health = 200;
514 } else { //100 the rest of the time
515 client->pers.health = 100;
516 client->pers.max_health = 100;
517 }
518
519 client->pers.max_bullets = 200;
520 client->pers.max_shells = 100;
521 client->pers.max_rockets = 50;
522 client->pers.max_grenades = 50;
523 client->pers.max_cells = 200;
524 client->pers.max_slugs = 50;
525
526 strcpy(client->pers.skin, skin);
527 client->pers.team = team;
528 client->pers.vote = vote;
529 }
530
531
InitClientResp(gclient_t * client)532 void InitClientResp(gclient_t *client){
533 memset(&client->resp, 0, sizeof(client->resp));
534 client->resp.enterframe = level.framenum;
535 }
536
537 /*
538 SaveClientData
539
540 Some information that should be persistant, like health,
541 is still stored in the edict structure, so it needs to
542 be mirrored out to the client structure before all the
543 edicts are wiped.
544 */
SaveClientData(void)545 void SaveClientData(void){
546 int i;
547 edict_t *ent;
548
549 for(i = 0; i < maxclients->value; i++){
550 ent = &g_edicts[1 + i];
551 if(!ent->inuse)
552 continue;
553 game.clients[i].pers.health = ent->health;
554 game.clients[i].pers.max_health = ent->max_health;
555 game.clients[i].pers.savedFlags =(ent->flags &(FL_GODMODE | FL_NOTARGET | FL_POWER_ARMOR));
556 }
557 }
558
FetchClientEntData(edict_t * ent)559 void FetchClientEntData(edict_t *ent){
560 ent->health = ent->client->pers.health;
561 ent->max_health = ent->client->pers.max_health;
562 ent->flags |= ent->client->pers.savedFlags;
563 }
564
565
566
567 /*
568
569 SelectSpawnPoint
570
571 */
572
573 /*
574 PlayersRangeFromSpot
575
576 Returns the distance to the nearest player from the given spot
577 */
PlayersRangeFromSpot(edict_t * spot)578 float PlayersRangeFromSpot(edict_t *spot){
579 edict_t *player;
580 float bestplayerdistance;
581 vec3_t v;
582 int n;
583 float playerdistance;
584
585
586 bestplayerdistance = 9999999;
587
588 for(n = 1; n <= maxclients->value; n++){
589 player = &g_edicts[n];
590
591 if(!player->inuse)
592 continue;
593
594 if(player->health <= 0)
595 continue;
596
597 VectorSubtract(spot->s.origin, player->s.origin, v);
598 playerdistance = VectorLength(v);
599
600 if(playerdistance < bestplayerdistance)
601 bestplayerdistance = playerdistance;
602 }
603
604 return bestplayerdistance;
605 }
606
607 /*
608 SelectRandomDeathmatchSpawnPoint
609
610 go to a random point, but NOT the two points closest
611 to other players
612 */
SelectRandomDeathmatchSpawnPoint(void)613 edict_t *SelectRandomDeathmatchSpawnPoint(void){
614 edict_t *spot, *spot1, *spot2;
615 int count = 0;
616 int selection;
617 float range, range1, range2;
618
619 spot = NULL;
620 range1 = range2 = 99999;
621 spot1 = spot2 = NULL;
622
623 while((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL){
624 count++;
625 range = PlayersRangeFromSpot(spot);
626 if(range < range1){
627 range1 = range;
628 spot1 = spot;
629 } else if(range < range2){
630 range2 = range;
631 spot2 = spot;
632 }
633 }
634
635 if(!count)
636 return NULL;
637
638 if(count <= 2){
639 spot1 = spot2 = NULL;
640 } else
641 count -= 2;
642
643 selection = rand() % count;
644
645 spot = NULL;
646 do {
647 spot = G_Find(spot, FOFS(classname), "info_player_deathmatch");
648 if(spot == spot1 || spot == spot2)
649 selection++;
650 } while(selection--);
651
652 return spot;
653 }
654
655 /*
656 SelectFarthestDeathmatchSpawnPoint
657
658 */
SelectFarthestDeathmatchSpawnPoint(void)659 edict_t *SelectFarthestDeathmatchSpawnPoint(void){
660 edict_t *bestspot;
661 float bestdistance, bestplayerdistance;
662 edict_t *spot;
663
664
665 spot = NULL;
666 bestspot = NULL;
667 bestdistance = 0;
668 while((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL){
669 bestplayerdistance = PlayersRangeFromSpot(spot);
670
671 if(bestplayerdistance > bestdistance){
672 bestspot = spot;
673 bestdistance = bestplayerdistance;
674 }
675 }
676
677 if(bestspot){
678 return bestspot;
679 }
680
681 // if there is a player just spawned on each and every start spot
682 // we have no choice to turn one into a telefrag meltdown
683 spot = G_Find(NULL, FOFS(classname), "info_player_deathmatch");
684
685 return spot;
686 }
687
SelectDeathmatchSpawnPoint(void)688 edict_t *SelectDeathmatchSpawnPoint(void){
689 if((int)(dmflags->value) & DF_SPAWN_FARTHEST)
690 return SelectFarthestDeathmatchSpawnPoint();
691 else return SelectRandomDeathmatchSpawnPoint();
692 }
693
694
SelectCoopSpawnPoint(edict_t * ent)695 edict_t *SelectCoopSpawnPoint(edict_t *ent){
696 return NULL;
697 }
698
699
700 /*
701 SelectSpawnPoint
702
703 Chooses a player start, deathmatch start, coop start, etc
704 */
SelectSpawnPoint(edict_t * ent,vec3_t origin,vec3_t angles)705 void SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles){
706 edict_t *spot = NULL;
707
708 spot = SelectDeathmatchSpawnPoint();
709
710 // find a single player start spot
711 if(!spot){
712 while((spot = G_Find(spot, FOFS(classname), "info_player_start")) != NULL){
713 if(!game.spawnpoint[0] && !spot->targetname)
714 break;
715
716 if(!game.spawnpoint[0] || !spot->targetname)
717 continue;
718
719 if(Q_stricmp(game.spawnpoint, spot->targetname) == 0)
720 break;
721 }
722
723 if(!spot){
724 if(!game.spawnpoint[0]){ // there wasn't a spawnpoint without a target, so use any
725 spot = G_Find(spot, FOFS(classname), "info_player_start");
726 }
727 if(!spot)
728 gi.error("Couldn't find spawn point %s\n", game.spawnpoint);
729 }
730 }
731
732 VectorCopy(spot->s.origin, origin);
733 origin[2] += 9;
734 VectorCopy(spot->s.angles, angles);
735 }
736
737
738
InitBodyQue(void)739 void InitBodyQue(void){
740 int i;
741 edict_t *ent;
742
743 level.body_que = 0;
744 for(i = 0; i < BODY_QUEUE_SIZE; i++){
745 ent = G_Spawn();
746 ent->classname = "bodyque";
747 }
748 }
749
body_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)750 void body_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point){
751 int n;
752
753 if(self->health < -40){
754 gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
755 for(n = 0; n < 4; n++)
756 ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
757 self->s.origin[2] -= 48;
758 ThrowClientHead(self, damage);
759 self->takedamage = DAMAGE_NO;
760 }
761 }
762
CopyToBodyQue(edict_t * ent)763 void CopyToBodyQue(edict_t *ent){
764 edict_t *body;
765
766 // grab a body que and cycle to the next one
767 body = &g_edicts[(int)maxclients->value + level.body_que + 1];
768 level.body_que =(level.body_que + 1) % BODY_QUEUE_SIZE;
769
770 // FIXME: send an effect on the removed body
771
772 gi.unlinkentity(ent);
773
774 gi.unlinkentity(body);
775 body->s = ent->s;
776 body->s.number = body - g_edicts;
777
778 body->svflags = ent->svflags;
779 VectorCopy(ent->mins, body->mins);
780 VectorCopy(ent->maxs, body->maxs);
781 VectorCopy(ent->absmin, body->absmin);
782 VectorCopy(ent->absmax, body->absmax);
783 VectorCopy(ent->size, body->size);
784 body->solid = ent->solid;
785 body->clipmask = ent->clipmask;
786 body->owner = ent->owner;
787 body->movetype = ent->movetype;
788
789 body->die = body_die;
790 body->takedamage = DAMAGE_YES;
791
792 gi.linkentity(body);
793 }
794
795
respawn(edict_t * self)796 void respawn(edict_t *self){
797 // spectator's don't leave bodies
798 if(self->movetype != MOVETYPE_NOCLIP)
799 CopyToBodyQue(self);
800 self->svflags &= ~SVF_NOCLIENT;
801 PutClientInServer(self);
802
803 // add a teleportation effect
804 self->s.event = EV_PLAYER_TELEPORT;
805
806 // hold in place briefly
807 self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
808 self->client->ps.pmove.pm_time = 14;
809
810 self->client->respawn_time = level.time;
811 }
812
813 /*
814 * only called when pers.spectator changes
815 * note that resp.spectator should be the opposite of pers.spectator here
816 */
spectator_respawn(edict_t * ent)817 void spectator_respawn(edict_t *ent){
818 int i, numspec;
819
820 // if the user wants to become a spectator, make sure he doesn't
821 // exceed max_spectators
822
823 if(ent->client->pers.spectator){
824 char *value = Info_ValueForKey(ent->client->pers.userinfo, "spectator");
825 if(*spectator_password->string &&
826 strcmp(spectator_password->string, "none") &&
827 strcmp(spectator_password->string, value)){
828 gi.cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n");
829 ent->client->pers.spectator = false;
830 gi.WriteByte(svc_stufftext);
831 gi.WriteString("spectator 0\n");
832 gi.unicast(ent, true);
833 return;
834 }
835
836 // count spectators
837 for(i = 1, numspec = 0; i <= maxclients->value; i++)
838 if(g_edicts[i].inuse && g_edicts[i].client->pers.spectator)
839 numspec++;
840
841 if(numspec >= maxspectators->value){
842 gi.cprintf(ent, PRINT_HIGH, "Server spectator limit is full.");
843 ent->client->pers.spectator = false;
844 // reset his spectator var
845 gi.WriteByte(svc_stufftext);
846 gi.WriteString("spectator 0\n");
847 gi.unicast(ent, true);
848 return;
849 }
850 } else {
851 // he was a spectator and wants to join the game
852 // he must have the right password
853 char *value = Info_ValueForKey(ent->client->pers.userinfo, "password");
854 if(*password->string && strcmp(password->string, "none") &&
855 strcmp(password->string, value)){
856 gi.cprintf(ent, PRINT_HIGH, "Password incorrect.\n");
857 ent->client->pers.spectator = true;
858 gi.WriteByte(svc_stufftext);
859 gi.WriteString("spectator 1\n");
860 gi.unicast(ent, true);
861 return;
862 }
863 }
864 // clear client on respawn
865 ent->client->resp.score = 0;
866
867 ent->svflags &= ~SVF_NOCLIENT;
868 PutClientInServer(ent);
869
870 // add a teleportation effect
871 if(!ent->client->pers.spectator){
872 // send effect
873 gi.WriteByte(svc_muzzleflash);
874 gi.WriteShort(ent - g_edicts);
875 gi.WriteByte(MZ_LOGIN);
876 gi.multicast(ent->s.origin, MULTICAST_PVS);
877
878 // hold in place briefly
879 ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
880 ent->client->ps.pmove.pm_time = 14;
881 }
882
883 ent->client->respawn_time = level.time;
884
885 if(ent->client->pers.spectator)
886 gi.bprintf(PRINT_HIGH, "%s likes to watch\n", ent->client->pers.netname);
887 else
888 gi.bprintf(PRINT_HIGH, "%s wants some\n", ent->client->pers.netname);
889 }
890
891
892 char *male_skins[] = {
893 "cipher", "claymore", "flak", "grunt", "howitzer", "major",
894 "nightops", "pointman", "psycho", "rampage", "razor",
895 "recon", "scout", "sniper", "viper"
896 };
897
898 int male_skins_count = 15;
899
900 /*
901 PutClientInServer
902
903 Called when a player connects to a server or respawns in
904 a deathmatch.
905 */
PutClientInServer(edict_t * ent)906 void PutClientInServer(edict_t *ent){
907 vec3_t mins = { -16, -16, -24};
908 vec3_t maxs = {16, 16, 32};
909 int index;
910 vec3_t spawn_origin, spawn_angles;
911 gclient_t *client;
912 int i;
913 client_persistant_t saved;
914 client_respawn_t resp;
915
916 // find a spawn point
917 // do it before setting health back up, so farthest
918 // ranging doesn't count this client
919 SelectSpawnPoint(ent, spawn_origin, spawn_angles);
920
921 index = ent - g_edicts - 1;
922 client = ent->client;
923
924 // deathmatch wipes most client data every spawn
925 char userinfo[MAX_INFO_STRING];
926
927 resp = client->resp;
928 memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
929 InitClientPersistant(client);
930 ClientUserinfoChanged(ent, userinfo);
931
932 // clear everything but the persistant data
933 saved = client->pers;
934 memset(client, 0, sizeof(*client));
935 client->pers = saved;
936 if(client->pers.health <= 0)
937 InitClientPersistant(client);
938 client->resp = resp;
939
940 // copy some data from the client to the entity
941 FetchClientEntData(ent);
942
943 // clear entity values
944 ent->groundentity = NULL;
945 ent->client = &game.clients[index];
946 ent->takedamage = DAMAGE_AIM;
947 ent->movetype = MOVETYPE_WALK;
948 ent->viewheight = 22;
949 ent->inuse = true;
950 ent->classname = "player";
951 ent->mass = 200;
952 ent->solid = SOLID_BBOX;
953 ent->deadflag = DEAD_NO;
954 ent->air_finished = level.time + 12;
955 ent->clipmask = MASK_PLAYERSOLID;
956 ent->model = "players/male/tris.md2";
957 ent->pain = player_pain;
958 ent->die = player_die;
959 ent->waterlevel = 0;
960 ent->watertype = 0;
961 ent->flags &= ~FL_NO_KNOCKBACK;
962 ent->svflags &= ~SVF_DEADMONSTER;
963
964 VectorCopy(mins, ent->mins);
965 VectorCopy(maxs, ent->maxs);
966 VectorClear(ent->velocity);
967
968 // clear playerstate values
969 memset(&ent->client->ps, 0, sizeof(client->ps));
970
971 client->ps.pmove.origin[0] = spawn_origin[0] * 8;
972 client->ps.pmove.origin[1] = spawn_origin[1] * 8;
973 client->ps.pmove.origin[2] = spawn_origin[2] * 8;
974
975 if(((int)dmflags->value & DF_FIXED_FOV)){
976 client->ps.fov = 90;
977 } else {
978 client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
979 if(client->ps.fov < 1)
980 client->ps.fov = 90;
981 else if(client->ps.fov > 160)
982 client->ps.fov = 160;
983 }
984
985 client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
986
987 // clear entity state values
988 ent->s.effects = 0;
989 ent->s.modelindex = 255; // will use the skin specified model
990 ent->s.modelindex2 = 255; // custom gun model
991 // sknum is player num and weapon number
992 // weapon number will be added in changeweapon
993 ent->s.skinnum = ent - g_edicts - 1;
994
995 ent->s.frame = 0;
996 VectorCopy(spawn_origin, ent->s.origin);
997 ent->s.origin[2] += 1; // make sure off ground
998 VectorCopy(ent->s.origin, ent->s.old_origin);
999
1000 // set the delta angle
1001 for(i = 0; i < 3; i++){
1002 client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
1003 }
1004
1005 ent->s.angles[PITCH] = 0;
1006 ent->s.angles[YAW] = spawn_angles[YAW];
1007 ent->s.angles[ROLL] = 0;
1008 VectorCopy(ent->s.angles, client->ps.viewangles);
1009 VectorCopy(ent->s.angles, client->v_angle);
1010
1011 // spawn a spectator
1012 if(client->pers.spectator || (teams->value && !client->pers.team)){
1013 client->chase_target = NULL;
1014
1015 client->pers.spectator = true;
1016 client->resp.spectator = true;
1017
1018 ent->movetype = MOVETYPE_NOCLIP;
1019 ent->solid = SOLID_NOT;
1020 ent->svflags |= SVF_NOCLIENT;
1021 ent->client->ps.gunindex = 0;
1022 gi.linkentity(ent);
1023 return;
1024 }
1025
1026 ent->svflags &= ~SVF_NOCLIENT;
1027 client->resp.spectator = false;
1028
1029 gi.linkentity(ent);
1030
1031 // force the current weapon up
1032 client->newweapon = client->pers.weapon;
1033 ChangeWeapon(ent);
1034 }
1035
1036 /*
1037 ClientBeginDeathmatch
1038
1039 A client has just connected to the server in
1040 deathmatch mode, so clear everything out before starting them.
1041 */
ClientBeginDeathmatch(edict_t * ent)1042 void ClientBeginDeathmatch(edict_t *ent){
1043 char welcome[256];
1044
1045 G_InitEdict(ent);
1046
1047 InitClientResp(ent->client);
1048
1049 // locate ent at a spawn point
1050 PutClientInServer(ent);
1051
1052 if(level.intermissiontime){
1053 MoveClientToIntermission(ent);
1054 } else {
1055 // send effect
1056 gi.WriteByte(svc_muzzleflash);
1057 gi.WriteShort(ent - g_edicts);
1058 gi.WriteByte(MZ_LOGIN);
1059 gi.multicast(ent->s.origin, MULTICAST_PVS);
1060 }
1061
1062 if(hook->value){ // hook
1063 gi.WriteByte(svc_stufftext);
1064 gi.WriteString("alias +hook \"cmd hook\"\n");
1065 gi.unicast(ent, true);
1066
1067 gi.WriteByte(svc_stufftext);
1068 gi.WriteString("alias -hook \"cmd unhook\"\n");
1069 gi.unicast(ent, true);
1070 }
1071
1072 memset(welcome, 0, sizeof(welcome));
1073
1074 strcat(welcome, "Welcome to Qmass.net Resurrection DM\n"
1075 "Visit http://jaydolan.com/quake.html\n\n");
1076
1077 if(sv_allow_vote->value) //tell client about voting
1078 strcat(welcome, "Voting is allowed\nType vote at the console");
1079
1080 gi.centerprintf(ent, welcome);
1081
1082 gi.bprintf(PRINT_HIGH, "%s wants some\n", ent->client->pers.netname);
1083
1084 // make sure all view stuff is valid
1085 ClientEndServerFrame(ent);
1086 }
1087
1088
1089 /*
1090 ClientBegin
1091
1092 called when a client has finished connecting, and is ready
1093 to be placed into the game. This will happen every level load.
1094 */
ClientBegin(edict_t * ent)1095 void ClientBegin(edict_t *ent){
1096
1097 int playernum = ent - g_edicts - 1;
1098
1099 ent->client = game.clients + playernum;
1100 ClientBeginDeathmatch(ent);
1101
1102 srand(time(NULL)); //set random seed
1103
1104 //random male skin
1105 snprintf(ent->client->pers.skin, 16,
1106 "male/%s", male_skins[rand() % male_skins_count]
1107 );
1108
1109 gi.configstring(CS_PLAYERSKINS + playernum,
1110 va("%s\\%s", ent->client->pers.netname, ent->client->pers.skin)
1111 );
1112
1113 ent->client->pers.team = NULL;
1114 }
1115
1116 /*
1117 ClientUserInfoChanged
1118
1119 called whenever the player updates a userinfo variable.
1120
1121 The game can override any of the settings in place
1122 (forcing skins or names, etc) before copying it off.
1123 */
ClientUserinfoChanged(edict_t * ent,char * userinfo)1124 void ClientUserinfoChanged(edict_t *ent, char *userinfo){
1125 char *s;
1126 int playernum;
1127
1128 // check for malformed or illegal info strings
1129 if(!Info_Validate(userinfo)){
1130 strcpy(userinfo, "\\name\\badinfo\\skin\\male/grunt");
1131 }
1132
1133 // set name
1134 s = Info_ValueForKey(userinfo, "name");
1135 strncpy(ent->client->pers.netname, s, sizeof(ent->client->pers.netname) - 1);
1136
1137 if(!teams->value){ // set spectator
1138 s = Info_ValueForKey(userinfo, "spectator");
1139 if(*s && strcmp(s, "0"))
1140 ent->client->pers.spectator = true;
1141 else
1142 ent->client->pers.spectator = false;
1143 }
1144
1145 playernum = ent - g_edicts - 1;
1146
1147 // combine name and skin into a configstring
1148 gi.configstring(CS_PLAYERSKINS + playernum,
1149 va("%s\\%s", ent->client->pers.netname, ent->client->pers.skin)
1150 );
1151
1152 // fov
1153 if(((int)dmflags->value & DF_FIXED_FOV)){
1154 ent->client->ps.fov = 90;
1155 } else {
1156 ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
1157 if(ent->client->ps.fov < 1)
1158 ent->client->ps.fov = 90;
1159 else if(ent->client->ps.fov > 160)
1160 ent->client->ps.fov = 160;
1161 }
1162
1163 // handedness
1164 s = Info_ValueForKey(userinfo, "hand");
1165 if(strlen(s)){
1166 ent->client->pers.hand = atoi(s);
1167 }
1168
1169 // save off the userinfo in case we want to check something later
1170 strncpy(ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo) - 1);
1171 }
1172
1173
1174 /*
1175 ClientConnect
1176
1177 Called when a player begins connecting to the server.
1178 The game can refuse entrance to a client by returning false.
1179 If the client is allowed, the connection process will continue
1180 and eventually get to ClientBegin()
1181 Changing levels will NOT cause this to be called again, but
1182 loadgames will.
1183 */
ClientConnect(edict_t * ent,char * userinfo)1184 qboolean ClientConnect(edict_t *ent, char *userinfo){
1185 char *value;
1186
1187 // check to see if they are on the banned IP list
1188 value = Info_ValueForKey(userinfo, "ip");
1189 if(SV_FilterPacket(value)){
1190 Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
1191 return false;
1192 }
1193
1194 // check for a spectator
1195 value = Info_ValueForKey(userinfo, "spectator");
1196 if(*value && strcmp(value, "0")){
1197 int i, numspec;
1198
1199 if(*spectator_password->string &&
1200 strcmp(spectator_password->string, "none") &&
1201 strcmp(spectator_password->string, value)){
1202 Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect.");
1203 return false;
1204 }
1205
1206 // count spectators
1207 for(i = numspec = 0; i < maxclients->value; i++)
1208 if(g_edicts[i + 1].inuse && g_edicts[i + 1].client->pers.spectator)
1209 numspec++;
1210
1211 if(numspec >= maxspectators->value){
1212 Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full.");
1213 return false;
1214 }
1215 } else {
1216 // check for a password
1217 value = Info_ValueForKey(userinfo, "password");
1218 if(*password->string && strcmp(password->string, "none") &&
1219 strcmp(password->string, value)){
1220 Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect.");
1221 return false;
1222 }
1223 }
1224
1225 // they can connect
1226 ent->client = game.clients + (ent - g_edicts - 1);
1227
1228 InitClientResp(ent->client);
1229 InitClientPersistant(ent->client);
1230
1231 ClientUserinfoChanged(ent, userinfo);
1232
1233 if(maxclients->value > 1)
1234 gi.dprintf("%s connected\n", ent->client->pers.netname);
1235
1236 #ifdef HAVE_MYSQL
1237 if(mysql != NULL){ // insert to db
1238
1239 snprintf(sql, sizeof(sql) - 1, "insert into connect values(null, now(), null, '%s', '%s')",
1240 ent->client->pers.netname, Info_ValueForKey(userinfo, "ip")
1241 );
1242 sql[sizeof(sql) - 1] = 0;
1243 mysql_query(mysql, sql);
1244 }
1245 #endif
1246
1247 ent->svflags = 0; // make sure we start with known default
1248 return true;
1249 }
1250
1251 /*
1252 ClientDisconnect
1253
1254 Called when a player drops from the server.
1255 Will not be called between levels.
1256 */
ClientDisconnect(edict_t * ent)1257 void ClientDisconnect(edict_t *ent){
1258 int playernum;
1259
1260 if(!ent->client)
1261 return;
1262
1263 gi.bprintf(PRINT_HIGH, "%s couldn't hang\n", ent->client->pers.netname);
1264
1265 // send effect
1266 gi.WriteByte(svc_muzzleflash);
1267 gi.WriteShort(ent - g_edicts);
1268 gi.WriteByte(MZ_LOGOUT);
1269 gi.multicast(ent->s.origin, MULTICAST_PVS);
1270
1271 #ifdef HAVE_MYSQL
1272 if(mysql != NULL){ // insert to db
1273
1274 snprintf(sql, sizeof(sql) - 1, "update connect set disconnect = now() "
1275 "where disconnect is null and player = '%s'", ent->client->pers.netname
1276 );
1277
1278 sql[sizeof(sql) - 1] = 0;
1279 mysql_query(mysql, sql);
1280 }
1281 #endif
1282
1283 hook_reset(ent->client->hook); // hook
1284
1285 gi.unlinkentity(ent);
1286 ent->s.modelindex = 0;
1287 ent->solid = SOLID_NOT;
1288 ent->inuse = false;
1289 ent->classname = "disconnected";
1290
1291 playernum = ent - g_edicts - 1;
1292 gi.configstring(CS_PLAYERSKINS + playernum, "");
1293 }
1294
1295
1296
1297
1298 edict_t *pm_passent;
1299
1300 // pmove doesn't need to know about passent and contentmask
PM_trace(vec3_t start,vec3_t mins,vec3_t maxs,vec3_t end)1301 trace_t PM_trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end){
1302 if(pm_passent->health > 0)
1303 return gi.trace(start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
1304 else
1305 return gi.trace(start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
1306 }
1307
CheckBlock(void * b,int c)1308 unsigned CheckBlock(void *b, int c){
1309 int v, i;
1310 v = 0;
1311 for(i = 0; i < c; i++)
1312 v += ((byte *)b)[i];
1313 return v;
1314 }
1315
1316 /*
1317 ClientThink
1318
1319 This will be called once for each client frame, which will
1320 usually be a couple times for each server frame.
1321 */
ClientThink(edict_t * ent,usercmd_t * ucmd)1322 void ClientThink(edict_t *ent, usercmd_t *ucmd){
1323 gclient_t *client;
1324 edict_t *other;
1325 int i, j;
1326 pmove_t pm;
1327
1328 level.current_entity = ent;
1329 client = ent->client;
1330
1331 if((client->hook_state == HOOK_ON) && client->hook)
1332 hook_service(client->hook);
1333
1334 if(level.intermissiontime){
1335 client->ps.pmove.pm_type = PM_FREEZE;
1336 // can exit intermission after five seconds
1337 if(level.time > level.intermissiontime + 5.0
1338 &&(ucmd->buttons & BUTTON_ANY))
1339 level.exitintermission = true;
1340 return;
1341 }
1342
1343 pm_passent = ent;
1344
1345 if(ent->client->chase_target){
1346
1347 client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
1348 client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
1349 client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
1350
1351 } else {
1352
1353 // set up for pmove
1354 memset(&pm, 0, sizeof(pm));
1355
1356 if(ent->movetype == MOVETYPE_NOCLIP)
1357 client->ps.pmove.pm_type = PM_SPECTATOR;
1358 else if(ent->s.modelindex != 255)
1359 client->ps.pmove.pm_type = PM_GIB;
1360 else if(ent->deadflag)
1361 client->ps.pmove.pm_type = PM_DEAD;
1362 else
1363 client->ps.pmove.pm_type = PM_NORMAL;
1364
1365 if(client->hook_state == HOOK_ON) // hook
1366 client->ps.pmove.gravity = 0;
1367 else
1368 client->ps.pmove.gravity = sv_gravity->value;
1369
1370 pm.s = client->ps.pmove;
1371
1372 for(i = 0; i < 3; i++){
1373 pm.s.origin[i] = ent->s.origin[i] * 8;
1374 pm.s.velocity[i] = ent->velocity[i] * 8;
1375 }
1376
1377 if(memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))){
1378 pm.snapinitial = true;
1379 }
1380
1381 pm.cmd = *ucmd;
1382
1383 pm.trace = PM_trace; // adds default parms
1384 pm.pointcontents = gi.pointcontents;
1385
1386 // perform a pmove
1387 gi.Pmove(&pm);
1388
1389 // save results of pmove
1390 client->ps.pmove = pm.s;
1391 client->old_pmove = pm.s;
1392
1393 for(i = 0; i < 3; i++){
1394 ent->s.origin[i] = pm.s.origin[i] * 0.125;
1395 ent->velocity[i] = pm.s.velocity[i] * 0.125;
1396 }
1397
1398 VectorCopy(pm.mins, ent->mins);
1399 VectorCopy(pm.maxs, ent->maxs);
1400
1401 client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
1402 client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
1403 client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
1404
1405 if(ent->groundentity && !pm.groundentity &&(pm.cmd.upmove >= 10) &&(pm.waterlevel == 0)){
1406 gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
1407 PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
1408 }
1409
1410 ent->viewheight = pm.viewheight;
1411 ent->waterlevel = pm.waterlevel;
1412 ent->watertype = pm.watertype;
1413 ent->groundentity = pm.groundentity;
1414 if(pm.groundentity)
1415 ent->groundentity_linkcount = pm.groundentity->linkcount;
1416
1417 if(ent->deadflag){
1418 client->ps.viewangles[ROLL] = 40;
1419 client->ps.viewangles[PITCH] = -15;
1420 client->ps.viewangles[YAW] = client->killer_yaw;
1421 } else {
1422 VectorCopy(pm.viewangles, client->v_angle);
1423 VectorCopy(pm.viewangles, client->ps.viewangles);
1424 }
1425
1426 gi.linkentity(ent);
1427
1428 if(ent->movetype != MOVETYPE_NOCLIP)
1429 G_TouchTriggers(ent);
1430
1431 // touch other objects
1432 for(i = 0; i < pm.numtouch; i++){
1433 other = pm.touchents[i];
1434 for(j = 0; j < i; j++)
1435 if(pm.touchents[j] == other)
1436 break;
1437 if(j != i)
1438 continue; // duplicated
1439 if(!other->touch)
1440 continue;
1441 other->touch(other, ent, NULL, NULL);
1442 }
1443
1444 }
1445
1446 client->oldbuttons = client->buttons;
1447 client->buttons = ucmd->buttons;
1448 client->latched_buttons |= client->buttons & ~client->oldbuttons;
1449
1450 // save light level the player is standing on for
1451 // monster sighting AI
1452 ent->light_level = ucmd->lightlevel;
1453
1454 // fire weapon from final position if needed
1455 if(client->latched_buttons & BUTTON_ATTACK){
1456 if(client->resp.spectator){
1457
1458 client->latched_buttons = 0;
1459
1460 if(client->chase_target){
1461 client->chase_target = NULL;
1462 client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
1463 } else
1464 GetChaseTarget(ent);
1465
1466 } else if(!client->weapon_thunk){
1467 client->weapon_thunk = true;
1468 Think_Weapon(ent);
1469 }
1470 }
1471
1472 if(client->resp.spectator){
1473 if(ucmd->upmove >= 10){
1474 if(!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)){
1475 client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
1476 if(client->chase_target)
1477 ChaseNext(ent);
1478 else
1479 GetChaseTarget(ent);
1480 }
1481 } else
1482 client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
1483 }
1484
1485 // update chase cam if being followed
1486 for(i = 1; i <= maxclients->value; i++){
1487 other = g_edicts + i;
1488 if(other->inuse && other->client->chase_target == ent)
1489 UpdateChaseCam(other);
1490 }
1491
1492 if((client->hook_state == HOOK_ON) && (VectorLength(ent->velocity) < 10)) {
1493 client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; // hook
1494 } else {
1495 client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
1496 }
1497 }
1498
1499
1500 /*
1501 ClientBeginServerFrame
1502
1503 This will be called once for each server frame, before running
1504 any other entities in the world.
1505 */
ClientBeginServerFrame(edict_t * ent)1506 void ClientBeginServerFrame(edict_t *ent){
1507 gclient_t *client;
1508 int buttonMask;
1509
1510 if(level.intermissiontime)
1511 return;
1512
1513 client = ent->client;
1514
1515 if(client->pers.spectator != client->resp.spectator &&
1516 (level.time - client->respawn_time) >= 5){
1517 spectator_respawn(ent);
1518 return;
1519 }
1520
1521 // run weapon animations if it hasn't been done by a ucmd_t
1522 if(!client->weapon_thunk && !client->resp.spectator)
1523 Think_Weapon(ent);
1524 else
1525 client->weapon_thunk = false;
1526
1527 if(ent->deadflag){
1528 // wait for any button just going down
1529 if(level.time > client->respawn_time){
1530 // in deathmatch, only wait for attack button
1531 buttonMask = BUTTON_ATTACK;
1532
1533 if((client->latched_buttons & buttonMask) || ((int)dmflags->value & DF_FORCE_RESPAWN)){
1534 respawn(ent);
1535 client->latched_buttons = 0;
1536 }
1537 }
1538 return;
1539 }
1540
1541 client->latched_buttons = 0;
1542 }
1543