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