1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 // g_combat.c
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include "g_local.h"
27 
28 /*
29 ============
30 CanDamage
31 
32 Returns true if the inflictor can directly damage the target.  Used for
33 explosions and melee attacks.
34 ============
35 */
CanDamage(edict_t * targ,edict_t * inflictor)36 qboolean CanDamage (edict_t *targ, edict_t *inflictor)
37 {
38 	vec3_t	dest;
39 	trace_t	trace;
40 
41 // bmodels need special checking because their origin is 0,0,0
42 	if (targ->movetype == MOVETYPE_PUSH)
43 	{
44 		VectorAdd (targ->absmin, targ->absmax, dest);
45 		VectorScale (dest, 0.5, dest);
46 		trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
47 		if (trace.fraction == 1.0)
48 			return true;
49 		if (trace.ent == targ)
50 			return true;
51 		return false;
52 	}
53 
54 	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID);
55 	if (trace.fraction == 1.0)
56 		return true;
57 
58 	VectorCopy (targ->s.origin, dest);
59 	dest[0] += 15.0;
60 	dest[1] += 15.0;
61 	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
62 	if (trace.fraction == 1.0)
63 		return true;
64 
65 	VectorCopy (targ->s.origin, dest);
66 	dest[0] += 15.0;
67 	dest[1] -= 15.0;
68 	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
69 	if (trace.fraction == 1.0)
70 		return true;
71 
72 	VectorCopy (targ->s.origin, dest);
73 	dest[0] -= 15.0;
74 	dest[1] += 15.0;
75 	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
76 	if (trace.fraction == 1.0)
77 		return true;
78 
79 	VectorCopy (targ->s.origin, dest);
80 	dest[0] -= 15.0;
81 	dest[1] -= 15.0;
82 	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
83 	if (trace.fraction == 1.0)
84 		return true;
85 
86 
87 	return false;
88 }
89 
90 
91 /*
92 ============
93 Killed
94 ============
95 */
Killed(edict_t * targ,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)96 void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
97 {
98 	if (targ->health < -999)
99 		targ->health = -999;
100 
101 	if (targ->monsterinfo.aiflags & AI_NPC) { //cows never really die, they just return to their
102 											  //original spawn points
103 		//send an effect
104 		gi.WriteByte (svc_temp_entity);
105 		gi.WriteByte (TE_BFG_BIGEXPLOSION);
106 		gi.WritePosition (targ->s.origin);
107 		gi.multicast (targ->s.origin, MULTICAST_PHS);
108 
109 		targ->health = targ->max_health;
110 		targ->s.event = EV_PLAYER_TELEPORT;
111 		targ->enemy = NULL;
112 		VectorCopy(targ->s.spawn_pos, targ->s.origin);
113 		return;
114 	}
115 
116 	targ->enemy = attacker;
117 
118 	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
119 	{
120 //		targ->svflags |= SVF_DEADMONSTER;	// now treat as a different content type
121 		if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY))
122 		{
123 			level.killed_monsters++;
124 			// medics won't heal monsters that they kill themselves
125 			if (strcmp(attacker->classname, "monster_medic") == 0)
126 				targ->owner = attacker;
127 		}
128 	}
129 
130 	if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE)
131 	{	// doors, triggers, etc
132 		targ->die (targ, inflictor, attacker, damage, point);
133 		return;
134 	}
135 
136 	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
137 	{
138 		targ->touch = NULL;
139 		monster_death_use (targ);
140 	}
141 
142 	targ->die (targ, inflictor, attacker, damage, point);
143 }
144 
145 
146 /*
147 ================
148 SpawnDamage
149 ================
150 */
SpawnDamage(int type,vec3_t origin,vec3_t normal,int damage)151 void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage)
152 {
153 	if (damage > 255)
154 		damage = 255;
155 	gi.WriteByte (svc_temp_entity);
156 	gi.WriteByte (type);
157 //	gi.WriteByte (damage);
158 	gi.WritePosition (origin);
159 	gi.WriteDir (normal);
160 	gi.multicast (origin, MULTICAST_PVS);
161 }
162 
163 
164 /*
165 ============
166 T_Damage
167 
168 targ		entity that is being damaged
169 inflictor	entity that is causing the damage
170 attacker	entity that caused the inflictor to damage targ
171 	example: targ=monster, inflictor=rocket, attacker=player
172 
173 dir			direction of the attack
174 point		point at which the damage is being inflicted
175 normal		normal vector from that point
176 damage		amount of damage being inflicted
177 knockback	force to be applied against targ as a result of the damage
178 
179 dflags		these flags are used to control how T_Damage works
180 	DAMAGE_RADIUS			damage was indirect (from a nearby explosion)
181 	DAMAGE_NO_ARMOR			armor does not protect from this damage
182 	DAMAGE_ENERGY			damage is from an energy based weapon
183 	DAMAGE_NO_KNOCKBACK		do not affect velocity, just view angles
184 	DAMAGE_BULLET			damage is from a bullet (used for ricochets)
185 	DAMAGE_NO_PROTECTION	kills godmode, armor, everything
186 ============
187 */
CheckArmor(edict_t * ent,vec3_t point,vec3_t normal,int damage,int te_sparks,int dflags)188 static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
189 {
190 	gclient_t	*client;
191 	int			save;
192 	int			index;
193 	gitem_t		*armor;
194 
195 	if (!damage)
196 		return 0;
197 
198 	client = ent->client;
199 
200 	if (!client)
201 		return 0;
202 
203 	if (dflags & DAMAGE_NO_ARMOR)
204 		return 0;
205 
206 	index = ArmorIndex (ent);
207 	if (!index)
208 		return 0;
209 
210 	armor = GetItemByIndex (index);
211 
212 	if (dflags & DAMAGE_ENERGY)
213 		save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage);
214 	else
215 		save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage);
216 	if (save >= client->pers.inventory[index])
217 		save = client->pers.inventory[index];
218 
219 	if (!save)
220 		return 0;
221 
222 	client->pers.inventory[index] -= save;
223 	SpawnDamage (te_sparks, point, normal, save);
224 
225 	return save;
226 }
227 
CheckTeamDamage(edict_t * targ,edict_t * attacker)228 qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
229 {
230 		//FIXME make the next line real and uncomment this block
231 		// if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
232 	return false;
233 }
234 
VerifyHeadShot(vec3_t point,vec3_t dir,float height,vec3_t newpoint)235 void VerifyHeadShot( vec3_t point, vec3_t dir, float height, vec3_t newpoint)
236 {
237         vec3_t normdir;
238         vec3_t normdir2;
239 
240 
241         VectorNormalize2(dir, normdir);
242         VectorScale( normdir, height, normdir2 );
243         VectorAdd( point, normdir2, newpoint );
244 }
245 
246 #define HEAD_HEIGHT 12.0
247 #define CROUCHING_MAXS2 16
248 
T_Damage(edict_t * targ,edict_t * inflictor,edict_t * attacker,vec3_t dir,vec3_t point,vec3_t normal,int damage,int knockback,int dflags,int mod)249 void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod)
250 {
251 	gclient_t	*client;
252 	int			take;
253 	int			save;
254 	int			asave;
255 	int			te_sparks;
256 	int         head_success = 0;
257     float       z_rel;
258     int         height;
259     float		from_top;
260     float       targ_maxs2;
261 
262 	if (!targ->takedamage)
263 		return;
264 
265 	if(mod != MOD_TELEFRAG)
266 		if(targ->inuse && targ->client)
267 			if(targ->client->spawnprotected)
268 				return;
269 
270 	//headshots for disruptors
271 	targ_maxs2 = targ->maxs[2];
272     if (targ_maxs2 == 4)
273 		targ_maxs2 = CROUCHING_MAXS2;
274 
275     height = abs(targ->mins[2]) + targ_maxs2;
276 
277     if (targ->client && mod == MOD_DISRUPTOR)
278 	{
279 		z_rel = point[2] - targ->s.origin[2];
280         from_top = targ_maxs2 - z_rel;
281 
282 		if (from_top < 0.0)
283 			from_top = 0.0;
284 
285         if ( from_top < 2*HEAD_HEIGHT )
286 		{
287 			vec3_t new_point;
288             VerifyHeadShot( point, dir, HEAD_HEIGHT, new_point );
289             VectorSubtract( new_point, targ->s.origin, new_point );
290 
291             if ( (targ_maxs2 - new_point[2]) < HEAD_HEIGHT
292 				&& (abs(new_point[1])) < HEAD_HEIGHT*.8
293                 && (abs(new_point[0])) < HEAD_HEIGHT*.8 ) {
294 				head_success = 1;
295             }
296         }
297 
298         if ( head_success )
299 		{
300 			damage = damage*1.8 + 1;
301             if (attacker->client)
302 				mod = MOD_HEADSHOT;
303         }
304     }
305 
306 	// friendly fire avoidance
307 	// if enabled you can't hurt teammates (but you can hurt yourself)
308 	// knockback still occurs
309 	if ((targ != attacker) && ((deathmatch->value && (dmflags->integer & (DF_SKINTEAMS))) || ctf->value || tca->value || cp->value))
310 	{
311 		if (OnSameTeam (targ, attacker) && mod != MOD_TELEFRAG) //telefrag kills no matter what
312 		{
313 			if (dmflags->integer & DF_NO_FRIENDLY_FIRE)
314 				damage = 0;
315 			else
316 				mod |= MOD_FRIENDLY_FIRE;
317 
318 			//educate the noobs....
319 			safe_centerprintf(attacker, "Stop shooting your teammates!!!");
320 		}
321 	}
322 
323 	if (targ == attacker) {
324 		damage *= wep_selfdmgmulti->value;
325 	}
326 
327 	meansOfDeath = mod;
328 
329 	// easy mode takes half damage
330 	if (skill->value == 0 && deathmatch->value == 0 && targ->client)
331 	{
332 		damage *= 0.5;
333 		if (!damage)
334 			damage = 1;
335 	}
336 
337 	client = targ->client;
338 
339 	if (dflags & DAMAGE_BULLET)
340 		te_sparks = TE_BULLET_SPARKS;
341 	else
342 		te_sparks = TE_SPARKS;
343 
344 	VectorNormalize(dir);
345 
346 	if (targ->flags & FL_NO_KNOCKBACK)
347 		knockback = 0;
348 
349 	// figure momentum add
350 	if (!(dflags & DAMAGE_NO_KNOCKBACK))
351 	{
352 		if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
353 		{
354 			vec3_t	kvel;
355 			float	mass;
356 
357 			if (targ->mass < 50)
358 				mass = 50;
359 			else
360 				mass = targ->mass;
361 
362 			if (targ->client  && attacker == targ)
363 				VectorScale (dir, 1600.0 * (float)knockback / mass, kvel);	// the rocket jump hack...
364 			else
365 				VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
366 
367 			VectorAdd (targ->velocity, kvel, targ->velocity);
368 		}
369 	}
370 	//diminish plasma splash, as we want this to be minimal, as it's more used for tricks
371 	if(mod == MOD_PLASMA_SPLASH)
372 	{
373 		// damage /= (1+ (int)(random() * 10.0));
374 		damage /= 6; // median of formerly random 1..11
375 	}
376 
377 	take = damage;
378 	save = 0;
379 
380 	// check for godmode
381 	if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
382 	{
383 		take = 0;
384 		save = damage;
385 		SpawnDamage (te_sparks, point, normal, save);
386 	}
387 
388 	// check for invincibility
389 	if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
390 	{
391 		if (targ->pain_debounce_time < level.time)
392 		{
393 			gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
394 			targ->pain_debounce_time = level.time + 2;
395 		}
396 		if(mod == MOD_TRIGGER_HURT)
397 		{
398 			take = 0;
399 			save = damage;
400 		}
401 		else
402 		{
403 			take = damage/3;
404 			save = 0;
405 		}
406 	}
407 
408 	asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
409 	take -= asave;
410 
411 	//treat cheat/powerup savings the same as armor
412 	asave += save;
413 
414 	// team damage avoidance
415 	if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
416 		return;
417 
418 // do the damage
419 	if (take)
420 	{
421 		if (client)
422 		{
423 			if (targ->ctype == 0) //alien, robot, human
424 				SpawnDamage (TE_GREENBLOOD, point, normal, take);
425 			else if (targ->ctype == 2)
426 				SpawnDamage (TE_GUNSHOT, point, normal, take);
427 			else
428 				SpawnDamage (TE_BLOOD, point, normal, take);
429 		}
430 		else
431 		{
432 			if (targ->ctype == 0) //alien, robot, human
433 				SpawnDamage (TE_GREENBLOOD, point, normal, take);
434 			else if (targ->ctype == 2)
435 				SpawnDamage (TE_GUNSHOT, point, normal, take);
436 			else
437 				SpawnDamage (TE_BLOOD, point, normal, take);
438 			if(tca->value)
439 			{
440 				if(!(strcmp(targ->classname, "misc_redspidernode")) || !(strcmp(targ->classname, "misc_bluespidernode")))
441 					safe_centerprintf(attacker, "Spider health at %i percent", 100*(targ->health-take)/600);
442 			}
443 			if(g_tactical->value)
444 			{
445 				if(!strcmp(targ->classname, "alien computer"))
446 					safe_centerprintf(attacker, "Alien Computer health at %i percent", 100*(targ->health-take)/1500);
447 				else if(!strcmp(targ->classname, "human computer"))
448 					safe_centerprintf(attacker, "Human Computer health at %i percent", 100*(targ->health-take)/1500);
449 				else if(!strcmp(targ->classname, "alien powersrc"))
450 					safe_centerprintf(attacker, "Alien Power Source health at %i percent", 100*(targ->health-take)/1500);
451 				else if(!strcmp(targ->classname, "human powersrc"))
452 					safe_centerprintf(attacker, "Human Power Source health at %i percent", 100*(targ->health-take)/1500);
453 				else if(!strcmp(targ->classname, "alien ammodepot"))
454 					safe_centerprintf(attacker, "Alien Ammo Depot health at %i percent", 100*(targ->health-take)/1500);
455 				else if(!strcmp(targ->classname, "human ammodepot"))
456 					safe_centerprintf(attacker, "Human Ammo Depot health at %i percent", 100*(targ->health-take)/1500);
457 			}
458 		}
459 
460 		targ->health = targ->health - take;
461 
462 		if (targ->health <= 0)
463 		{
464 			if (client)
465 				targ->flags |= FL_NO_KNOCKBACK;
466 			Killed (targ, inflictor, attacker, take, point);
467 			return;
468 		}
469 	}
470 
471 	if (client)
472 	{
473 		if (!(targ->flags & FL_GODMODE) && (take) && (targ->pain))
474 			targ->pain (targ, attacker, knockback, take);
475 	}
476 	else if (take)
477 	{
478 		if (targ->pain)
479 			targ->pain (targ, attacker, knockback, take);
480 	}
481 
482 	// add to the damage inflicted on a player this frame
483 	// the total will be turned into screen blends and view angle kicks
484 	// at the end of the frame
485 	if (client)
486 	{
487 		client->damage_armor += asave;
488 		client->damage_blood += take;
489 		client->damage_knockback += knockback;
490 		VectorCopy (point, client->damage_from);
491 	}
492 }
493 
494 
495 /*
496 ============
497 T_RadiusDamage
498 ============
499 */
T_RadiusDamage(edict_t * inflictor,edict_t * attacker,float damage,edict_t * ignore,float radius,int mod,int weapon)500 void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod, int weapon)
501 {
502 	float	points;
503 	edict_t	*ent = NULL;
504 	vec3_t	v;
505 	vec3_t	dir;
506 
507 	while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
508 	{
509 		if (ent == ignore)
510 			continue;
511 		if (!ent->takedamage)
512 			continue;
513 
514 		VectorAdd (ent->mins, ent->maxs, v);
515 		VectorMA (ent->s.origin, 0.5, v, v);
516 		VectorSubtract (inflictor->s.origin, v, v);
517 		points = damage - 0.5 * VectorLength (v);
518 		if (ent == attacker)
519 			points = points * 0.5;
520 		if (points > 0)
521 		{
522 			if (CanDamage (ent, inflictor))
523 			{
524 				VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
525 				T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
526 				if (ent != attacker)
527 					gi.sound (attacker, CHAN_VOICE, gi.soundindex("misc/hit.wav"), 1, ATTN_STATIC, 0);
528 				if(weapon >=0)
529 					attacker->client->resp.weapon_hits[weapon]++;
530 			}
531 		}
532 	}
533 }
534